发信人: 
Zellux (null), 信区: Software_06
标  题: OSLab之中断处理
发信站: 日月光华 (2008年08月30日20:15:58 星期六), 站内信件
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 %d\n", 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就比较简单了,不需要任何内嵌汇编代码即可完成。