關於 XtratuM 的中斷接管過程

這裏分析的是 XtratuM 1.0 的代碼。

關於 XM 中斷接管的代碼主要在 arch/$ARCH/kernel/irq.c ($ARCH = i386) 中。還有一部分在 patch 文件中,不過那個貌似關係不是很大,主要是替換了某些 cli 和 sti 指令,但是這些替換後的代碼實質上和 cli、sti 的作用是一樣的,所以我也有點奇怪爲什麼要有這樣的替換,貌似不替換也是可以的……


在 irq.c 中,替換函數是 hw_irq_takeover():

/* filename: arch/i386/kernel/irq.c */

193 int hw_irq_takeover (void) {
194   unsigned long hw_flags, vector, irq;
195 
196    // Our irq and trap tables which will replace actual IDT table
197 
198   irq_addr  = (void (**) (void)) &__start_irq_handlers_addr;
199   trap_addr = (void (**) (void)) &__start_trap_handlers_addr;
200 
201   real_idt_table = hw_get_idt_table_addr ();
202 
203   hw_save_flags_and_cli(&hw_flags);
204 
205   __root_sti = XM_root_func.__sti;
206   __root_cli = XM_root_func.__cli;
207   __root_save_flags = XM_root_func.__save_flags;
208   __root_restore_flags = XM_root_func.__restore_flags;
209   __root_is_cli = XM_root_func.__is_cli;
210 
211   XM_root_func.__sti = vsti;
212   XM_root_func.__cli = vcli;
213   XM_root_func.__save_flags = vsave_flags;
214   XM_root_func.__restore_flags = vrestore_flags;
215   XM_root_func.__is_cli = vis_cli;
216   XM_root_func.__emulate_iret = emulate_iret;
217 
218   for (vector = 0; vector < IDT_ENTRIES; vector ++)
219     root_idt_table [vector] = hw_get_gate_addr (vector);
220 
221 
222   for (irq = 0; irq < NR_IRQS; irq ++) {
223     hw_xpic [irq] = ((irq_desc_t *)XM_root_func.__irq_desc)[irq].handler;
224     ((irq_desc_t *)XM_root_func.__irq_desc)[irq].handler = &vpic;
225   }
226 
227   // In an i386 there are 16 irqs, 0..15 (besides of the apic interrupt)
228 
229   for (irq = 0; irq < NR_IRQS; irq++) {
230     vector = irq + FIRST_EXTERNAL_VECTOR;
231 
232     // Replacing all hw irq gates for XtratuM routines
233     hw_set_irq_gate(vector, irq_addr [irq]);
234   }
235
236   hw_set_trap_gate(0, trap_addr[0]);
237   hw_set_trap_gate(1, trap_addr[1]);
238   hw_set_sys_gate(3, trap_addr[3]);
239   hw_set_sys_gate(4, trap_addr[4]);
240   hw_set_sys_gate(5, trap_addr[5]);
241   hw_set_trap_gate(6, trap_addr[6]);
242   hw_set_trap_gate(7, trap_addr[7]);
243   hw_set_trap_gate(8, trap_addr[8]);
244   hw_set_trap_gate(9, trap_addr[9]);
245   hw_set_trap_gate(10, trap_addr[10]);
246   hw_set_trap_gate(11, trap_addr[11]);
247   hw_set_trap_gate(12, trap_addr[12]);
248   hw_set_trap_gate(13, trap_addr[13]);
249   hw_set_irq_gate(14, trap_addr[14]);
250   hw_set_trap_gate(15, trap_addr[15]);
251   hw_set_trap_gate(16, trap_addr[16]);
252   hw_set_trap_gate(17, trap_addr[17]);
253   hw_set_trap_gate(18, trap_addr[18]);
254   hw_set_trap_gate(19, trap_addr[19]);
255 
256   // The XM's syscall interrupt
257   hw_set_sys_gate (0x82, SYSTEM_CALL_HANDLER_ASM(0x82));
258 
259   // The Root OS sycall, it can not be called when it not in execution
260   hw_set_sys_gate (0x80, INTERCEPT_SYSTEM_CALL_HANDLER_ASM(0x80));
261 
262   hw_restore_flags(hw_flags);
263 
264   return 0;
265 }

