自制操作系统day6(GDTR、段描述符、PIC、实模式和保护模式、16位到32位切换、中断处理程序、idt的设定、EFLAG寄存器)(ai辅助整理)
day6
分割源文件(harib03a)
优点
- 按照处理内容进行分类,如果分得好的话,将来进行修改时,容易找到地方。
- 如果Makefile写得好,只需要编译修改过的文件,就可以提高make的速度。
- 单个源文件都不长。多个小文件比一个大文件好处理。
- 看起来很酷(笑)。
缺点 - 源文件数量增加。
- 分类分得不好的话,修改时不容易找到地方。
makefile修改
整理头文件(harib03c)
将所有的宏,常量,数据结构和函数声明都放在一个.h头文件当中,编译时将.h的内容插到.c文件当中
解析头文件中的宏,常量,数据结构和函数声明并用于后续的编译
头文件的作用
- 数据结构定义:
struct BOOTINFO
:描述了启动信息,包括屏幕分辨率、显存地址等。这些信息由启动代码(如引导扇区)提供,用于初始化操作系统的图形界面。struct SEGMENT_DESCRIPTOR
和struct GATE_DESCRIPTOR
:用于描述全局段表(GDT)和中断描述符表(IDT)的结构。
- 常量定义:
- 定义了与中断控制器相关的端口地址(如
PIC0_ICW1
)。 - 定义了颜色常量(如
COL8_000000
表示黑色,COL8_FFFFFF
表示白色)和内存地址常量(如ADR_BOOTINFO
表示启动信息的地址)。
- 定义了与中断控制器相关的端口地址(如
- 函数声明:
- 包括汇编函数(如
io_hlt
、io_cli
)和 C 函数(如init_palette
、init_screen8
)。 - 汇编函数通常用于直接与硬件交互,而 C 函数用于更高级别的操作,如初始化图形界面或设置鼠标指针。
- 包括汇编函数(如
- 模块化管理:
- 将不同功能的代码分离到不同的文件中(如 graphic.c 处理图形相关功能,
dsctbl.c
处理段表和中断表),通过头文件统一管理这些模块的接口。
- 将不同功能的代码分离到不同的文件中(如 graphic.c 处理图形相关功能,
编译时的运作
- 预处理阶段:
- 编译器在编译 bootpack.c 时,会通过
#include "bootpack.h"
将 bootpack.h 的内容插入到 bootpack.c 中。 - 头文件中的宏、常量、数据结构和函数声明会被解析并用于后续的编译。
- 编译器在编译 bootpack.c 时,会通过
- 编译阶段:
- 编译器根据头文件中声明的函数原型检查 bootpack.c 中的函数调用是否正确。
- 如果头文件中声明的函数在其他源文件中实现(如
init_palette
在 graphic.c 中实现),编译器会生成对这些函数的外部引用。
- 链接阶段:
- 编译器将 bootpack.c 和其他源文件(如 graphic.c、
dsctbl.c
)编译成目标文件后,链接器会将这些目标文件合并为一个可执行文件。 - 链接器会根据头文件中的声明找到函数和变量的定义地址,并将它们正确地链接起来。
- 编译器将 bootpack.c 和其他源文件(如 graphic.c、
- 运行时:
- 系统启动时,
bootpack.c
中的HariMain
函数作为入口点被调用。 HariMain
调用的所有函数(如init_gdtidt
、init_palette
)会根据头文件中的声明和链接器的地址分配被正确执行。
- 系统启动时,
总结
bootpack.h 是整个项目的**核心接口文件,**它将不同模块的功能整合在一起,提供了统一的接口定义。在编译时,它确保了代码的模块化和可维护性,同时在链接阶段保证了函数和变量的正确引用。
关于GDTR
_load_gdtr: ; void load_gdtr(int limit, int addr);MOV AX,[ESP+4] ; limitMOV [ESP+6],AXLGDT [ESP+6]RET
GDTR低16位为段上限,等于GDT的有效字节数-1,因为从0开始计数
高32位表示段的起始地址
为什么是 [ESP + 4]
而不是 [ESP + 2]
?
1. 栈的对齐和参数传递规则
- 在 x86 架构中,栈通常以 4 字节(32 位)对齐,即使传递的是 16 位数据,编译器也会按照 32 位对齐的规则将其压入栈中。
- 在调用
load_gdtr
函数时,limit
和addr
是作为参数传递的:limit
是一个 16 位的值,但它会被扩展为 32 位(低 16 位存储实际值,高 16 位填充为 0)。addr
是一个 32 位的值。
因此,limit
被存储在 [ESP + 4]
(从栈顶偏移 4 字节的位置),而不是 [ESP + 2]
。
GDTR详细解释
_load_gdtr: ; void load_gdtr(int limit, int addr);MOV AX,[ESP+4] ; limitMOV [ESP+6],AXLGDT [ESP+6]RET
以下是按 8位(1字节)为单位 的栈布局变化,分阶段展示 _load_gdtr
函数的执行过程:
阶段 0:初始状态(调用函数前)
假设调用函数时参数按顺序压栈(小端存储):
地址 字节值 解释
ESP+0x00 → [Return Address] ; 返回地址(4字节,假设为 0x11223344)
ESP+0x04 → 0xFF ; limit 低字节(0x0000ffff → FF FF 00 00)
ESP+0x05 → 0xFF
ESP+0x06 → 0x00
ESP+0x07 → 0x00
ESP+0x08 → 0x00 ; addr 低字节(0x00270000 → 00 00 27 00)
ESP+0x09 → 0x00
ESP+0x0A → 0x27
ESP+0x0B → 0x00
- 参数布局:
limit
(32位):0x0000ffff
→ 存储为FF FF 00 00
(ESP+4 ~ ESP+7)。addr
(32位):0x00270000
→ 存储为00 00 27 00
(ESP+8 ~ ESP+11)。
阶段 1:执行 MOV AX, [ESP+4]
-
从
ESP+4
读取 2 字节(limit
的低16位):复制下载ESP+4 → 0xFF (低字节) ESP+5 → 0xFF (高字节)
-
AX
寄存器值变为0xFFFF
。 -
栈布局不变(仅读取数据)。
阶段 2:执行 MOV [ESP+6], AX
-
将
AX
(0xFFFF
)写入ESP+6
和ESP+7
的位置:复制下载ESP+6 → 0xFF (低字节) ESP+7 → 0xFF (高字节)
-
覆盖原栈数据:
- 原
limit
的高16位(ESP+6 ~ ESP+7:00 00
)被覆盖为FF FF
。 - 原
addr
的低2字节(ESP+8 ~ ESP+9:00 00
)不受影响(写入的是ESP+6 ~ ESP+7
)。
- 原
-
新栈布局:
地址 字节值 解释
ESP+0x00 → [Return Address]
ESP+0x04 → 0xFF ; limit 低字节(保持原值)
ESP+0x05 → 0xFF
ESP+0x06 → 0xFF ; 新写入的低16位(覆盖原 limit 高16位)
ESP+0x07 → 0xFF
ESP+0x08 → 0x00 ; addr 低字节(保持原值)
ESP+0x09 → 0x00
ESP+0x0A → 0x27
ESP+0x0B → 0x00
阶段 3:执行 LGDT [ESP+6]
- 从
ESP+6
开始读取 6 字节,构造 GDTR 结构:
地址 字节值 对应字段
ESP+6 → 0xFF ; GDTR 低字节 → Limit 低8位
ESP+7 → 0xFF ; GDTR 高字节 → Limit 高8位(Limit = 0xFFFF)
ESP+8 → 0x00 ; Base Address 字节0(低字节)
ESP+9 → 0x00 ; Base Address 字节1
ESP+A → 0x27 ; Base Address 字节2
ESP+B → 0x00 ; Base Address 字节3(高字节)
- GDTR 最终值:
- Limit:
0xFFFF
(有效字节数为0xFFFF + 1 = 64KB
)。 - Base Address:
0x00270000
(小端存储为00 00 27 00
)。
- Limit:
最终栈布局总结
地址 字节值 解释
ESP+0x00 → [Return Address]
ESP+0x04 → FF FF FF FF ; 修改后的 limit(原 limit 低16位 + 新写入的 FF FF)
ESP+0x08 → FF FF 00 00 ; addr 前两字节被覆盖为 FF FF(但实际未影响 addr 的完整值)
ESP+0x0C → 27 00 ... ; addr 后两字节保持原值
关键点
- 小端存储:所有 32 位值(如
0x00270000
)按字节逆序存储。 - 数据覆盖:通过写入
ESP+6 ~ ESP+7
,将limit
的低16位与addr
的32位合并为连续的6字节。 - GDTR 格式:前2字节为
Limit
,后4字节为Base Address
,完全符合LGDT
指令要求。
通过逐步覆盖栈中数据,最终成功构造了 GDTR 需要的 6 字节结构!
关于段描述符
void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)
{if (limit > 0xfffff) {ar |= 0x8000; /* G_bit = 1 */limit /= 0x1000;}sd->limit_low = limit & 0xffff;sd->base_low = base & 0xffff;sd->base_mid = (base >> 16) & 0xff;sd->access_right = ar & 0xff;sd->limit_high = ((limit >> 16) & 0x0f) | ((ar >> 8) & 0xf0);sd->base_high = (base >> 24) & 0xff;return;
}
结构总结
段描述符是一个 8 字节(64 位) 的结构,用于描述内存段的属性、大小和基址。它是 CPU 在保护模式下管理内存段的核心数据结构。以下是段描述符的详细结构:
段描述符的组成
段描述符的 8 字节分为以下几个部分:
字节偏移 | 位数范围 | 字段名称 | 描述 |
---|---|---|---|
0-1 | 16 位 | limit_low | 段上限的低 16 位,表示段的大小(字节数 - 1)。 |
2-3 | 16 位 | base_low | 段基址的低 16 位,表示段的起始地址。 |
4 | 8 位 | base_mid | 段基址的中间 8 位。 |
5 | 8 位 | access_right | 段的访问权限属性,包括是否可读写、是否可执行、段类型等。 |
6 | 4 位 | limit_high | 段上限的高 4 位。 |
6 | 4 位 | flags | 段的扩展属性,包括 G 位(粒度)和 D 位(操作数大小)。 |
7 | 8 位 | base_high | 段基址的高 8 位。 |
字段详细说明
1. 段基址(Base Address)
- 段基址是一个 32 位的地址,表示段的起始地址。
- 它被分为 3 个部分存储:
base_low
:低 16 位。base_mid
:中间 8 位。base_high
:高 8 位。
- 通过这 3 个字段组合,可以完整表示一个 32 位地址。
2. 段上限(Limit)
- 段上限是一个 20 位的值,表示段的大小(字节数 - 1)。
- 它被分为 2 个部分存储:
limit_low
:低 16 位。limit_high
:高 4 位。
- 如果 G 位(粒度位)为 0,则段上限以字节为单位,最大值为 1 MB(
0xFFFFF
)。 - 如果 G 位为 1,则段上限以 4 KB 为单位,最大值为 4 GB(
0xFFFFF * 4KB
)。
3. 访问权限(Access Rights,access_right
)
-
访问权限是一个 8 位的字段,定义了段的类型和访问权限。
-
具体位的含义如下:
7 6 5 4 3 2 1 0 P DPL 1 S TYPE
- P(1 位):段是否存在(Present)。
- 1:段存在。
- 0:段不存在。
- DPL(2 位):描述符特权级(Descriptor Privilege Level)。
- 值为 0-3,0 表示最高权限,3 表示最低权限。
- S(1 位):描述符类型。
- 1:代码段或数据段。
- 0:系统段(如 TSS、LDT)。
- TYPE(4 位):段的具体类型。
- 代码段:是否可执行、可读等。
- 数据段:是否可写、扩展方向等。
- P(1 位):段是否存在(Present)。
4. 扩展属性(Flags,flags
)
-
扩展属性是
limit_high
的高 4 位,定义了段的额外属性:G D 0 A
- G(1 位):粒度(Granularity)。
- 0:段上限以字节为单位。
- 1:段上限以 4 KB 为单位。
- D(1 位):操作数大小(Default Operation Size)。
- 0:16 位模式。
- 1:32 位模式。
- 0(1 位):保留位,必须为 0。
- A(1 位):访问位(Accessed)。
- 0:段未被访问。
- 1:段已被访问(由 CPU 自动设置)。
- G(1 位):粒度(Granularity)。
存储示例
假设:
- 段基址(Base Address)为
0x00270000
。 - 段上限(Limit)为
0xFFFFF
(4 GB,G 位为 1)。 - 访问权限(Access Rights)为
0x9A
(可执行、可读、系统段)。 - 扩展属性(Flags)为
0xC
(G 位为 1,D 位为 1)。
段描述符的 8 字节存储如下:
Byte 0-1: FF FF (limit_low)
Byte 2-3: 00 00 (base_low)
Byte 4: 00 (base_mid)
Byte 5: 9A (access_right)
Byte 6: CF (limit_high + flags: limit_high=F, flags=C)
Byte 7: 27 (base_high)
完整的段描述符为:
[FF FF 00 00 00 9A CF 27]
总结
段描述符是一个 8 字节的结构,包含段的基址、段上限和访问权限等信息。它的设计兼顾了 80286 和 80386 的兼容性,同时通过 G 位和 D 位扩展了段的大小和操作模式。通过 set_segmdesc
函数,可以按照 CPU 的要求将这些信息正确地写入内存。
初始化PIC(harib03d)
_io_out8: ; void io_out8(int port, int data);MOV EDX,[ESP+4] ; 从栈中获取端口号参数MOV AL,[ESP+8] ; 从栈中获取8位数据参数OUT DX,AL ; 向指定端口写入8位数据RET ; 无返回值
//bootpack.h
void init_pic(void);
#define PIC0_ICW1 0x0020
#define PIC0_OCW2 0x0020
#define PIC0_IMR 0x0021
#define PIC0_ICW2 0x0021
#define PIC0_ICW3 0x0021
#define PIC0_ICW4 0x0021
#define PIC1_ICW1 0x00a0
#define PIC1_OCW2 0x00a0
#define PIC1_IMR 0x00a1
#define PIC1_ICW2 0x00a1
#define PIC1_ICW3 0x00a1
#define PIC1_ICW4 0x00a1
//int.c
void init_pic(void)
/* PIC初始化 */
{io_out8(PIC0_IMR, 0xff ); /* 屏蔽主PIC(PIC0)所有中断 */io_out8(PIC1_IMR, 0xff ); /* 屏蔽从PIC(PIC1)所有中断 */io_out8(PIC0_ICW1, 0x11 ); /* 初始化命令字1: 边沿触发,级联模式,需要ICW4 */io_out8(PIC0_ICW2, 0x20 ); /* 初始化命令字2: 主PIC中断向量基址为0x20 */io_out8(PIC0_ICW3, 1 << 2); /* 初始化命令字3: 从PIC连接到主PIC的IRQ2 */io_out8(PIC0_ICW4, 0x01 ); /* 初始化命令字4: 8086模式 */io_out8(PIC1_ICW1, 0x11 ); /* 从PIC初始化命令字1: 同主PIC */io_out8(PIC1_ICW2, 0x28 ); /* 从PIC中断向量基址为0x28 */io_out8(PIC1_ICW3, 2 ); /* 从PIC标识号为2(对应主PIC的IRQ2) */io_out8(PIC1_ICW4, 0x01 ); /* 从PIC初始化命令字4: 同主PIC */io_out8(PIC0_IMR, 0xfb ); /* 允许从PIC中断(IRQ2),其他保持屏蔽 */io_out8(PIC1_IMR, 0xff ); /* 保持从PIC所有中断屏蔽 */return;
}//程序中的PIC0和PIC1,分别指主PIC和从PIC。
//具体的端口号码写在bootpack.h里,
//写入ICW1之后,紧跟着一定要写入ICW2等,所以即使端口号
//相同,也能够很好地区别开来。
-
PIC是“programmable interrupt controller”的缩写,意思是“可编程中断控制器”。
-
-
PIC是将8个中断信号(interrupt request,缩写为IRQ。)集合成一个中断信号的装置。PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知给CPU。
各 IRQ 对应的中断源及其在代码中的体现:
PIC0 (主芯片,端口 0x20-0x21)master PIC 的中断源:
- IRQ0 (0x20): 定时器中断(代码中未显式使用)
- IRQ1 (0x21): 键盘中断 → 对应
idt + 0x21
和asm_inthandler21
- IRQ2 (0x22): 级联 PIC1 的中断(必须保持开启)
- IRQ3 (0x23): COM2 串口(未使用)
- IRQ4 (0x24): COM1 串口(未使用)
- IRQ5 (0x25): LPT2 并口(未使用)
- IRQ6 (0x26): 软盘控制器(未使用)
- IRQ7 (0x27): LPT1 并口 → 对应
idt + 0x27
和asm_inthandler27
PIC1 (从芯片,端口 0xA0-0xA1)slave PIC 的中断源:
- IRQ8 (0x28): CMOS 实时时钟(未使用)
- IRQ9 (0x29): 自由中断(未使用)
- IRQ10 (0x2A): 自由中断(未使用)
- IRQ11 (0x2B): 自由中断(未使用)
- IRQ12 (0x2C): PS/2 鼠标中断 → 对应
idt + 0x2c
和asm_inthandler2c
- IRQ13 (0x2D): FPU 异常(未使用)
- IRQ14 (0x2E): 主 IDE 控制器(未使用)
- IRQ15 (0x2F): 次 IDE 控制器(未使用)
- ibm设置了两个pic,中断信号有15个
- 与CPU直接相连的PIC称为主PIC(master PIC),与主PIC相连的PIC称为从
PIC(slave PIC)。主PIC负责处理第0到第7号中断信号,从PIC负责处理第8到第15
号中断信号。
PIC的寄存器
它们都是8位寄存器。
- IMR是“interrupt mask register”的缩写,意思是“中断屏蔽寄存器”。1,就屏蔽,该位的IRQ
- ICW是“initial control word”的缩写,意为“初始化控制数据”。
- ICW有4个,分别编号为1~4,共有4个字节的数据。
- ICW1和ICW4与PIC主板配线方式、中断信号的电气特性等有关
- ICW3是有关主—从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,是用8位来设定的。但我们所用的电脑并不是这样的,所以就设定成00000100。对从PIC来说,该从PIC与主PIC的第几号相连,用3位来设定。
- 这上面的icw都硬件决定,软件方面不能更改
- 不同的操作系统可以进行独特设定的就只有ICW2
- 这个ICW2,决定了IRQ以哪一号中断通知CPU
- ICW2的作用:
- 决定IRQ以哪个中断号通知CPU
- 主PIC默认设置为0x20(IRQ0-7对应INT 20-27)
- 从PIC默认设置为0x28(IRQ8-15对应INT 28-2f)
- 为什么不能使用0x00-0x1f:
- 这些中断号被CPU保留用于内部异常处理(如除零错误、页错误等)
- 如果IRQ使用这些号码,CPU无法区分是硬件中断还是系统异常
- 这是x86架构的设计规范
- 中断触发机制:
- PIC通过发送0xcd(INT指令)加中断号来触发CPU中断
- 例如:IRQ0会触发PIC发送0xcd 0x20,CPU执行INT 0x20
注意:0xcd 0x20在CPU看来,从内存读进来的程序是完全一样的 ,所以CPU就把送过来的“0xcd 0x??”作为机器语言执行。这恰恰就是把数据当作程序来执行的情况。这里的0xcd就是调用BIOS时使用的那个INT指令。我们在程序里写的“INT 0x10”,最后就被编译成了“0xcd0x10”。所以,CPU上了PIC的当,按照PIC所希望的中断号执行了INT指令。
实模式和保护模式
首先先解释一下实模式和保护模式,然后解释为什么要编写中断处理程序而不是用bios中断
实模式(Real Mode)
- 基本特点:
- 16位模式,兼容最早的8086处理器
- 直接物理内存访问,最大寻址空间1MB(20位地址线)
- 使用段寄存器:偏移量的方式计算物理地址(物理地址=段寄存器×16+偏移量)
- 典型应用:
- 计算机启动时CPU自动进入实模式
- BIOS运行环境
- DOS操作系统的工作模式
- 局限性:
- 无内存保护机制
- 无特权级别划分
- 无法支持多任务和现代操作系统需求
保护模式(Protected Mode)
- 基本特点:
- 32/64位模式(在您的代码中使用了32位保护模式)
- 支持4GB内存空间(32位)或更大(64位)
- 引入分段和分页内存管理机制
- 支持4个特权级(0-3级),0级最高(内核),3级最低(用户程序)
- 关键机制:
- 全局描述符表(GDT)和局部描述符表(LDT)
- 中断描述符表(IDT)
- 内存分页机制(可选)
- 硬件级任务切换支持
- 优势:
- 内存保护:防止程序越界访问
- 特权隔离:内核和用户程序分离
- 支持虚拟内存
- 支持多任务操作系统
注意:从实模式切换到保护模式是操作系统启动过程中的关键步骤,通过load_gdtr
和load_idtr
等函数完成了这一转换。
主要就是这个权限的分离,通过上面所说的关键机制实现
为什么不能用bios中断:
- 运行模式不同:
- BIOS中断运行在16位实模式下
- 您的操作系统运行在32位保护模式下
- 保护模式下无法直接调用实模式的BIOS中断
- 内存管理差异:
- 保护模式下使用分段和分页内存管理
- BIOS中断处理程序假设CPU处于实模式
- 直接调用会导致内存访问错误
- 中断上下文不同:
- BIOS中断处理程序假设特定的寄存器状态和栈布局
- 保护模式下的中断上下文与之不兼容
- 功能需求差异:
- BIOS提供的是通用硬件抽象
- 操作系统需要特定的、可定制的硬件控制
- 例如:键盘缓冲区管理、鼠标坐标处理等
- 性能考虑:
- BIOS中断处理较慢
- 操作系统需要高效的中断处理
- 自定义处理程序可以优化性能
16位到32位切换的重要步骤
在dsctbl.c
中初始化GDT:
1. 准备GDT(全局描述符表)
dsctbl.cvoid init_gdtidt(void)
{// 设置GDT描述符set_segmdesc(gdt + 1, 0xffffffff, 0x00000000, AR_DATA32_RW); // 数据段set_segmdesc(gdt + 2, 0x0007ffff, 0x00280000, AR_CODE32_ER); // 代码段// ...
}
2. 加载GDTR寄存器
通过naskfunc.nas
中的汇编指令加载GDT:
_load_gdtr:MOV AX,[ESP+4] ; 加载GDT界限值MOV [ESP+6],AXLGDT [ESP+6] ; 加载GDTR寄存器RET
3. 设置CR0寄存器进入保护模式
通常在启动代码(如asmhead.nas
)中包含以下关键指令:
MOV EAX,CR0AND EAX,0x7fffffff ; 禁用分页OR EAX,0x00000001 ; 设置保护模式位MOV CR0,EAX ; 正式进入保护模式
4. 初始化IDT和PIC
在int.c
中初始化可编程中断控制器:
int.cvoid init_pic(void)
{io_out8(PIC0_ICW2, 0x20); // 主PIC中断向量基址io_out8(PIC1_ICW2, 0x28); // 从PIC中断向量基址// ...
}
5. 远跳转刷新流水线
进入保护模式后立即行远跳转:
JMP DWORD 2*8:0x0000001b ; 跳转到保护模式代码段
注意:
- 顺序不能错:必须先设置GDT,再加载GDTR,最后修改CR0
- 特权级设置:代码段描述符的AR_CODE32_ER(0x409a)包含特权级信息
- 地址空间:保护模式下使用32位地址线,可访问4GB内存
- 中断处理:必须重新设置IDT,不能继续使用实模式的中断向量表
中断处理程序制作(harib03e)
鼠标是IRQ12,键盘是IRQ1,所以我们编写了用于INT 0x2c和INT 0x21的中断处理程序(handler),即中断发生时所要调用的程序。
void inthandler21(int *esp)
/* PS/2键盘中断处理程序 */
{// 获取BOOTINFO结构体指针,该结构体包含系统启动信息struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;// 在屏幕顶部绘制黑色矩形区域(用于显示中断信息)boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);// 在黑色区域显示白色文字,说明这是键盘中断(IRQ1)putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 21 (IRQ-1) : PS/2 keyboard");// 无限循环,暂时不做其他处理for (;;) {io_hlt(); // 执行HLT指令使CPU进入休眠状态}
}void inthandler2c(int *esp)
/* PS/2鼠标中断处理程序 */
{// 获取BOOTINFO结构体指针struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;// 在屏幕顶部绘制黑色矩形区域boxfill8(binfo->vram, binfo->scrnx, COL8_000000, 0, 0, 32 * 8 - 1, 15);// 显示鼠标中断信息(IRQ12)putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, "INT 2C (IRQ-12) : PS/2 mouse");// 无限循环for (;;) {io_hlt(); // CPU休眠}
}void inthandler27(int *esp)
/* PIC0的不完全中断处理程序 */
/* 在Athlon64X2等多核处理器中,由于PIC初始化时的时序问题,这个中断可能会被触发一次 */
/* 这个中断处理函数不需要实际处理任何设备中断 */
/* 为什么不需要处理?因为这个中断是由PIC芯片本身的电气特性引起的伪中断,所以不需要进行任何实质性的处理 */
{// 向PIC发送EOI(End Of Interrupt)命令,通知中断处理完成// 0x67参数表示: // - 0x60: EOI命令的基础值// - 0x07: 指定IRQ7(这是PIC的级联中断线)io_out8(PIC0_OCW2, 0x67); return;
}
_asm_inthandler21:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler21POP EAXPOPADPOP DSPOP ESIRETD_asm_inthandler27:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler27POP EAXPOPADPOP DSPOP ESIRETD_asm_inthandler2c:PUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AX
;关于在DS和ES中放入SS值的部分,因为C语言自以为是地认为“DS也好,ES也好,
;SS也好,它们都是指同一个段”,所以如果不按照它的想法设定的话,函数
;inthandler21就不能顺利执行。CALL _inthandler2cPOP EAXPOPADPOP DSPOP ESIRETD
IRETD和代码结构解释:
在您提供的代码片段中,IRETD
是一个 x86 汇编指令,通常用于从中断服务例程返回到调用程序。以下是对其的详细解释:
然后要往中断向量表里写入中断程序地址(idt的设定)
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
/*
第一个参数:写明程序所对应的中断地址
第二个参数:写明程序的地址
第三个参数:代码段选择子(selector),这里2表示GDT中的第2个描述符,*8是因为每个描述符占8字节GDT中的每个描述符占8个字节,所以索引号需要乘以8来得到正确的偏移量“2 * 8” 也可以写成 “2<<3”, 当然,写成16也可以。
第四个参数:属性值(0x008e),表示32位中断门,AR_INTGATE32将IDT的属性,设定为0x008e。
它表示这是用于中断处理的有效设定。
*/
选择子计算规则:
-
索引号:
2
表示使用GDT中的第3个描述符(从0开始计数)- gdt[0]:空描述符(保留)
- gdt[1]:数据段描述符
- gdt[2]:代码段描述符
-
乘法原理:
8
是因为每个描述符占8字节- 描述符0的地址 = GDT基地址 + 0*8
- 描述符1的地址 = GDT基地址 + 1*8
- 描述符2的地址 = GDT基地址 + 2*8
-
选择子结构:
| 15..3 | 2 | 1..0 |索引号 TI RPL
- 这里
2*8
=0x0010
(二进制0000 0000 0001 0000
) - TI=0(使用GDT),RPL=00(请求特权级0,内核模式)
- 这里
对应到实际内存:
当CPU通过这个选择子访问代码段时,会:
- 用
0x0010 >> 3
= 2 得到GDT索引号 - 通过
gdtr
寄存器找到GDT基地址 - 基地址 + 2*8 找到代码段描述符
- 用描述符中的基地址和限制值进行内存访问验证
IRETD
的作用
- 全称:
IRETD
是 “Interrupt Return Doubleword” 的缩写。 - 功能:它从堆栈中弹出返回地址和处理器状态(EFLAGS 寄存器),并将控制权交还给中断发生前的代码。
- 适用环境:
IRETD
专门用于保护模式(Protected Mode)下的 32 位环境。如果是在实模式(Real Mode)下,通常使用IRET
。
工作原理
当中断发生时,CPU 会自动将以下内容压入堆栈:
- 返回地址(包括 CS 段寄存器和 EIP 指令指针)。
- EFLAGS 寄存器的值。
- 如果是任务切换或特权级改变,还会保存额外的段寄存器(如 SS 和 ESP)。
IRETD
的作用是:
- 从堆栈中依次弹出 EIP、CS 和 EFLAGS 的值。
- 恢复中断发生前的 CPU 状态。
- 将程序控制权返回到中断发生前的代码。
使用场景
- 在操作系统内核中,用于处理硬件或软件中断。
- 在自定义的中断服务例程(ISR)中,用于结束中断处理。
注意事项
- 如果堆栈中的数据不正确(例如被破坏或未正确保存),
IRETD
会导致程序崩溃或行为异常。 - 在 64 位模式下,
IRETD
被替换为IRETQ
,用于处理 64 位地址。
示例代码
以下是一个简单的中断服务例程的伪代码,展示了 IRETD
的使用:
section .text
global isr_handlerisr_handler:; 保存通用寄存器pusha; 处理中断逻辑; ...; 恢复通用寄存器popa; 从中断返回IRETD
总结
IRETD
是一个关键的汇编指令,用于从中断返回并恢复 CPU 的状态。它在操作系统开发和底层硬件编程中非常重要。如果您正在编写中断处理程序,请确保堆栈的内容正确无误,以避免潜在问题。
pushad
PUSH
指令
-
作用:将一个寄存器或立即数的值压入堆栈。
-
堆栈变化:堆栈指针(
ESP
)会减少 4(在 32 位模式下),然后将值存储到新的堆栈顶。 -
代码中的用途:这两行代码将段寄存器
ES
和DS
的值保存到堆栈中,以便稍后恢复。这是为了保护中断处理程序修改这些寄存器时不会破坏原有的值。PUSH ES
PUSH DS
PUSHAD
指令
- 作用:将所有通用寄存器的值(
EAX
、ECX
、EDX
、EBX
、ESP
、EBP
、ESI
、EDI
)按顺序压入堆栈。 - 堆栈变化:
ESP
会减少 32(每个寄存器 4 字节,共 8 个寄存器)。 - 代码中的用途:这行代码保存了所有通用寄存器的值,确保中断处理程序可以安全地使用这些寄存器,而不会影响中断返回后的程序状态。
完整的保存和恢复过程
- 保存状态:
PUSH ES
和PUSH DS
保存段寄存器。PUSHAD
保存所有通用寄存器。
- 恢复状态:
POPAD
恢复所有通用寄存器。POP DS
和POP ES
恢复段寄存器。
EFLAG寄存器
- 状态标志位:
- CF (Carry Flag, 位0):进位标志。用于表示无符号数运算的进位或借位
- PF (Parity Flag, 位2):奇偶标志。表示结果中1的个数是否为偶数
- AF (Auxiliary Carry Flag, 位4):辅助进位标志。用于BCD运算
- ZF (Zero Flag, 位6):零标志。表示运算结果是否为零
- SF (Sign Flag, 位7):符号标志。表示运算结果的符号(0为正,1为负)
- OF (Overflow Flag, 位11):溢出标志。表示有符号数运算是否溢出
- 控制标志位:
- DF (Direction Flag, 位10):方向标志。控制字符串操作的方向(0=递增,1=递减)
- 系统标志位:
- TF (Trap Flag, 位8):陷阱标志。用于单步调试
- IF (Interrupt Flag, 位9):中断允许标志。控制是否响应可屏蔽中断
- IOPL (I/O Privilege Level, 位12-13):I/O特权级。决定当前任务的I/O权限
- NT (Nested Task, 位14):嵌套任务标志。表示当前任务是否嵌套在另一个任务中
- RF (Resume Flag, 位16):恢复标志。用于调试异常处理
- VM (Virtual-8086 Mode, 位17):虚拟8086模式标志
- AC (Alignment Check, 位18):对齐检查标志
- VIF (Virtual Interrupt Flag, 位19):虚拟中断标志
- VIP (Virtual Interrupt Pending, 位20):虚拟中断挂起标志
- ID (ID Flag, 位21):ID标志。表示CPU是否支持CPUID指令
修改pic的IMR
io_out8(PIC0_IMR, 0xf9);
io_out8(PIC1_IMR, 0xef);
以便接受来自键盘和鼠标的中断
相关文章:
自制操作系统day6(GDTR、段描述符、PIC、实模式和保护模式、16位到32位切换、中断处理程序、idt的设定、EFLAG寄存器)(ai辅助整理)
day6 分割源文件(harib03a) 优点 按照处理内容进行分类,如果分得好的话,将来进行修改时,容易找到地方。如果Makefile写得好,只需要编译修改过的文件,就可以提高make的速度。单个源文件都不长。…...
大模型评测与可解释性
随着大模型在各个领域展现出惊人的能力,我们对其性能的评估和对其决策过程的理解变得尤为重要。一个模型即使在基准测试中表现出色,也可能在实际应用中遇到意想不到的问题。同时,由于大模型的复杂性,它们常常被视为“黑箱”,这给其在关键领域的应用带来了挑战。 本章将深…...
【TTS回顾】StyleTTS 深度剖析:TTS+风格迁移
写在前面 这篇博客我们回顾一下StyleTTS,当时的背景是,文本转语音(TTS)技术,早已不再满足于仅仅将文字转化为可听的语音。行业需要的是“真人TTS”,AI 不仅能“说得清楚”,更能“说得生动”、“说得有感情”,甚至能模仿特定人物的说话风格。富有表现力的语音合成,即能…...
GStreamer (四)交叉编译
交叉编译 下载链接库交叉编译1、下载Gstreamer (方式二 ),进入到编译目录2、在gst-build目录下创建交叉编译配置文件cross_file.txt3、修改meson_options.txt中libmount选项为false,否则编译前需要先编译libmount。4、在gst-build…...
电路设计基础
只有当电容两端的电压等于0伏的时候,就是这一点的电压和这一点电压之间没有压差的时候,我门才可以把电容当成是一根导线,如果当我电容比如说它己经充到有一个1伏的电压了,这个时候我们是不可以把电容当成是导线的,所以…...
C语言——函数递归与迭代
(1)递归的例子: 顺序打印一个整数,打印整数的每一位。 例如: input:1234 output:1 2 3 4 input:520 output:5 2 0 我们可能会想到用这种方法:(但是运行之后,我们发现结果是事…...
详解 C# 中基于发布-订阅模式的 Messenger 消息传递机制:Messenger.Default.Send/Register
🧑 博主简介:CSDN博客专家、CSDN平台优质创作者,高级开发工程师,数学专业,10年以上C/C, C#, Java等多种编程语言开发经验,拥有高级工程师证书;擅长C/C、C#等开发语言,熟悉Java常用开…...
8 种快速易用的Python Matplotlib数据可视化方法
你是否曾经面对一堆复杂的数据,却不知道如何让它们变得直观易懂?别慌,Python 的 Matplotlib 库是你数据可视化的最佳伙伴!它简单易用、功能强大,能将枯燥的数字变成引人入胜的图表。无论是学生、数据分析师还是程序员&…...
嵌入式开发学习日志(linux系统编程--文件读写函数(2))Day25
一、linux操作命令 【wc】:指定字符统计; 【file 文件名】:可以查看文件的类型; 二、写入函数【fwrite】————可写入二进制文件 形式: size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE…...
离线服务器Python环境配置指南
离线服务器Python环境配置指南:避坑与实战 0. 场景分析:当服务器与世隔绝时 典型困境: 无法访问国际网络(如PyPI、Conda官方源)服务器处于内网隔离环境安全策略限制在线安装 解决方案矩阵: 方法适用场…...
Java线程池调优与实践经验
在Java面试中,线程池调优是一个常见且重要的考察点,尤其是当涉及Spring生态时,ThreadPoolTaskExecutor的使用经验通常会被深入追问。以下是针对该问题的结构化回答,结合原理、实践和调优经验: 1. 线程池调优的核心参数…...
Python 包管理工具核心指令uvx解析
uvx 是 Python 包管理工具 uv 的重要组成部分,主要用于在隔离环境中快速运行 Python 命令行工具或脚本,无需永久安装工具包。以下是其核心功能和使用场景的详细解析: 一、uvx 的定位与核心功能 工具执行器的角色 uvx 是 uv tool run 的别名&a…...
力扣-三数之和
1.题目描述 2.题目链接 LCR 007. 三数之和 - 力扣(LeetCode) 3.题目代码 import java.util.*; class Solution {public List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);int tempnums.length-1;Set<List<Integer>…...
【AI模型学习】ESM2
文章目录 1. 版本2. 开始2.1 安装2.2 使用预训练模型2.2.1 代码2.2.2 讲解 2.2 结构预测 3. 任务类型总结1. 蛋白质结构预测(ESMfold)2. 特征嵌入提取(esm-extract)3. 零镜头变体预测(ESM-1v/ESM-2)4. 逆向…...
c++11特性——可变参数模板及emplace系列接口
文章目录 可变参数模板基本语法和使用sizeof...运算符 从语法角度理解可变参数模板包扩展通过编译时递归解析参数包直接对解析行为展开 emplace系列接口举例讲解emplace_back的实现 可变参数模板 可变参数模板是c11新特性中极其重要的一节。前文我们提到过,c11中对…...
深入理解 Pre-LayerNorm :让 Transformer 训练更稳
摘要 在超深 Transformer 与大语言模型(LLM)时代,归一化策略直接决定了模型能否稳定收敛、推理性能能否最大化。把归一化层从 “残差之后” 挪到 “子层之前”(Pre-LayerNorm,Pre-LN),再将传统…...
vue3:十三、分类管理-表格--分页功能
一、实现效果 实现分页功能,并且可对分页功能和搜索框功能能动态显示 1、显示分页 2、分页功能和搜索栏隐藏 二、基础搭建 1、官网参考 Pagination 分页 | Element Plus 使用分页的附加功能 2、表格中底部写入分页 (1)样式class 在全局js中写入顶部外边距样式margin-t…...
工商总局可视化模版-Echarts的纯HTML源码
概述 基于ECharts的工商总局数据可视化HTML模版,帮助开发者快速搭建专业级工商广告数据展示平台。这款模版设计规范,功能完善,适合各类工商监管场景使用。 主要内容 本套模版采用现代化设计风格,主要包含以下核心功能模块&…...
8.2 线性变换的矩阵
一、线性变换的矩阵 本节将对每个线性变换 T T T 都指定一个矩阵 A A A. 对于一般的列向量,输入 v \boldsymbol v v 在空间 V R n \pmb{\textrm V}\pmb{\textrm R}^n VRn 中,输出 T ( v ) T(\boldsymbol v) T(v) 在空间 W R m \textrm{\pmb W}\…...
工业路由器WiFi6+5G的作用与使用指南,和普通路由器对比
工业路由器的技术优势 在现代工业环境中,网络连接的可靠性与效率直接影响生产效率和数据处理能力。WiFi 6(即802.11ax)和5G技术的结合,为工业路由器注入了强大的性能,使其成为智能制造、物联网和边缘计算的理想选择。…...
Nginx核心服务
一.正向代理 正向代理(Forward Proxy)是一种位于客户端和原始服务器之间的代理服务器,其主要作用是将客户端的请求转发给目标服务器,并将响应返回给客户端 Nginx 的 正向代理 充当客户端的“中间人”,代…...
条件随机场 (CRF) 原理及其在语义分割中的应用
条件随机场 (CRF) 原理及其在语义分割中的应用 一、条件随机场的原理 条件随机场 (Conditional Random Fields, CRF) 是一种判别式概率无向图模型。它用于在给定观测序列 (如图像中的像素) 的条件下,对另一组序列 (如像素的语义标签) 进行建模和预测。 与生成式模…...
2025年Y2大型游乐设施操作证备考练习题
Y2 大型游乐设施操作证备考练习题 单选题 1、《游乐设施安全技术监察规程(试行)》规定:对操作控制人员无法观察到游乐设施的运行情况,在可能发生危险的地方应( ),或者采取其他必要的安全措施。…...
L53.【LeetCode题解】二分法习题集2
目录 1.162. 寻找峰值 分析 代码 提交结果 2.153. 寻找旋转排序数组中的最小值 分析 图像的增长趋势可以分这样几类 逐个击破 比较明显的 先增后减再增 用二段性给出left和right的更新算法 代码 提交结果 其他做法 提交结果 3.LCR 173. 点名(同剑指offer 53:0~…...
趣味编程:抽象图(椭圆组成)
概述:本篇博客主要讲解由椭圆图案组合而成的抽象图形。 1.效果展示 该程序的实际运行是一个动态的效果,因此实际运行相较于博客图片更加灵动。 2.源码展示 // 程序名称:椭圆组合而成的抽象图案// #include <graphics.h> #include <…...
RPA浪潮来袭,职业竞争的新风口已至?
1. RPA职业定义与范畴 1.1 RPA核心概念 RPA(Robotic Process Automation,机器人流程自动化)是一种通过软件机器人模拟人类操作,实现重复性、规律性任务自动化的技术。它能够自动执行诸如数据输入、文件处理、系统操作等任务&…...
【Elasticsearch】字段别名
在 Elasticsearch 中,字段别名(Field Alias)主要用于查询和检索阶段,而不是直接用于写入数据。 为什么不能通过字段别名写入数据? 字段别名本质上是一个映射关系,它将别名指向实际的字段。Elasticsearch …...
【Linux笔记】防火墙firewall与相关实验(iptables、firewall-cmd、firewalld)
一、概念 1、防火墙firewall Linux 防火墙用于控制进出系统的网络流量,保护系统免受未授权访问。常见的防火墙工具包括 iptables、nftables、UFW 和 firewalld。 防火墙类型 包过滤防火墙:基于网络层(IP、端口、协议)过滤流量&a…...
人工智能解析:技术革命下的认知重构
当生成式AI能够自主创作内容、设计方案甚至编写代码时,我们面对的不仅是工具革新,更是一场关于智能本质的认知革命。人工智能解析的核心,在于理解技术如何重塑人类解决问题和创造价值的底层逻辑——这种思维方式的转变,正成为数字…...
Neo4j实现向量检索
最近因为Dify、RagFlow这样的智能体的镜像拉取的速度实在太麻烦,一狠心想实现自己的最简单的RAG。 因为之前图数据库使用到了neo4j,查阅资料才发现Neo4j从5.11版本开始支持向量索引,提供一个真实可用的单元测试案例。 Neo4j建向量索引表…...
SpringBoot外部化配置
外部化配置(Externalized Configuration)是指将应用的配置从代码中剥离出来,放在外部文件或环境中进行管理的一种机制。 通俗地说,就是你不需要在代码里写死配置信息(比如数据库账号、端口号、日志级别等)…...
Gut(IF: 23.1)|深度多组学破局肝癌免疫联合治疗耐药的空间微环境图谱
肝细胞癌(HCC)是癌症相关死亡的主要原因之一,晚期患者预后极差。近年来,免疫检查点抑制剂(ICI)联合治疗(如抗CTLA-4的tremelimumab和抗PD-L1的durvalumab)已成为晚期HCC的一线治疗方…...
2025年保姆级教程:Powershell命令补全、主题美化、文件夹美化及Git扩展
文章目录 1. 美化 Powershell 缘起2. 安装 oh-my-posh 和 posh-git3. 安装文件夹美化主题【可选】 1. 美化 Powershell 缘起 背景:用了 N 年的 Windows 系统突然觉得命令行实在太难用了,没有补全功能、界面也不美观。所以,我决定改变它。但是…...
LeetCode-链表-合并两个有序链表
LeetCode-链表-合并两个有序链表 ✏️ 关于专栏:专栏用于记录 prepare for the coding test。 文章目录 LeetCode-链表-合并两个有序链表📝 合并两个有序链表🎯题目描述🔍 输入输出示例🧩题目提示🧪AC递归&…...
SpringBoot3+Vue3(2)-前端基本页面配置-登录界面编写-Axios请求封装-后端跨越请求错误
前端: 清理文件 main.js 刷新后页面上什么都没有了 App.vue就留这 1.基本页面配置 新建Vue组件 单页面,考路由才操作。 1.前端根目录下安装路由 2.创建路由文件夹 main.js中添加路由配置 App.vue 添加上路由 welcomeView.vue 浏览器刷新&…...
Android Framework学习八:SystemServer及startService原理
文章目录 SystemServer、SystemServiceManger、SystemService、serviceManager的关系SystemServer进程的执行包含的ServiceSystemServer启动服务的流程startService Framework学习系列文章 SystemServer、SystemServiceManger、SystemService、serviceManager的关系 管理机制&a…...
远程访问家里的路由器:异地访问内网设备或指定端口网址
在一些情况下,我们可能需要远程访问家里的路由器,以便进行设置调整或查看网络状态等,我们看看怎么操作? 1.开启远程访问 在路由本地电脑或手机,登录浏览器访问路由管理后台,并设置开启WEB远程访问。 2.内…...
大语言模型 17 - MCP Model Context Protocol 介绍对比分析 基本环境配置
MCP 基本介绍 官方地址: https://modelcontextprotocol.io/introduction “MCP 是一种开放协议,旨在标准化应用程序向大型语言模型(LLM)提供上下文的方式。可以把 MCP 想象成 AI 应用程序的 USB-C 接口。就像 USB-C 提供了一种…...
python生成requirements.txt文件
方法一:只生成项目所用到的python包(常用) 首先安装pipreqs pip install pipreqs 然后进入到你所在的项目根目录,运行以下命令: pipreqs ./ --encodingutf-8 方法二:把本地所有安装包写入文件 pip freeze > requirements.txt …...
如何在PyCharm2025中设置conda的多个Python版本
前言 体验的最新版本的PyCharm(Community)2025.1.1,发现和以前的版本有所不同。特别是使用Anaconda中的多个版本的Python的时候。 关于基于Anaconda中多个Python版本的使用,以及对应的Pycharm(2023版)的使用,可以参考…...
StepX-Edit:一个通用图像编辑框架——论文阅读笔记
一. 前言 代码:https://github.com/stepfun-ai/Step1X-Edit 论文:https://arxiv.org/abs/2504.17761 近年来,图像编辑技术发展迅速,GPT- 4o、Gemini2 Flash等前沿多模态模型的推出,展现了图像编辑能力的巨大潜力。 这…...
vue原生table表格实现动态添加列,一行添加完换行继续添加。el-select输入框背景颜色根据所选内容不同而改变
效果如下 动态添加列 代码如下 <template><div class"table-container"><button click"addColumn">添加列</button><div class"scroll-container"><div class"table-grid"><div v-for"(r…...
maven之pom.xml
MAVEN 1、基础配置2、项目信息3、依赖管理4、构建配置5、继承与聚合6、仓库与SCM7、其他高级配置 Maven的pom.xml文件是项目的核心配置文件,用于定义项目结构、依赖关系和构建过程 https://www.runoob.com/maven/maven-pom.html 1、基础配置 **<…...
深度学习Y8周:yolov8.yaml文件解读
🍨 本文为🔗365天深度学习训练营中的学习记录博客🍖 原作者:K同学啊 本周任务:根据yolov8n、yolov8s模型的结构输出,手写出yolov8l的模型输出、 文件位置:./ultralytics/cfg/models/v8/yolov8.…...
充电桩APP的数据分析:如何用大数据优化运营?
随着新能源汽车的普及,充电桩作为基础设施的核心环节,其运营效率直接影响用户体验和行业可持续发展。充电桩APP积累了海量用户行为、充电记录、设备状态等数据,如何利用这些数据优化运营成为关键课题。大数据分析能够帮助运营商精准定位问题、…...
shell脚本之函数详细解释及运用
什么是函数 通俗地讲,所谓函数就是将一组功能相对独立的代码集中起来,形成一个代码块,这个代码可 以完成某个具体的功能。从上面的定义可以看出,Shell中的函数的概念与其他语言的函数的 概念并没有太大的区别。从本质上讲&#…...
校平机的原理、应用及发展趋势
一、校平机的定义与作用 校平机(Leveling Machine)是一种用于矫正金属板材、带材或卷材表面平整度的工业设备。其核心功能是通过机械作用消除材料内部残余应力,修正材料在加工、运输或存储过程中产生的弯曲、波浪形、翘曲等缺陷,…...
NFM算法解析:如何用神经网络增强因子分解机的特征交互能力?
在推荐系统和广告点击率预测等场景中,特征交叉(Feature Interaction)是提升模型效果的关键。传统的因子分解机(FM)通过二阶特征交互取得了显著效果,但其线性建模方式和有限阶数限制了模型的表达能力。今天&…...
Python人工智能算法 模拟退火算法:原理、实现与应用
模拟退火算法:从物理启发到全局优化的深度解析 一、算法起源与物理隐喻 模拟退火算法(Simulated Annealing, SA)起源于20世纪50年代的固体退火理论,其核心思想可追溯至Metropolis等人提出的蒙特卡罗模拟方法。1983年,…...
服务器网络配置 netplan一个网口配置两个ip(双ip、辅助ip、别名IP别名)
文章目录 问答 问 # This is the network config written by subiquity network:ethernets:enp125s0f0:dhcp4: noaddresses: [192.168.90.180/24]gateway4: 192.168.90.1nameservers:addresses:- 172.0.0.207- 172.0.0.208enp125s0f1:dhcp4: trueenp125s0f2:dhcp4: trueenp125…...