地址 0x1C: EFLAGS
| | 3 | 压入 EIP
| CPU 硬件 | 0x18 | 0x14 | 地址 0x14: EIP
地址 0x18: CS
地址 0x1C: EFLAGS
| | 4 | 压入错误码(若异常有) | CPU 硬件 | 0x14 | 0x10 | 地址 0x10: 错误码
地址 0x14: EIP
地址 0x18: CS
地址 0x1C: EFLAGS
|
| 步骤 | 操作内容 | 操作主体 | 操作前 ESP
| 操作后 ESP
| 栈中剩余存储情况(从栈顶到栈底) | | — | — | — | — | — | — | | 1 | 弹出错误码(若有) | 程序代码(手动处理) | 0x10 | 0x14 | 地址 0x14: EIP
地址 0x18: CS
地址 0x1C: EFLAGS
| | 2 | 弹出 EIP
| CPU 硬件(执行 iret
指令) | 0x14 | 0x18 | 地址 0x18: CS
地址 0x1C: EFLAGS
| | 3 | 弹出 CS
| CPU 硬件(执行 iret
指令) | 0x18 | 0x1C | 地址 0x1C: EFLAGS
| | 4 | 弹出 EFLAGS
| CPU 硬件(执行 iret
指令) | 0x1C | 0x20 | 无 |
EFLAGS
、CS
、EIP
、错误码(如果该异常产生错误码)的顺序将这些值压入栈中,以保存被中断程序的执行现场。add esp, 4
指令跳过错误码,或者使用 pop
指令将其弹出)。iret
指令,iret
指令会按照 EIP
、CS
、EFLAGS
的顺序从栈中弹出相应的值,从而恢复被中断程序的执行现场,使程序能够从中断处继续执行。以下是 x86 架构中常见异常的详细信息表格,涵盖异常编号、名称、是否有错误码、进入中断和退出中断时的入栈出栈顺序等内容:
异常编号 | 异常名称 | 是否有错误码 | 进入中断入栈顺序(CPU 硬件行为) | 退出中断出栈顺序(程序代码手动处理 + CPU 硬件 iret 指令) | 简要说明 |
---|---|---|---|---|---|
0 | 除法错误(Divide Error) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行除法指令(DIV 或 IDIV )时,除数为 0 或商无法表示时触发。 |
1 | 单步中断(Debug Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 用于调试,当 EFLAGS 寄存器中的单步标志(TF)置 1 时,每条指令执行后触发。 |
2 | 非屏蔽中断(NMI Interrupt) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 由硬件触发的非屏蔽中断,通常用于处理紧急情况,如硬件故障。 |
3 | 断点中断(Breakpoint Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行 INT 3 指令时触发,用于调试。 |
4 | 溢出中断(Overflow Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行 INTO 指令且 EFLAGS 寄存器中的溢出标志(OF)置 1 时触发。 |
5 | 边界检查异常(BOUND Range Exceeded Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行 BOUND 指令时,操作数超出指定范围触发。 |
6 | 无效操作码异常(Invalid Opcode Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | CPU 遇到无法识别的操作码时触发。 |
7 | 设备不可用异常(Device Not Available Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行浮点运算指令,但协处理器不可用时触发。 |
8 | 双重故障异常(Double Fault Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 尝试处理一个异常时,又发生另一个异常,且无法正确处理时触发。 |
9 | 协处理器段超越异常(Coprocessor Segment Overrun) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 早期 x86 架构中,浮点运算跨越段边界时触发,现代架构已较少使用。 |
10 | 无效 TSS 异常(Invalid TSS Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 任务切换时,TSS(任务状态段)无效或不可访问时触发。 |
11 | 段不存在异常(Segment Not Present Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 访问的段描述符的 P (存在位)为 0 时触发。 |
12 | 栈段异常(Stack-Segment Fault Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 访问栈段时发生错误,如栈溢出或栈段不存在时触发。 |
13 | 一般保护异常(General Protection Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 违反系统保护规则时触发,如访问权限不足等。 |
14 | 页错误异常(Page Fault Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 访问的页面不在物理内存中,触发页错误,需要进行页面置换等操作。 |
16 | 浮点错误异常(x87 FPU Floating-Point Error) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 执行 x87 浮点运算指令时发生错误。 |
17 | 对齐检查异常(Alignment Check Exception) | 是 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP 4. 压入错误码 | 1. 手动弹出错误码 2. iret 弹出 EIP 3. iret 弹出 CS 4. iret 弹出 EFLAGS | 访问未对齐的内存地址时触发,需要启用对齐检查标志。 |
18 | 机器检查异常(Machine Check Exception) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 硬件检测到内部错误,如总线错误、缓存错误等。 |
19 - 31 | 保留异常 | - | - | - | 为未来的 CPU 扩展保留。 |
32 - 255 | 可屏蔽中断(Interrupts) | 通常无(取决于中断处理程序设计) | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 由外部设备通过中断控制器发送的中断请求。 |
这个表格总结了 x86 架构中常见异常的关键信息,包括异常编号、名称、是否有错误码以及进入和退出中断时的栈操作顺序,同时对每个异常的触发条件做了简要说明。
在 x86 架构中,IRQ(Interrupt Request,中断请求)是外部设备向 CPU 发送的信号,用于请求 CPU 处理特定事件。以下是常见的 IRQ 列表及相关信息整理:
IRQ 编号 | 名称 | 是否有错误码 | 压栈信息 | 出栈恢复信息 | 触发原因及用途 |
---|---|---|---|---|---|
0 | 系统定时器(System Timer) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 由系统定时器芯片(如 PIT)周期性触发,用于实现系统的时钟滴答、进程调度等功能。 |
1 | 键盘控制器(Keyboard Controller) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 当用户按下或释放键盘上的按键时,键盘控制器会向 CPU 发送此中断,以便操作系统处理键盘输入。 |
2 | 级联中断(Cascade) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 用于连接主中断控制器(PIC)和从中断控制器,使得系统可以处理更多的中断请求。 |
3 | 串口 2(COM2) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 当串口 2 有数据到达或发生错误时,会触发此中断,以便操作系统进行相应的处理。 |
4 | 串口 1(COM1) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 功能与串口 2 类似,用于处理串口 1 的数据传输和错误情况。 |
5 | 并行口 2/声卡(LPT2/Sound Card) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 可用于并行口 2 的数据传输,也可能被声卡等设备使用,具体取决于系统配置。 |
6 | 软盘控制器(Floppy Disk Controller) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 当软盘驱动器进行读写操作或发生错误时,会触发此中断。 |
7 | 并行口 1(LPT1) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 用于处理并行口 1 的数据传输和状态变化。 |
8 | 实时时钟(Real - Time Clock,RTC) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 实时时钟芯片会按一定周期触发此中断,可用于系统时间的更新和定时任务。 |
9 | 重定向为通用用途(Redirected to General Purpose) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 在某些系统中,此中断可能被重定向用于其他设备或功能。 |
10 | 通用用途(General Purpose) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 可由系统管理员或设备驱动程序配置给特定的设备使用。 |
11 | 通用用途(General Purpose) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 用途与 IRQ 10 类似,可灵活分配给不同设备。 |
12 | 鼠标控制器(PS/2 Mouse Controller) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 当鼠标移动或按键被按下/释放时,鼠标控制器会触发此中断,以便操作系统处理鼠标输入。 |
13 | 数学协处理器(Math Coprocessor) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 当数学协处理器(如早期的 x87 FPU)发生错误或完成运算时,会触发此中断。 |
14 | 主 IDE 控制器(Primary IDE Controller) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 用于处理主 IDE 接口(通常连接硬盘、光驱等设备)的数据传输和状态变化。 |
15 | 从 IDE 控制器(Secondary IDE Controller) | 否 | 1. 压入 EFLAGS 2. 压入 CS 3. 压入 EIP | 1. iret 弹出 EIP 2. iret 弹出 CS 3. iret 弹出 EFLAGS | 功能与主 IDE 控制器类似,用于处理从 IDE 接口的设备。 |
需要注意的是,现代计算机系统中,随着硬件的发展和中断控制器的改进(如使用 APIC 替代传统的 PIC),IRQ 的分配和管理方式可能会有所不同。同时,一些设备可能会使用更高级的中断机制(如 MSI,Message - Signaled Interrupts)来提高中断处理的效率。
抱歉,此前表格存在逻辑错误,现重新梳理修正(基于32位系统,按iret
弹栈逻辑:先弹EFLAGS
,再弹CS
,最后弹EIP
等,结合入栈顺序还原正确栈操作):
操作阶段 | 操作步骤 | 操作内容 | 操作前 ESP 值 | 操作后 ESP 值 | 栈中存储情况(从栈顶到栈底) |
---|---|---|---|---|---|
入栈 | 初始状态 | - | 20 | 20 | 无 |
入栈 | 1 | pushl $0x23 | 20 | 16 | 地址 16: SS |
入栈 | 2 | pushl %%eax | 16 | 12 | 地址 12: ESP 地址 16: SS |
入栈 | 3 | pushfl | 12 | 8 | 地址 8: EFLAGS 地址 12: ESP 地址 16: SS |
入栈 | 4 | orl $0x200, (%%esp) | 8 | 8 | 地址 8: EFLAGS 地址 12: ESP 地址 16: SS |
入栈 | 5 | pushl $0x1B | 8 | 4 | 地址 4: CS 地址 8: EFLAGS 地址 12: ESP 地址 16: SS |
入栈 | 6 | pushl %%ebx | 4 | 0 | 地址 0: EIP 地址 4: CS 地址 8: EFLAGS 地址 12: ESP 地址 16: SS |
弹栈 | 1 | 弹出 EIP | 0 | 4 | 地址 4: CS 地址 8: EFLAGS 地址 12: ESP 地址 16: SS (弹出值:EIP ) |
弹栈 | 2 | 弹出 CS | 4 | 8 | 地址 8: EFLAGS 地址 12: ESP 地址 16: SS (弹出值:CS ) |
弹栈 | 3 | 弹出 EFLAGS | 8 | 12 | 地址 12: ESP 地址 16: SS (弹出值:EFLAGS ) |
弹栈 | 4 | 弹出 ESP | 12 | 16 | 地址 16: SS (弹出值:ESP ) |
弹栈 | 5 | 弹出 SS | 16 | 20 | 无(弹出值:SS ) |
修正说明:
iret
弹栈逻辑:按 EIP
→ CS
→ EFLAGS
顺序弹出(特权级切换场景),表格按此还原。EIP
最先被弹出)。(iret
正常弹栈逻辑:先弹 EIP
,再弹 CS
,最后弹 EFLAGS
)重新梳理,假设初始 ESP = 20
,32 位系统下每步栈操作 ESP
增减 4:
操作阶段 | 操作步骤 | 操作内容 | 操作前 ESP 值 | 操作后 ESP 值 | 栈中存储情况(从栈顶到栈底) |
---|---|---|---|---|---|
入栈 | 初始状态 | - | 20 | 20 | 无 |
入栈 | 1 | pushfl (压 EFLAGS ) | 20 | 16 | 地址 16: EFLAGS |
入栈 | 2 | pushl $0x1B (压 CS ) | 16 | 12 | 地址 12: CS 地址 16: EFLAGS |
入栈 | 3 | pushl %%ebx (压 EIP ) | 12 | 8 | 地址 8: EIP 地址 12: CS 地址 16: EFLAGS |
弹栈 | 1 | 弹出 EIP | 8 | 12 | 地址 12: CS 地址 16: EFLAGS (弹出值:EIP ) |
弹栈 | 2 | 弹出 CS | 12 | 16 | 地址 16: EFLAGS (弹出值:CS ) |
弹栈 | 3 | 弹出 EFLAGS | 16 | 20 | 无(弹出值:EFLAGS ) |
关键逻辑说明:
EFLAGS
→ CS
→ EIP
压栈,栈顶地址逐步递减(20→16→12→8)。iret
按 EIP
(栈顶最先压入)→ CS
→ EFLAGS
弹出,栈顶地址逐步回增(8→12→16→20),最终恢复初始 ESP
值。iret
、iretd
和 iretq
都是用于从中断或异常处理程序返回的指令,不过它们在不同的架构和操作数大小方面存在差异,下面为你详细介绍:
iret
iret
会弹出 6 个字节的数据,分别是 2 字节的 IP
(指令指针)、2 字节的 CS
(代码段寄存器)和 2 字节的 FLAGS
(标志寄存器);在 32 位模式下,它会弹出 12 个字节的数据,即 4 字节的 EIP
、4 字节的 CS
和 4 字节的 EFLAGS
。iretd
iretd
指令明确弹出 12 个字节的数据,具体为 4 字节的 EIP
、4 字节的 CS
和 4 字节的 EFLAGS
。在 32 位系统中,iretd
和 iret
的功能是一样的,只是 iretd
更清晰地表明是 32 位的中断返回操作。iretq
iretq
会弹出 16 个字节的数据,包含 8 字节的 RIP
(64 位指令指针)、8 字节的 CS
和 8 字节的 RFLAGS
(64 位标志寄存器)。这是为了适应 64 位系统更大的地址空间和寄存器宽度。iret
:当编写的代码需要同时兼容 16 位和 32 位系统时,使用 iret
是一个合适的选择。例如,在一些早期的操作系统开发或者跨平台的中断处理代码中会用到。iretd
:在 32 位系统的开发中,如果需要明确表示是 32 位的中断返回操作,使用 iretd
可以提高代码的可读性。比如在 32 位 Linux 内核的中断处理代码中就可能会使用到。iretq
:在 64 位系统的开发中,必须使用 iretq
来进行中断返回操作,以确保能够正确恢复 64 位的执行环境。例如,64 位 Windows 或 Linux 内核的中断处理程序就会使用 iretq
。iret
; 假设中断处理程序结束,准备返回
iret ; 弹出 IP、CS 和 FLAGS 恢复执行环境
iretd
; 32 位中断处理程序结束
iretd ; 弹出 EIP、CS 和 EFLAGS 恢复执行环境
iretq
; 64 位中断处理程序结束
iretq ; 弹出 RIP、CS 和 RFLAGS 恢复执行环境
综上所述,iret
、iretd
和 iretq
的主要区别在于适用的架构和操作数大小,你需要根据具体的系统架构来选择合适的指令。
以下是补充了不同CALL指令区别的详细表格:
| 指令类型 | 操作数类型 | 执行流程 | 适用架构 | 使用场景 | 示例代码 | |—————-|——————|————————————————————————–|———-|————————————————————————–|——————————| | call label
| 相对偏移量(近调用) | 1. 压入当前EIP/RIP
2. 跳转到label
地址 | 16/32/64位 | 同一代码段内的函数调用 | call subproc
call 0x1234
| | call reg
| 寄存器中的地址 | 1. 压入当前EIP/RIP
2. 跳转到寄存器指定的地址 | 32/64位 | 通过寄存器间接调用(如call eax
) | call edi
call rax
| | call mem
| 内存中的地址 | 1. 压入当前EIP/RIP
2. 跳转到内存指定的地址 | 32/64位 | 通过内存间接调用(如call DWORD PTR [esi]
) | call [0x1000]
call qword ptr [rbp+8]
| | lcall
| 段选择子+偏移量(远调用) | 1. 压入当前CS和EIP
2. 跳转到指定段和偏移 | 16/32位 | 跨代码段调用(实模式/保护模式) | lcall 0x08:0x1234
| | call far ptr
| 段选择子+偏移量(远调用) | 功能与lcall
相同,但语法更明确 | 16/32位 | 显式远调用(兼容旧语法) | call far ptr [0x1000]
| | call qword ptr
| 64位远指针 | 1. 压入当前CS和RIP
2. 跳转到64位地址(含段选择子) | 64位 | 长模式下的远调用(需配合RIP相对寻址) | call qword ptr [rip+0x100]
|
call
):仅修改指令指针(IP/EIP/RIP),用于同一代码段内的跳转。lcall
/call far ptr
/call qword ptr
):修改段寄存器(CS)和指令指针,用于跨段调用(需特权级检查)。call subproc
)或绝对地址(如call 0x1234
)。0x08:0x1234
)。lcall
和retf
,操作数为16位段选择子+16位偏移量。call
支持32位偏移量,lcall
支持32位偏移量+16位段选择子。call
默认使用RIP相对寻址,call qword ptr
用于远调用(需显式指定段选择子)。syscall
指令进入内核态。call
)以提高效率。; 近调用(同一代码段)
call subroutine ; 压入当前EIP,跳转到subroutine
; 远调用(跨段)
lcall 0x08:0x1000 ; 压入当前CS和EIP,跳转到段0x08的0x1000地址
; 64位远调用
call qword ptr [rip+0x20] ; 压入当前CS和RIP,跳转到[rip+0x20]指向的64位地址
如果需要进一步解释某个指令的细节,可以随时补充说明!
以下是结合之前两个回答内容的整合版本,采用结构化呈现:
任务状态段(Task State Segment, TSS) 是x86架构用于存储任务上下文的核心数据结构,包含:
关键功能:
ltr
/task
指令)| 模式 | TSS结构差异 | 典型应用场景 | |————–|————————————-|—————————| | 保护模式 | 存储完整任务上下文(寄存器+控制信息)| 硬件任务切换(较少使用) | | 长模式 | 仅存储中断栈表(IST) | 中断处理栈切换 |
// arch/x86/include/asm/tss.h
struct tss_struct {
unsigned long esp0; // 特权级0栈指针
unsigned long ss0; // 特权级0栈段
unsigned long ldt; // LDT选择子
unsigned short iomap; // I/O许可位图基址
// ...其他保留字段
};
// arch/x86/kernel/tss.c
void __init tss_setup_idtentry(int cpu) {
struct tss_struct *tss = &per_cpu(init_tss, cpu);
// 设置内核栈指针
tss->esp0 = per_cpu(kernel_stack, cpu) + THREAD_SIZE;
tss->ss0 = __KERNEL_DS;
// 加载TSS到TR寄存器
load_TR(&tss_desc);
}
| 阶段 | 硬件行为 | 软件行为 | 关键指令/寄存器 | |——–|————————————————————————–|————————————————————————–|———————-| | 进入 | 1. 加载TSS.esp0/ss0
2. 压入EFLAGS/CS/EIP到内核栈 | 1. 保存通用寄存器(pusha
)
2. 保存段寄存器(push ds/es/fs/gs
) | IDTR
, TR
| | 处理 | 无 | 1. 调用do_IRQ()
处理中断
2. 恢复寄存器(pop ds/es/fs/gs
, popa
) | call
, iretd
| | 返回 | 1. 弹出EIP/CS/EFLAGS
2. 恢复用户栈指针(SS/ESP) | 无 | iret
|
interrupt_entry:
pusha ; 保存通用寄存器
push ds ; 保存段寄存器
call do_IRQ ; 调用C处理函数
pop ds
popa
iret ; 返回用户态
| 阶段 | 硬件行为 | 软件行为 | 关键指令/寄存器 | |——–|————————————————————————–|————————————————————————–|———————-| | 触发 | 1. 执行syscall
指令
2. 加载TSS.esp0/ss0
3. 压入RIP/CS/RFLAGS到内核栈 | 无 | syscall
, TR
| | 处理 | 无 | 1. 保存参数(push rdi/rsi/rbx
)
2. 调用sys_read
等系统服务例程 | system_call
| | 返回 | 1. 执行sysret
指令
2. 弹出RIP/CS/RFLAGS
3. 恢复用户栈指针 | 无 | sysret
|
; 用户态
mov eax, __NR_read
syscall
; 内核态
system_call:
push rbx ; 保存寄存器
call sys_read
pop rbx
sysret
| 阶段 | 硬件行为 | 软件行为 | 关键指令/寄存器 | |——–|————————————————————————–|————————————————————————–|———————-| | 触发 | 1. 检测到页错误
2. 压入EFLAGS/CS/EIP/错误码到内核栈 | 无 | CR2
, TR
| | 处理 | 无 | 1. 分析错误码(do_page_fault
)
2. 分配物理页并更新页表 | alloc_page
, mmap
| | 返回 | 1. 执行iret
指令
2. 弹出EIP/CS/EFLAGS
3. 恢复用户栈指针 | 无 | iret
|
page_fault:
push %error_code ; 硬件已压入错误码
call do_page_fault
add $4, %esp ; 跳过错误码
iret
| 维度 | 中断处理 | 系统调用 | 硬件异常处理 | |————–|————————-|————————-|————————-| | 触发方式 | 外部中断(如IRQ) | 软件指令(syscall) | 硬件错误(如页错误) | | 栈切换 | 自动加载TSS.esp0 | 自动加载TSS.esp0 | 自动加载TSS.esp0 | | 特权级 | 用户态→内核态 | 用户态→内核态 | 用户态→内核态 | | 保存内容 | EFLAGS/CS/EIP + 错误码 | RFLAGS/CS/RIP | EFLAGS/CS/EIP + 错误码 |
| 特性 | 说明 | |————–|———————————————————————-| | 核心功能 | 存储任务上下文,支持硬件栈切换 | | Linux实现 | 每个CPU一个TSS,仅保存内核栈和LDT信息 | | 关键指令 | ltr
(加载TSS)、syscall
(系统调用)、iret
(中断返回) | | 优化点 | 避免硬件任务切换,仅保留最小化硬件支持 |
应用建议:
ltr
指令访问TSS,但需注意特权级限制CR2
寄存器分析异常原因以下是关于硬件任务切换的详细技术解析,结合x86架构原理和TSS使用方法:
struct tss32 {
uint32_t prev_tss; // 前一个任务TSS选择子(链接指针)
uint32_t esp0; // 特权级0栈指针
uint32_t ss0; // 特权级0栈段选择子
uint32_t esp1; // 特权级1栈指针(保护模式)
uint32_t ss1; // 特权级1栈段选择子
uint32_t esp2; // 特权级2栈指针
uint32_t ss2; // 特权级2栈段选择子
uint32_t cr3; // 页目录基址寄存器
uint32_t eip; // 指令指针
uint32_t eflags; // 标志寄存器
uint32_t eax, ecx, edx, ebx; // 通用寄存器
uint32_t esp; // 用户栈指针(SS/ESP)
uint32_t ebp; // 基址指针
uint32_t esi; // 源变址寄存器
uint32_t edi; // 目标变址寄存器
uint16_t es, cs, ss, ds, fs, gs; // 段寄存器
uint16_t ldt; // LDT选择子
uint16_t iomap_base; // I/O许可位图基址
};
struct task_gate {
uint16_t offset_low; // TSS选择子偏移低16位
uint16_t selector; // TSS段选择子
uint8_t dcount; // 保留(必须为0)
uint8_t attributes; // P(1), DPL(0-3), TYPE(5)
uint16_t offset_high; // TSS选择子偏移高16位
};
; 定义全局描述符表(GDT)
gdt:
.quad 0x0000000000000000 ; 空描述符
.quad 0x00CF9A000000FFFF ; 代码段(32位,特权级0)
.quad 0x00CF92000000FFFF ; 数据段(32位,特权级0)
.quad tss_desc ; TSS描述符
.quad task_gate_desc ; 任务门描述符
; TSS描述符格式(32位)
tss_desc:
.word 0x0000 ; TSS段界限低16位
.word 0x0000 ; TSS段基址低16位
.byte 0x00 ; TSS段基址中8位
.byte 0x89 ; 类型(B=1, TSS=9)
.byte 0x00 ; 段界限高4位
.byte 0x00 ; TSS段基址高8位
; 任务门描述符(在IDT中)
task_gate_desc:
.word 0x0000 ; 偏移低16位
.word tss_selector ; TSS选择子(索引为3)
.byte 0x00 ; DCount
.byte 0x85 ; P=1, DPL=0, TYPE=5
.word 0x0000 ; 偏移高16位
// 任务A的TSS
struct tss32 task_a_tss = {
.ss0 = DATA_SEG_SEL, // 特权级0栈段
.esp0 = 0x00007c00, // 特权级0栈指针
.cr3 = page_directory_a, // 页目录基址
.eip = 0x00007c00, // 任务入口地址
.cs = CODE_SEG_SEL, // 代码段选择子
.eflags = 0x00000202, // 启用IF标志
// 其他寄存器初始化...
};
// 任务B的TSS
struct tss32 task_b_tss = {
.ss0 = DATA_SEG_SEL,
.esp0 = 0x00007d00,
.cr3 = page_directory_b,
.eip = 0x00007d00,
.cs = CODE_SEG_SEL,
.eflags = 0x00000202,
// 其他寄存器初始化...
};
; 通过任务门切换任务
jmp far [task_gate_desc]
; 直接切换任务(使用TSS选择子)
jmp 0x18:0x00000000 ; 选择子0x18指向目标TSS
; 用户态任务A执行切换指令
mov eax, 0x18 ; TSS选择子
ltr ax ; 加载任务A的TSS到TR寄存器
; 任务A主动切换到任务B
jmp far [task_b_gate]
| 步骤 | 操作内容 | 涉及寄存器/结构 | |——|———-|——————| | 1 | 保存当前任务上下文到旧TSS | TR寄存器指向的TSS | | 2 | 加载新任务TSS到TR寄存器 | 目标TSS选择子 | | 3 | 切换页目录(CR3) | 新TSS.cr3 | | 4 | 加载段寄存器(CS/DS等) | 新TSS中的段选择子 | | 5 | 恢复通用寄存器(EAX/EBX等) | 新TSS中的寄存器值 | | 6 | 跳转至新任务入口(EIP) | 新TSS.eip |
; 任务A(特权级0)切换到任务B(特权级0)
jmp far [task_b_tss_selector]
; 用户态任务执行特权指令触发异常
mov eax, 0x00000000
lgdt [gdt_ptr] ; 特权指令,触发异常
; 异常处理程序切换到内核任务
iret ; 硬件自动加载TSS.esp0
B
标志(0x80
)表示32位TSS0x67
(103字节)VMX
虚拟化技术实现section .text
extern task_a_tss
extern task_b_tss
switch_task:
; 保存当前任务上下文
mov eax, [tr_reg]
ltr ax
; 加载新任务TSS
mov ax, task_b_tss_selector
ltr ax
; 切换页目录
mov eax, [task_b_tss.cr3]
mov cr3, eax
; 跳转至新任务
jmp task_b_tss.eip
void switch_to_task(struct tss32 *tss) {
asm volatile (
"ltr %%ax\n"
"mov %%eax, %%cr3\n"
"ljmp $0x%x, $0x%x\n"
:
: "a" (tss->selector),
"i" (tss->cs),
"b" (tss->eip)
: "memory"
);
}
| 特性 | 硬件任务切换 | 软件任务切换(Linux) | |————–|—————————–|—————————–| | 上下文保存 | 硬件自动完成 | 软件显式保存(如switch_to
)| | 切换开销 | 约500-1000 CPU周期 | 约100-200 CPU周期 | | 灵活性 | 低(依赖硬件设计) | 高(可定制上下文) | | 应用场景 | 实时系统、虚拟化 | 通用操作系统 |
TR
寄存器配合调试寄存器(DR0-DR7)建议在现代系统开发中优先使用软件任务切换,硬件任务切换仅适用于特定场景(如实时嵌入式系统)。如需进一步优化,可结合Intel VT-x或AMD SVM技术实现虚拟化任务管理。
以下是使用Markdown高亮技术术语的版本(关键术语加粗,指令和寄存器用反引号包裹):
描述符类型 | 作用 | 加载方式 | 加载指令 | 使用示例 | 行为说明 |
---|---|---|---|---|---|
段描述符 | 定义内存段属性(如基地址、界限、访问权限),用于程序访问内存段。细分包括: - 代码段描述符 - 数据段描述符 - 栈段描述符 | 通过16位段选择子加载到段寄存器(如CS 、DS 、ES )。段选择子包含:- GDT/LDT索引 - 请求特权级(RPL) - 描述符表指示位(TI) | 无特定指令(通过mov 赋值段选择子) | asm<br>mov ax, 0x08 ; 代码段选择子<br>mov cs, ax ; 加载到CS寄存器<br> | CPU根据段选择子从GDT/LDT获取段描述符,后续内存访问基于描述符的基地址和界限计算物理地址。 |
任务状态段描述符 | 指向任务状态段(TSS),用于任务切换时保存/恢复上下文(寄存器、栈指针等)。 | 使用ltr 指令加载TSS描述符的选择子到任务寄存器(TR )。 | ltr | asm<br>mov ax, 0x10 ; TSS描述符选择子<br>ltr ax ; 加载到TR寄存器<br> | CPU根据选择子从GDT获取TSS描述符,将TSS基地址和界限存入TR ,任务切换时通过TR 找到目标TSS。 |
调用门描述符 | 实现跨特权级子程序调用(如用户态→内核态),包含目标代码段选择子、偏移量和权限检查逻辑。 | 通过call 或jmp 指令配合调用门选择子触发,CPU自动查找GDT/LDT中的调用门描述符。 | call 、jmp | asm<br>call 0x18:0x1234 ; 调用门选择子0x18,偏移量0x1234<br> | CPU验证调用权限(CPL ≤ DPL)后,跳转到目标代码段执行,自动压栈返回地址。 |
中断门描述符 | 处理硬件中断(如IRQ)和软件中断(如int 3 ),包含中断服务程序(ISR)的入口地址和权限。 | 通过初始化中断描述符表(IDT)加载,中断发生时CPU根据向量号查找IDT中的中断门。 | 无特定指令(IDT初始化) | c<br>// C语言示例:设置IDT表项<br>set_idt_entry(0x20, &irq_handler);<br> | 中断发生时,CPU自动压栈EFLAGS 、CS 、EIP ,并跳转到ISR,自动清除IF 标志。 |
陷阱门描述符 | 与中断门类似,但执行时不清除中断标志(IF ),允许嵌套中断。 | 与中断门相同,通过IDT加载。 | 无特定指令(IDT初始化) | asm<br>int 0x80 ; 触发陷阱门(系统调用)<br> | CPU压栈上下文后跳转到陷阱处理程序,IF 标志保持不变,允许响应其他中断。 |
任务门描述符 | 实现硬件任务切换,指向目标任务的TSS描述符。 | 通过call 或jmp 指令配合任务门选择子触发,CPU自动查找GDT中的任务门描述符。 | call 、jmp | asm<br>jmp 0x20:0x0000 ; 任务门选择子0x20,无实际偏移量<br> | CPU保存当前任务上下文到原TSS,加载目标任务的TSS,切换页目录(CR3 )并跳转执行。 |
CS
、TR
)ltr
、call
)IF
、RPL
)如需调整高亮方式或补充其他术语,请随时告知!