這裏我刪掉了源文件中的一些註釋,所以行號有點不太一樣。函數的開頭定義了兩個變量這兩個變量是返回值爲 void 的且參數爲 void 的一維函數指針數組。這裏以 trap_addr 爲例,它的值爲 __start_trap_handlers_addr 的地址。__start_trap_handlers_addr 這個變量有點隱蔽,不過也不難找,它在:

/* filename: include/i386/irqs.h */

167 #define TRAP_ADDR_TABLE_START() \
168   __asm__ (".section trap_handlers_addr,\"a\"\n\t" \
169            "__start_trap_handlers_addr:\n\t" \
170            ".previous\n\t");
171 
172 #define TRAP_ADDR_TABLE_END() \
173   __asm__ (".section trap_handlers_addr,\"a\"\n\t" \
174            "__end_trap_handlers_addr:\n\t" \
175            ".long -1\n\t" \
176            ".previous\n\t");

可以看出,XM 專門爲 i386 的 trap 處理函數定義了一個 section 叫做 trap_handlers_addr,這個 section 的標誌爲 a (a:允許段;w:可寫段;x:執行段)。那麼 XM 定義的所有 trap 處理函數都在 __start_trap_handlers_addr 和 __end_trap_handlers_addr 之間。另外,這裏的 .previous 僞指令是用來切換段的,表示恢復當前段的前一個段作爲當前段,有點搞不清楚在這裏爲什麼要這樣做,不過這不會影響對代碼的理解。那麼,是誰使用了 TRAP_ADDR_TABLE_START() 這個宏呢?

/* filename: arch/i386/kernel/irqs.c */

360 // Trap table
361 TRAP_ADDR_TABLE_START();
362 BUILD_TRAP_NOERRCODE(0x0);  BUILD_TRAP_NOERRCODE(0x1);
363 BUILD_TRAP_NOERRCODE(0x2);  BUILD_TRAP_NOERRCODE(0x3);
364 BUILD_TRAP_NOERRCODE(0x4);  BUILD_TRAP_NOERRCODE(0x5);
365 BUILD_TRAP_NOERRCODE(0x6);  BUILD_TRAP_NOERRCODE(0x7);
366 BUILD_TRAP_ERRCODE(0x8);    BUILD_TRAP_NOERRCODE(0x9);
367 BUILD_TRAP_ERRCODE(0xa);    BUILD_TRAP_ERRCODE(0xb);
368 BUILD_TRAP_ERRCODE(0xc);    BUILD_TRAP_ERRCODE(0xd);
369 BUILD_TRAP_ERRCODE(0xe);    BUILD_TRAP_NOERRCODE(0xf);
370 BUILD_TRAP_NOERRCODE(0x10); BUILD_TRAP_ERRCODE(0x11);
371 BUILD_TRAP_NOERRCODE(0x12); BUILD_TRAP_NOERRCODE(0x13);
372 BUILD_TRAP_ERRCODE(0x14); 	BUILD_TRAP_ERRCODE(0x15);
373 BUILD_TRAP_ERRCODE(0x16); 	BUILD_TRAP_ERRCODE(0x17);
374 BUILD_TRAP_ERRCODE(0x18); 	BUILD_TRAP_ERRCODE(0x19);
375 BUILD_TRAP_ERRCODE(0x1a); 	BUILD_TRAP_ERRCODE(0x1b);
376 BUILD_TRAP_ERRCODE(0x1c); 	BUILD_TRAP_ERRCODE(0x1d);
377 BUILD_TRAP_ERRCODE(0x1e); 	BUILD_TRAP_ERRCODE(0x1f);
378 TRAP_ADDR_TABLE_END();

在 TRAP_ADDR_TABLE_START() 和 TRAP_ADDR_TABLE_END() 之間有 32 個宏調用,包括 BUILD_TRAP_ERRCODE() 和 BUILD_TRAP_NOERRCODE(),其參數從 0 到 31,表示 32 個異常號。具體看看 BUILD_TRAP_ERRCODE(0x8):

/* filename: include/i386/irqs.h */

