4.2 向start_kernel进发
分段、分页单元完成以后,我们的0号进程的运行环境就初步搭建起来了,那么就要“走向现代”,去调用start_kernel了,调用它之前还有几个重要的步骤要走。
4.2.1 加载全局/中断描述符表
很多人就有疑问, 刚才我们设置好了中断描述符表,那内核怎么用它呢?不好意思,刚才在略过了的checkCPUtype过程中有一步非常重要的步骤,现在把它补上:
418 is386: movl $2,%ecx # set MP
419 2: movl %cr0,%eax
420 andl $0x80000011,%eax # Save PG,PE,ET
421 orl %ecx,%eax
422 movl %eax,%cr0
423
424 call check_x87
425 lgdt early_gdt_descr
426 lidt idt_descr
427 ljmp $(__KERNEL_CS),$1f
428 1: movl $(__KERNEL_DS),%eax # reload all the segment registers
429 movl %eax,%ss # after changing gdt.
430
431 movl $(__USER_DS),%eax # DS/ES contains default USER segment
432 movl %eax,%ds
433 movl %eax,%es
434
435 movl $(__KERNEL_PERCPU), %eax
436 movl %eax,%fs # set this cpu's percpu
425行重新加载一个全局描述符表,early_gdt_descr:
707 ENTRY(early_gdt_descr)
708 .word GDT_ENTRIES*8-1
709 .long gdt_page /* Overwritten for secondary CPUs */
同样也是包含段限和全局描述符表地址,gdt_page在arch/x86/include/asm/desc.h定义:
35
36struct gdt_page {
37 struct desc_struct gdt[GDT_ENTRIES];
38} __attribute__((aligned(PAGE_SIZE)));
为什么需要重新加载一个全局描述符表?很简单,因为现在起到分页了,所以,要重新定位全局描述符表的位置,而由于aligned(PAGE_SIZE),所以gdt_page正好是一个页面的开始位置。GDT_ENTRIES的值是32,定义在arch/x86/include/asm/segment.h:
110#define GDT_ENTRIES 32
所以全局描述符表式每个表项为desc_struct结构的,32个表项的数组,它的每个表项是:
22struct desc_struct {
23 union {
24 struct {
25 unsigned int a;
26 unsigned int b;
27 };
28 struct {
29 u16 limit0;
30 u16 base0;
31 unsigned base1: 8, type: 4, s: 1, dpl: 2, p: 1;
32 unsigned limit: 4, avl: 1, l: 1, d: 1, g: 1, base2: 8;
33 };
34 };
35} __attribute__((packed));
正好看到第二个联合体,熟悉吧,描述符结构。哦,你不熟悉,那就看看博客“Intel 80286工作模式” http://blog.csdn.net/yunsongice/archive/2010/10/04/5920447.aspx。8个字节,所以gdt表共占8*32=256个字节,相对于一个页面的4096字节,仅仅占了一个页面前1/16的空间。
回到426行,把我们刚才设置好的中断门描述符表idt_descr通过lidt加载上,然后427~436行分别将段选择子__KERNEL_CS、__KERNEL_DS、__USER_DS、__KERNEL_PERCPU加载到寄存器cs、ss、ds/es、fs。注意这里一个细节,加载cs寄存器重来都不使用mov指令,而是通过ljmp $(__KERNEL_CS),$1f来设置。还有,这时候,gdt是早已初始化了的。