关于 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 处理就好了。

你可能感兴趣的:(timer,vector,table,domain,Build,events)