181 #define BUILD_TRAP_NAME(trapnr)  trap_##trapnr(void)
182 
183 #define BUILD_TRAP_ERRCODE(trapnr) \
184 asmlinkage void BUILD_TRAP_NAME(trapnr); \
185 __asm__ (".section trap_handlers_addr,\"a\"\n\t" \
186          ".align 4\n\t" \
187          ".long "SYMBOL_NAME_STR(trap_) #trapnr "\n\t" \
188          ".text\n\t" \
189          "\n" __ALIGN_STR"\n\t" \
190          SYMBOL_NAME_STR(trap_) #trapnr ":\n\t" \
191          "cld\n\t" \
192          HW_SAVE_ALL \
193          "pushl $"#trapnr"\n\t" \
194          "call " SYMBOL_NAME_STR(trap_handler) "\n\t" \
195          "addl $4,%esp\n\t" /* popl trapnr */ \
196          "testl %eax,%eax\n\t" \
197          "popl %ebx\n\t" \
198          "popl %ecx\n\t" \
199          "popl %edx\n\t" \
200          "popl %esi\n\t" \
201          "popl %edi\n\t" \
202          "popl %ebp\n\t" \
203          "jnz 1f\n\t" \
204          "popl %eax\n\t" \
205          "popl %ds\n\t"  \
206          "popl %es\n\t" \
207          "addl $4,%esp\n\t" /* popl error code */ \
208          "iret\n" \
209          "1:\n\t" \
210          "movl ("SYMBOL_NAME_STR(root_idt_table + 4 * trapnr)"),%eax\n\t" \
211          "mov 8(%esp),%es\n\t" \
212          "movl %eax,8(%esp)\n\t" \
213          "popl %eax\n\t" \
214          "popl %ds\n\t" \
215          "ret\n\t")

在這個內聯彙編的開頭首先調用 trap_8(),trap_8() 定義在下面的代碼中所以,trap_addr[0x8] 的值應該就是 trap_8() 的首地址。然後 185 行也定義了 trap_handlers_addr 這個 section,那麼最終鏈接的時候,這些 trap_handlers_addr 都會被鏈接到同一個 section 中。187 行使用 .long 指令創建了一個 32 位的變量 trap_#trapnr (這裏的代碼貌似有問題:問題在於連接符 # 和 ##,這裏得到的結果好像是 trap_trapnr 而不是類似於 trap_8 這樣的?),所以 184 行的 trap_8() 函數就從這裏開始。SYMBOL_NAME_STR(x) 被定義爲 "#x",也就是連接上前邊的字符串。接着 192 行調用 HW_SAVE_ALL 來把所有的寄存器的值保存到棧中。193 行異常號入棧。194 行調用 trap_handler 函數。trap_handler() 定義在:

/* filename: arch/i386/kernel/irqs.c */

136 int trap_handler (int trap, struct pt_regs regs) {
137   if (xm_current_domain -> events -> trap_handler [trap])
138     (*xm_current_domain -> events -> trap_handler [trap]) (trap, &s);
139   
140   // return 1 if the root trap handler must to be executed
141   return (xm_current_domain == xm_root_domain);
142 }

如果對應的異常處理函數存在,就執行異常處理函數。如果是根域,返回 1,非根域返回 0。trap_handler() 函數的返回值存放在 eax 中。
接着 testl 指令測試 eax 的值是否爲 1,如果爲 1,則跳轉到 210 行處,否則繼續執行。在跳轉之前,還原堆棧中的某些寄存器的值,也就是第 195、197 ~ 202 行。210 ~ 214 行將以 (root_idt_table+4*trapnr) 爲地址的值保存到棧底 (也就是將第 root_idt_table+4*trapnr 項的函數地址保存到棧底),然後恢復堆棧剩餘寄存器的值。最後 ret 指令返回到調用 trap_8() 的地方。至於 ERRCODE 和 NOERRCODE 是因爲 CPU 在處理不同的異常時,有的會有錯誤碼產生,而有的沒有,有錯誤碼的 CPU 會自動將錯誤碼壓棧,大概應該是這個區別。

對於 irq_addr,情形差不多也是這樣的,它使用宏 BUILD_IRQ() 和 BUILD_COMMON_IRQ_BODY():

/* filename: include/i386/irqs.h */

