posts - 403, comments - 310, trackbacks - 0, articles - 7
            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          OSLab之中斷處理

          Posted on 2008-09-02 11:55 ZelluX 閱讀(642) 評論(5)  編輯  收藏 所屬分類: SystemCourses
          發信人: 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就比較簡單了,不需要任何內嵌匯編代碼即可完成。

          評論

          # re: OSLab之中斷處理 [未登錄]  回復  更多評論   

          2008-09-02 13:22 by damocles
          你們做的是哪個?nachos? 看著覺得不像啊。。。

          # re: OSLab之中斷處理   回復  更多評論   

          2008-09-02 13:40 by ZelluX
          @damocles
          CMU的OSLab簡化版,最后是要實現一個支持二級頁表、多進程切換調度的微內核,少了文件系統。

          # re: OSLab之中斷處理   回復  更多評論   

          2008-09-07 20:54 by luoyan
          第四點在2.6.9的Linux內核中實際上對應arch/i386/kernel/entry.S

          .data
          ENTRY(interrupt)
          .previous

          vector=0
          ENTRY(irq_entries_start)
          .rept NR_IRQS
          ALIGN
          1: pushl $vector-256
          jmp common_interrupt
          .data
          .long 1b
          .previous
          vector=vector+1
          .endr


          ALIGN
          common_interrupt:
          SAVE_ALL
          call do_IRQ
          jmp ret_from_intr

          實現的功能是一樣的,只是Linux的匯編更加晦澀

          第五點:與arch/i386/kernel/entry.S中的SAVE_ALL,RESTORE_ALL類似,只是PUSHA的寄存器更多一些。
          挺有意思的,呵呵

          # re: OSLab之中斷處理   回復  更多評論   

          2008-09-07 22:42 by ZelluX
          @luoyan
          贊羅帥

          # re: OSLab之中斷處理   回復  更多評論   

          2008-09-09 09:25 by sherry
          zan~
          主站蜘蛛池模板: 孟连| 双鸭山市| 深水埗区| 旬邑县| 藁城市| 忻州市| 新闻| 三台县| 红安县| 和龙市| 瑞昌市| 山西省| 西昌市| 安福县| 达日县| 和龙市| 台中市| 阳信县| 精河县| 遂昌县| 天津市| 海林市| 潼南县| 中卫市| 莲花县| 上高县| 平江县| 怀化市| 永福县| 习水县| 平利县| 山丹县| 纳雍县| 田林县| 柳江县| 平湖市| 奉化市| 青海省| 天等县| 通化县| 尼玛县|