这里分析的是 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 处理就好了。