把中断按事件来源分类,来自CPU外部的中断就称为外部中断,来自CPU内部的中断就称为内部中断。
外部中断可分为:可屏蔽中断和不可屏蔽中断
内部中断可分为:软中断和异常
以下是可以发起中断的指令:
“int 8 位立即数”。这是我们以后常用的指令,我们要通过它进行系统调用,8 位立即数可表示 256种中断,这与处理器所支持的中断数是相吻合的。
“int3”。这可不是 int 空格 3,它们之间无间隙。int3 是调试断点指令,其所触发的中断向量号是 3,int3的机器码是0xcc。
into。这是中断溢出指令,它所触发的中断向量号是 4。不过,能否引发 4 号中断是要看 eflags 标志寄存器中的 OF 位是否为 1,如果是 1 才会引发中断,否则该指令悄悄地什么都不做,低调得很。
bound。这是检查数组索引越界指令,它可以触发 5 号中断,用于检查数组的索引下标是否在上下边界之内。该指令格式是bound 16/32 位寄存器, 16/32 位内存
。目的操作数是用寄存器来存储的,其内容是待检测的数组下标值。。源操作数是内存,其内容是数组下标的下边界和上边界。当执行 bound 指令时,若下标处于数组索引的范围之外,则会触发 5 号中断。
ud2。未定义指令,这会触发第 6 号中断。该指令表示指令无效,CPU 无法识别。主动使用它发起中断,常用于软件测试中,无实际用途。
以上几种软中断指令,除第一种的“int 8 位立即数”之外,其他的几种又可以称为异常。
向量号 | 助记符 | 说明 | 类型 | 错误号 | 产生源 |
---|---|---|---|---|---|
0 | #DE | 除 出错 | 故障 | 无 | DIV或IDIV指令 |
1 | #DB | 调试 | 故障/陷阱 | 无 | 任何代码或数据引用,或是INT 1指令 |
2 | - - | NMI中断 | 中断 | 无 | 非屏蔽外部中断 |
3 | #BP | 断点 | 陷阱 | 无 | INT 3指令 |
4 | #OF | 溢出 | 陷阱 | 无 | INTO指令 |
5 | #BR | 边界范围超出 | 故障 | 无 | BOUND指令 |
6 | #UD | 无效操作码(未定义操作码) | 故障 | 无 | UD2指令或保留的操作码(奔腾Pro中加入的新指令) |
7 | #NM | 设备不存在(无数学协处理器) | 故障 | 无 | 浮点或WAIT/FWAIT指令 |
8 | #DF | 双重错误 | 异常终止 | 有(0) | 任何可产生异常、NMI或INTR的指令 |
9 | - - | 协处理器段超越(保留) | 故障 | 无 | 浮点指令(386以后的CPU不产生该异常) |
10 | #TS | 无效的任务状态段TSS | 故障 | 有 | 任务交换或访问TSS |
11 | #NP | 段不存在 | 故障 | 有 | 加载段寄存器或访问系统段 |
12 | #SS | 堆栈错误 | 故障 | 有 | 堆栈操作和SS寄存器加载 |
13 | #GP | 一般保护错误 | 故障 | 有 | 任何内存引用和其他保护检查 |
14 | #PF | 页面保护 | 故障 | 有 | 任何内存引用 |
15 | (Intel保留,请勿使用) | 无 | |||
16 | #MF | x87FPU浮点错误(数学错误) | 故障 | 无 | x87FPU浮点或WAIT/FWAIT指令 |
17 | #AC | 对齐检查 | 故障 | 有(0) | 对内存中任何数据的引用 |
18 | #MC | 机器检查 | 异常终止 | 无 | 错误码(若有)和产生源与CPU类型有关(奔腾处理器引进) |
19 | #XM | SIMD浮点异常 | 故障 | 无 | SSE和SSE2浮点指令(PIII处理器引进) |
20~31 | - - | (Intel保留,请勿使用) | |||
32~255 | - - | 用户定义(非保留)中断 | 中断 | 外部中断或者INT n指令 |
CPU 内部有个中断描述符表寄存器(Interrupt Descriptor Table Register,IDTR),该寄存器分为两部分:第0~15位是表界限, IDT 大小减 1,第 16~47 位是 IDT 的基地址。16 位的表界限,表示最大范围是 0xffff,即 64KB。可容纳的描述符个数是 64KB/8=8K=8192 个。特别注意的是 GDT 中的第 0个段描述符是不可用的,但 IDT 却无此限制,第 0 个门描述符也是可用的,中断向量号为 0 的中断是除法错。但处理器只支持 256个中断,即 0~254,中断描述符中其余的描述符不可用。在门描述符中有个 P 位,所以,咱们将来在构建 IDT 时,记得把 P 位置 0,这样就表示门描述符中的中断处理程序不在内存中。
同加载 GDTR 一样,加载 IDTR 也有个专门的指令—lidt,其用法是:lidt 48 位内存数据
完整的中断过程分为 CPU 外和 CPU 内两部分:
CPU 内的过程:
中断发生后,eflags中的NT位和TF位会被置0.如果中断对应的门描述符是中断门,标志寄存器eflags的IF位会被自动置0,避免中断嵌套,即中断处理过程中又来了个新的中断,这是为防止处理某个中断的过程中又来了个相同的中断。这会导致一般保护性(GP)异常。这表示默认情况下,处理器会在无人打扰的方式下执行中断门描述符中的中断处理例程。
若中断发生时对应的是任务门或陷阱门,CPU 是不会将 IF 位清 0 的。因为陷阱门主要用于调试,它允许 CPU 响应更高级别的中断,所以允许中断嵌套。而对任务门来说,这是执行一个新任务,任务都应该在开中断的情况下进行,否则就独占 CPU 资源,操作系统也会由多任务退化成单任务了。
从中断返回的指令是 iret,它从栈中弹出数据到寄存器 cs、eip、eflags 等,根据特权级是否改变,判断是否要恢复旧栈,也就是说是否将栈中位于 SS_old 和 ESP_old 位置的值弹出到寄存 ss 和 esp。当中断处理程序执行完成返回后,通过 iret 指令从栈中恢复 eflags 的内容。
中断发生后,由于CS加载了新的目标代码段选择子,处理器不管新的选择子和任何段寄存器中当前的选择子是否相同,也不管这两个选择子是否指向相同的段,只要段寄存器被加载,段描述符缓冲寄存器就会被刷新,处理器都会认为换了一个段,属于段间转移,也就是远转移。所以,当前进程被中断打断后,为了从中断返回后能继续运行该进程,处理器自动把CS和EIP的当前值保存到中断处理程序使用的栈中。不同特权级别下处理器使用不同的栈,至于中断处理程序使用的是哪个栈,要视它当时所在的特权级别,因为中断是可以在任何特权级别下发生的。除了要保存CS,EIP外,还需要保存标志寄存器EFLAGS,如果涉及到特权级变换,还要压入SS和ESP寄存器。
下面看看以上寄存器入栈情况及顺序,这里不再讨论有关特权检查的内容:
处理器根据中断向量号找到对应的中断描述符后,拿CPL和中断门描述符中选择子对应的目标代码段的DPL比对,若CPL权限比DPL低,这表示要向高特权级转移,需要切换到高特权级的栈。这意味着当执行完中断处理程序后,若要正确返回到当前被中断的进程,同样需要将栈恢复为此时的旧栈。于是处理器先临时保存旧栈SS和ESP的值,记作SS_old 和 ESP_old,然后在TSS中找到同目标代码段DPL级别相同的栈加载到寄存器SS和ESP中,记作SS_new和ESP_new,再将之前临时保存的SS_old和ESP_old压入新栈备份,以备返回时重新加载到栈段寄存器SS和栈指针ESP。由于SS_old是16位数据,32位模式下的栈操作数是32位,所以将SS_old用0扩展其16位,成为32位数据后入栈。此时新栈内容如图A所示。
在新栈中压入EFLAGS寄存器,新栈内容如图B所示
由于要切换到目标代码段,对于这种段间转移,要将CS和EIP保存到当前栈中备份,记作CS_old和EIP_old,以便中断程序执行结束后能恢复到被中断的进程。同样 CS_old 是 16 位数据,需要用 0 填充其高 16 位,扩展为 32 位数据后入栈。此时新栈内容如图C所示。
某些异常会有错误码,此错误码用于报告异常是在哪个段上发生的,也就是异常发生的位置,所以错误码中包含选择子等信息,错误码会紧跟在 EIP 之后入栈,记作 ERROR_CODE。此时新栈内容如图D所示。
下面咱们聊聊这个从中断处理程序返回的过程:
(1)当处理器执行到 iret 指令时,它知道要执行远返回,首先需要从栈中返回被中断进程的代码段选择子CS_old 及指令指针 EIP_old。这时候它要进行特权级检查。先检查栈中 CS 选择子 CS_old,根据其RPL 位,即未来的 CPL,判断在返回过程中是否要改变特权级。
(2) 栈中 CS 选择子是 CS_old,根据 CS_old 对应的代码段的 DPL 及 CS_old 中的 RPL 做特权级检查,如果检查通过,随即需要更新寄存器 CS 和 EIP。由于 CS_old 在入栈时已经将高 16 位扩充为 0,现在是 32 位数据,段寄存器 CS 是 16 位,故处理器丢弃 CS_old 高 16 位,将低 16 位加载到 CS,将 EIP_old 加载到 EIP 寄存器,之后栈指针指向 EFLAGS。如果进入中断时未涉及特权级转移,此时栈指针是 ESP_old(说明在之前进入中断后,是继续使用旧栈)。否则栈指针是 ESP_new(说明在之前进入中断后用的是 TSS 中记录的新栈)。
(3) 将栈中保存的 EFLAGS 弹出到标志寄存器 EFLAGS。如果在第 1 步中判断返回后要改变特权级,此时栈指针是 ESP_new,它指向栈中的 ESP_old。否则进入中断时属于平级转移,用的是旧栈,此时栈指针是 ESP_old,栈中已无因此次中断发生而入栈的数据,栈指针指向中断发生前的栈顶。
(4) 如果在第 1 步中判断出返回时需要改变特权级,也就是说需要恢复旧栈,此时便需要将 ESP_old和 SS_old 分别加载到寄存器 ESP 及 SS,丢弃寄存器 SS 和 ESP 中原有的 SS_new 和 ESP_new,同时进行特权级检查。补充,由于 SS_old 在入栈时已经由处理器将高 16 位填充为 0,现在是 32 位数据,所以在重新加载到栈段寄存器 SS 之前,需要将 SS_old 高 16 位剥离丢弃,只用其低 16 位加载 SS。
有些中断会在栈中压入错误码,有点“临终遗言,提供线索”的意味,用来指明中断发生在哪个段上。所以,错误码最主要的部分就是选择子,只不过此选择子可以在多种表中检索描述符。错码码由几部分组成,格式如图 所示
8259A 用于管理和控制可屏蔽中断,它表现在屏蔽外设中断,对它们实行优先级判决,向 CPU 提供中断向量号等功能。而它称为可编程的原因,就是可以通过编程的方式来设置以上的功能。
Intel 处理器共支持 256 个中断,但 8259A 只可以管理 8 个中断,所以为了多支持一些中断设备,提供了另一个解决方案,将多个 8259A 组合,官方术语就是级联。有了级联这种组合后,每一个 8259A 就被称为 1 片。若采用级联方式,即多片 8259A 芯片串连在一起,最多可级 9 个,也就是最多支持 64 个中断。n 片 8259A 通过级联可支持 7n+1 个中断源,级联时只能有一片 8259A为主片 master,其余的均为从片 slave。来自从片的中断只能传递给主片,再由主片向上传递给 CPU,也就是说只有主片才会向 CPU 发送 INT 中断信号。
每个独立运行的外部设备都是一个中断源,它们所发出的中断,只有接在中断请求(IRQ:InterruptReQuest)信号线上才能被 CPU 大神知晓,这也就是大家在开机时,电脑屏幕上会看到的 IRQ1…IRQn,这些都是为外部设备所分配的中断号。
8259A工作流程:当某个外设发送一个中断信号时,由于主板上已经将信号通路指向了8259A芯片的某个IRQ接口,所以该中断信号最终被送入了8259A。
如果8259A的“EOI通知(End Of Interrupt)”若被设置为非自动模式(手工模式),中断处理程序结束处必须有向 8259A 发送 EOI 的代码,8259A 在收到 EOI 后,将当前正处理的中断在 ISR 寄存器中对应的 BIT 置 0。如果“EOI 通知”被设置为自动模式,在刚才 8259A 接收到第二个 INTA 信号后,也就是 CPU 向 8259A 要中断向量号的那个 INTA,8259A 会自动将此中断在 ISR 中对应的 BIT 置 0。
8259A 的编程就是对它进行初始化,设置主片与从片的级联方式,指定起始中断向量号以及设置各种工作。
中断向量号是逻辑上的东西,它在物理上是 8259A 上的 IRQ 接口号。8259A 上 IRQ 号的排列顺序是固定的,但其对应的中断向量号是不固定的,这其实是一种由硬件到软件的映射,通过设置 8259A,可以 IRQ 接口映射到不同的中断向量号。
在 8259A 内部有两组寄存器,一组是初始化命令寄存器组,用来保存初始化命令字(InitializationCommand Words,ICW),ICW 共 4 个,ICW1~ICW4。另一组寄存器是操作命令寄存器组,用来保存操作命令字(Operation Command Word,OCW),OCW 共 3 个,OCW1~OCW3。所以,我们对 8259A 的编程,也分为初始化和操作两部分。
ICW2 用来设置起始中断向量号,就是前面所说的硬件 IRQ 接口到逻辑中断向量号的映射。由于每个8259A 芯片上的 IRQ 接口是顺序排列的,所以咱们这里的设置就是指定 IRQ0 映射到的中断向量号,其他IRQ 接口对应的中断向量号会顺着自动排下去。
ICW3 仅在级联的方式下才需要(如果 ICW1 中的 SNGL 为 0),用来设置主片和从片用哪个 IRQ 接口互连。
由于主片和从片的级联方式不一样,对于这个 ICW3,主片和从片都有自己不同的结构
OCW1 用来屏蔽连接在 8259A 上的外部设备的中断信号,实际上就是把 OCW1 写入了 IMR 寄存器。这里的屏蔽是说是否把来自外部设备的中断信号转发给 CPU。由于外部设备的中断都是可屏蔽中断,所以最终还是要受标志寄存器 eflags 中的 IF 位的管束,若 IF 为 0,可屏蔽中断全部被屏蔽,也就是说,在IF 为 0 的情况下,即使 8259A 把外部设备的中断向量号发过来,CPU 也置之不理。
对于 8259A 的初始化必须最先完成,步骤是:
在以上初始化 8259A 之后才可以用 OCW 对它操作。
//interrupt.c
#include "interrupt.h"
#include "print.h"
#include "io.h"
/*中断门描述符结构体*/
struct gate_desc {
unsigned short func_offset_low_word;
unsigned short selector;
unsigned char dcount; //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑
unsigned char attribute;
unsigned short func_offset_high_word;
};
static struct gate_desc idt[33]; // idt是中断描述符表,本质上就是个中断门描述符数组
extern int intr_entry_table[33];
static void idt_desc_init(void)
{
int i;
for (i = 0; i < 33; i++)
{
idt[i].func_offset_low_word = ((unsigned int)intr_entry_table[i]) & 0x0000FFFF;
idt[i].selector=0b1000; //代码段选择子
idt[i].dcount = 0; //未使用
idt[i].attribute=0b10001110;
idt[i].func_offset_high_word = ((unsigned int)intr_entry_table[i] & 0xFFFF0000) >>16;
}
put_str(" idt_desc_init done\n");
}
/* 初始化可编程中断控制器8259A */
static void pic_init(void) {
/* 初始化主片 */
outb (0x20, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb (0x21, 0x20); // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.
outb (0x21, 0x04); // ICW3: IR2接从片. 对于主片,ICW3 中置 1 的那一位对应的 IRQ 接口用于连接从片
outb (0x21, 0x01); // ICW4: 8086模式, 正常EOI
/* 初始化从片 */
outb (0xa0, 0x11); // ICW1: 边沿触发,级联8259, 需要ICW4.
outb (0xa1, 0x28); // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.
outb (0xa1, 0x02); // ICW3: 设置从片连接到主片的IR2引脚
outb (0xa1, 0x01); // ICW4: 8086模式, 正常EOI
/* 打开主片上IR0,也就是目前只接受时钟产生的中断 */
outb (0x21, 0xfe); //OCW1主片
outb (0xa1, 0xff); //OCW1 从片
put_str(" pic_init done\n");
}
void idt_init()
{
put_str("idt_init start\n");
idt_desc_init(); // 初始化中断描述符表
pic_init(); // 初始化8259A
//第0~15位是表界限,即IDT减1,可容纳8192个中段描述符;第16~47位时IDT的基地址。
/* 加载idt */
unsigned long long int idt_operand = ((sizeof(idt) - 1) | (( unsigned long long int)(unsigned int)idt << 16));
asm volatile("lidt %0" : : "m" (idt_operand));
put_str("idt_init done\n");
}
//kernel.c
#include "print.h"
#include "io.h"
void intr_entry_0()
{
put_str("intr_entry_0\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_1()
{
put_str("intr_entry_1\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_2()
{
put_str("intr_entry_2\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_3()
{
put_str("intr_entry_3\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_4()
{
put_str("intr_entry_4\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_5()
{
put_str("intr_entry_5\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_6()
{
put_str("intr_entry_6\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_7()
{
put_str("intr_entry_7\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_8()
{
put_str("intr_entry_8\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_9()
{
put_str("intr_entry_9\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_a()
{
put_str("intr_entry_A\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_b()
{
put_str("intr_entry_b\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_c()
{
put_str("intr_entry_c\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_d()
{
put_str("intr_entry_d\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_e()
{
put_str("intr_entry_e\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_f()
{
put_str("intr_entry_f\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_10()
{
put_str("intr_entry_10\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_11()
{
put_str("intr_entry_11\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_12()
{
put_str("intr_entry_12\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_13()
{
put_str("intr_entry_13\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_14()
{
put_str("intr_entry_14\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_15()
{
put_str("intr_entry_15\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_16()
{
put_str("intr_entry_16\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_17()
{
put_str("intr_entry_17\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_18()
{
put_str("intr_entry_18\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_19()
{
put_str("intr_entry_19\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_1a()
{
put_str("intr_entry_1a\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_1b()
{
put_str("intr_entry_1b\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_1c()
{
put_str("intr_entry_1c\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_1d()
{
put_str("intr_entry_1d\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_1e()
{
put_str("intr_entry_1e\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("addl $4, %esp;iret");
}
void intr_entry_1f()
{
put_str("intr_entry_1f\n");
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
void intr_entry_20()
{
static int i=0;
put_str("intr_entry_20\n");
put_int(i);
put_str("\n");
i++;
// 向主片和从片发送中断结束命令(EOI)
outb(0xA0, 0x20); // 向从片发送EOI
outb(0x20, 0x20); // 向主片发送EOI
asm volatile ("leave");
asm volatile ("iret");
}
int intr_entry_table[33]={
(int)intr_entry_0,
(int)intr_entry_1,
(int)intr_entry_2,
(int)intr_entry_3,
(int)intr_entry_4,
(int)intr_entry_5,
(int)intr_entry_6,
(int)intr_entry_7,
(int)intr_entry_8,
(int)intr_entry_9,
(int)intr_entry_a,
(int)intr_entry_b,
(int)intr_entry_c,
(int)intr_entry_d,
(int)intr_entry_e,
(int)intr_entry_f,
(int)intr_entry_10,
(int)intr_entry_11,
(int)intr_entry_12,
(int)intr_entry_13,
(int)intr_entry_14,
(int)intr_entry_15,
(int)intr_entry_16,
(int)intr_entry_17,
(int)intr_entry_18,
(int)intr_entry_19,
(int)intr_entry_1a,
(int)intr_entry_1b,
(int)intr_entry_1c,
(int)intr_entry_1d,
(int)intr_entry_1e,
(int)intr_entry_1f,
(int)intr_entry_20};
用objdump -d kernel.o
命令查看反汇编代码可以看出,编译器在函数的开头部分,增加了保存旧的基址指针、建立新的基址指针以及为局部变量分配空间的代码。所以需要使用leave
命令相当于mov esp, ebp
pop ebp
,使堆栈平衡。