這裏分析的是 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 處理就好了。