125 #define IRQ_NAME(irq) irq_handler_##irq(void)
126 
127 #define BUILD_IRQ(irq) \
128 asmlinkage void IRQ_NAME(irq); \
129   __asm__ (".section irq_handlers_addr,\"a\"\n\t" \
130            ".align 4\n\t" \
131            ".long "SYMBOL_NAME_STR(irq_handler_) #irq "\n\t" \
132            ".text\n\t" \
133            "\n"__ALIGN_STR"\n" \
134            SYMBOL_NAME_STR(irq_handler_) #irq ":\n\t" \
135            "pushl $"#irq"-256\n\t" \
136            "jmp " SYMBOL_NAME_STR(common_irq_body) "\n\t")
137   
138 #define BUILD_COMMON_IRQ_BODY() \
139   __asm__ (".text\n\t" \
140            "\n" __ALIGN_STR"\n" \
141            SYMBOL_NAME_STR(common_irq_body) ":\n\t" \
142            "cld\n\t" \
143            HW_SAVE_ALL \
144            "call " SYMBOL_NAME_STR(irq_handler) "\n\t" \
145            "testl %eax,%eax\n\t" \
146            "jnz  1f\n\t" \
147            HW_RESTORE_ALL \
148            "1: cld\n\t" \
149            "jmp *(" SYMBOL_NAME_STR(XM_root_func) " + 20)\n")

若此時 irq = 0x0,那麼和前邊的 trap 一樣,irq_addr[0x0] 的值應該是 irq_handler_0() 的首地址,而函數的函數體定義在後面的內聯彙編中。irq_handler_0() 先將中斷號入棧 (內核用負數表示外部中斷)。然後跳轉到 common_irq_body。也就是 BUILD_COMMON_IRQ_BODY() 中的第 142 行。接着調用 irq_handler() 函數。

/* filename: arch/i386/kernel/irqs.c */

115 int irq_handler (struct pt_regs regs) {
116   int irq = regs.orig_eax & 0xff;
117   int execute_root_ret_from_intr;
118   
119   hw_xpic[irq] -> ack (irq);
120   if (irq != hwtimer.timer_event) {
121     set_bit (xm_domain_list -> events -> pending_events, irq);
122   } else {
123     timer_handler ();
124     hw_xpic[irq] -> end (irq);
125   }
126   // xm_sched is called to execute the suitable handlers
127   // it returns "1" if the root irq handler has been executed
128   execute_root_ret_from_intr = 
129     (xm_sched () && (xm_current_domain == xm_root_domain));
130 
131   //hw_xpic[irq] -> end (irq);
132 
133   return execute_root_ret_from_intr;
134 }

irq_handler() 中 116 行首先得到此次外部中斷的中斷號。然後執行相應的外部中斷在系統中原來的中斷相應函數,這個地方由於 hw_xpic 數組在後面的代碼中初始化,但是這個地方不需要判斷 ack() 函數是否爲空嗎?並且爲空時,這樣寫不會出現訪問內存的錯誤嗎?接着判斷如果發生的時鐘定時中斷,就需要設置域鏈表的 pending 事件,以用於後來的域輪詢。如果是其他類型的外部中斷,就先調用 timer_handler() 來更新定時器的值。並調用 end() 來結束中斷響應。下面判斷是否是執行的根域的中斷處理函數,如果是就返回 1,這裏調用 xm_sched() 來做事件輪詢和中斷處理。這裏執行完以後,回到 BUILD_COMMON_IRQ_BODY() 的第 145 行,判斷 irq_handler() 的返回值,如果爲 1,則跳轉到 148 行。148 行的 jmp 指令又調用 XM_root_func 的 __ret_from_intr(),而 __ret_from_intr() 實際上就是 x86 原生的 ret_from_intr() 函數,來完成中斷的返回操作。


hw_irq_takeover() 中第 201 行得到系統中原來的 IDT 的地址,保存到 real_idt_table 中。205 ~ 216 行備份,並設置新的 XM_root_func。接下來 218 行的 for 循環將系統中原來的 IDT 的所有入口地址保存到 root_idt_table 數組中。接着 222 行的 for 循環備份並設置新的中斷操作。然後 229 行的第三個 for 循環從 IDT 的第 32 項開始設置設置新的入口地址,前 32 項是 x86 處理器保留的系統異常。最後 236 ~ 262 設置新的異常處理函數,和 XM 的系統調用 0x82,以及 x86 的系統調用 0x80。hw_set_***_gate() 都使用宏 hw_set_gate(),這個宏在 x86 中斷初始化 中有類似的操作。



但是對於不同的架構,中斷的接管有很大的不同。例如,在 PowerPC 中,貌似沒有 IDT 這個東西,它使用 IOPR 和 IOVRx 寄存器來設置。在 PowerPC 中,系統調用也不是 0x80。對於一些處理器保留的中斷,貌似沒必要進行替換,因爲這樣的異常往往是具有重大問題的(例如,除零,缺頁等),這些交給 Linux 處理就好了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章