2

OSLab 之中断处理

 2 years ago
source link: https://blog.yxwang.me/2008/09/oslab-interrupt-handling/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

1. 准备工作

在开始分析Support Code之前,先配置下我们的Source Insight,使它能够支持.s文件的搜索。

在Options->Document Options->Document Types中选择x86 Asm Source File,在File fileter中增加一个*.s,变成*.asm;*.inc;*.s 然后在Project->Add and Remove Project Files中重新将整个oslab的目录加入,这样以后进行文本搜索时.s文件也不会漏掉了。

2. Source Insight使用

接下来简单分析下内核启动的过程,在浏览代码的过程中可以迅速的掌握Source Insight的使用技巧。

lib/multiboot /multiboot.s完成了初始化工作,可以看到其中一句call EXT(multiboot_main)调用了C函数multiboot_main,使用ctrl+/搜索包含multiboot_main的所有文件,最终base_multiboot_main.c中找到了它的定义。依次进行cpu、内存的初 始化,然后开启中断,跳转到kernel_main函数,也是Lab1中所要改写的函数之一。另外 在这里可以通过ctrl+单击或者ctrl+=跳转到相应的函数定义处,很方便。

3. irq处理初始化工作

来看下Lab 1的重点之一,irq的处理。跟踪multiboot_main->base_cpu_setup->base_cp u_init->base_irq_init,可以看到这行代码

gate_init(base_idt,  base_irq_inittab,  KERNEL_CS);

继续使用ctrl+/找到base_irq_inittab的藏身之处:base_irq_inittab.s

4. base_irq_inittab.s

这个汇编文件做了不少重复性工作,方便我们在c语言级别实现各种handler。

GATE_INITTAB_BEGIN(base_irq_inittab)  /* irq处理函数表的起始,还记得jump
table 吗? */
MASTER(0, 0) /* irq0 对应的函数  */

来看看这个MASTER(0, 0)宏展开后是什么样子:

#define MASTER(irq, num)
GATE_ENTRY(BASE_IRQ_MASTER_BASE + (num), 0f, ACC_PL_K|ACC_INTR_GATE)  ;
P2ALIGN(TEXT_ALIGN) ;
0: ;
pushl $(irq) /* error code = irq vector  */ ;
pushl $BASE_IRQ_MASTER_BASE + (num) /* trap number */ ;
pusha /*  save general registers */ ;
movl $(irq),%ecx /* irq vector number */  ;
movb $1 << num,%dl /* pic mask for this irq */ ;
jmp  master_ints

依次push irq号,trap号(0x20+irq号),通用寄存器(eax ecx等)入栈,把irq号保 存到ecx寄存器,然后跳转到master_ints,master_ints是所有master interrupts公用 的代码。

跳过master_ints的前几行,从第七行开始

/* Acknowledge the  interrupt */
movb $0x20,%al
outb %al,$0x20

/* Save the rest of the  standard trap frame (oskit/x86/base_trap.h). */
pushl %ds
pushl  %es
pushl %fs
pushl %gs

/* Load the kernel's segment registers.  */
movw %ss,%dx
movw %dx,%ds
movw %dx,%es

/* Increment the  hardware interrupt nesting counter */
incb EXT(base_irq_nest)

/* Load  the handler vector */
movl  EXT(base_irq_handlers)(,%ecx,4),%esi

注释写得很详细,首先发送0x20到0x20端口,也就是Lab1文档上所说的发送INT_CTL_DON E到INT_CTL_REG,看来这一步support code已经替我们完成了。接下来保存四个段寄存 器ds es fs gs,并读入kernel态的段寄存器信息。

最后一句很关键,把base_irq_handlers + %ecx * 4这个值保存到了esi寄存器中,%ecx 中保存了irq号,而*4则是一个函数指针的大小,那么base_irq_handlers是什么呢?继 续用ctrl+/搜索,可以在base_irq.c中找到这个数组的定义 unsigned int (*base_irq_handlers[BASE_IRQ_COUNT])(struct trap_state *ts) 且初始时这个数组的每一项都是base_irq_default_handler

看来这句汇编代码的功能是把处理irq对应的函数地址保存到了esi寄存器中。 为了证实这一点,继续看base_irq_inittab.s的代码:

#else
/*  Call the interrupt handler with the trap frame as a parameter */
pushl  %esp
call *%esi
popl  %edx
#endif

果然,在保存了esp值后,紧接着就调用了esi指向的那个函数。而从那个函数返回后, 之前在栈上保存的相关信息都被恢复了:

/*  blah blah blah */
/* Return from the interrupt */
popl %gs
popl  %fs
popl %es
popl %ds
popa
addl $4*2,%esp /* Pop trap number and  error code  */
iret

这样就恢复到了进入这个irq处理单元前的状态,文档中所要求的保存通用寄存器这一步 其实在这里也已经完成了,不需要我们自己写代码。

好了,这样一分析后,我们要做的事情就很简单,就是把base_irq_handlers数组中的对 应项改成相应的handler函数就行了。 注意index是相应的idt_entry号减去BASE_IRQ_SLAVE_BASE,或者直接使用IRQ号。

另外这个数组的初始值都是base_irq_default_handler,用ctrl+左键跳到这个函数的定 义,可以看到这个函数只有一句简单的输出语句: printf(“Unexpected interrupt %dn”, ts->err); 而这就是没有注册handler前我们所看到的那句Unexpected interrupt 0的来源了。

5. struct trap_state *ts

所有的handler函数的参数都是一个struct trap_state *ts,这个参数是哪来的呢? 注意call *%esi的前一行

/* Call the interrupt handler with the  trap frame as a parameter */
pushl  %esp

这里把当前的esp当作指向ts的指针传给了handler,列一下从esp指向的地址开始的内容 ,也就是在此之前push入栈的内容:

pushl $(irq) /* error code = irq vector */ ;
pushl  $BASE_IRQ_MASTER_BASE + (num) /* trap number */ ;
pusha /* save general  registers */ ;
pushl %ds
pushl %es
pushl %fs
pushl %gs

再看一下trap_state的定义,你会发现正好和push的顺序相反:

/* Saved segment registers  */
unsigned int gs;
unsigned int fs;
unsigned int es;
unsigned int  ds;

/* PUSHA register state frame */
unsigned int edi;
unsigned  int esi;
unsigned int ebp;
unsigned int cr2; /* we save cr2 over esp for  page faults */
unsigned int ebx;
unsigned int edx;
unsigned int  ecx;
unsigned int eax;

/* Processor trap number, 0-31. */
unsigned  int trapno;

/* Error code pushed by the processor, 0 if none.  */
unsigned int err;

而这个定义后面的

/* Processor state frame  */
unsigned int eip;
unsigned int cs;
unsigned int eflags;
unsigned  int esp;
unsigned int ss;

则是发生interrupt时硬件自动push的五个数据(参见Understand the Linux Kernel)

也就是说,ts指针指向的是调用当前handler前的寄存器状态,也是当前handler结束后 用来恢复的寄存器状态,了解这一点对以后的几个lab帮助很大。

p.s. 另外提一句和这个lab无关的话,非vm86模式下栈上是不会有v86_es等四个寄存器 信息的,所以以后根据task_struct指针计算*ts的地址时使用的偏移量不应该是sizeof( struct trap_state)

6. The End

这样差不多就把support code中处理interrupt的方法过了一遍(另外还有base_trap_in ittab.s,不过和irq的处理很相似)

了解这些后Lab1就比较简单了,不需要任何内嵌汇编代码即可完成。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK