第三次启动保护模式

来源:互联网 发布:python ssh执行.sh 编辑:程序博客网 时间:2024/05/01 16:07

4.2.2 第三次启动保护模式

很多人就有疑问, 刚才我们设置好了中断描述符表,那内核怎么用它呢?不好意思,刚才在略过了的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_pagearch/x86/include/asm/desc.h定义:

 

  35

  36struct gdt_page {

  37        struct desc_struct gdt[GDT_ENTRIES];

  38} __attribute__((aligned(PAGE_SIZE)));

 

为什么需要重新加载一个全局描述符表很简单因为现在起到分页了所以要重新定位全局描述符表的位置,GDT的第三次段设置是在开启并设置了了页面寻址之后进行的。设置本身很简单,也是最终的设置,而且还同时设置了IDT而由于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工作模式” 8个字节,所以gdt表共占8*32=256个字节,相对于一个页面的4096字节,仅仅占了一个页面前1/16的空间。

 

回到426行,把我们刚才设置好的中断门描述符表idt_descr通过lidt加载上,然后427~436行分别将段选择子__KERNEL_CS__KERNEL_DS__USER_DS__KERNEL_PERCPU加载到寄存器csssds/esfs。注意这里一个细节,加载cs寄存器重来都不使用mov指令,而是通过ljmp $(__KERNEL_CS),$1f来设置。还有,这时候,gdt是早已初始化了的。

 

那么,这个gdt是在哪里初始化的呢,请看arch/x86/kernel/cpu/common.c文件:

 

86DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page) = { .gdt = {

87#ifdef CONFIG_X86_64

88       /*

89        * We need valid kernel segments for data and code in long mode too

90        * IRET will check the segment types  kkeil 2000/10/28

91        * Also sysret mandates a special GDT layout

92        *

93        * TLS descriptors are currently at a different place compared to i386.

94        * Hopefully nobody expects them at a fixed place (Wine?)

95        */

96        [GDT_ENTRY_KERNEL32_CS]  = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),

97        [GDT_ENTRY_KERNEL_CS]    = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),

98        [GDT_ENTRY_KERNEL_DS]    = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),

99        [GDT_ENTRY_DEFAULT_USER32_CS] = GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),

100       [GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),

101       [GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),

102#else

103       [GDT_ENTRY_KERNEL_CS]      = GDT_ENTRY_INIT(0xc09a, 0, 0xfffff),

104       [GDT_ENTRY_KERNEL_DS]      = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),

105       [GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xc0fa, 0, 0xfffff),

106       [GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),

107       /*

108        * Segments used for calling PnP BIOS have byte granularity.

109        * They code segments and data segments have fixed 64k limits,

110        * the transfer segment sizes are set at run time.

111        */

112       /* 32-bit code */

113       [GDT_ENTRY_PNPBIOS_CS32]   = GDT_ENTRY_INIT(0x409a, 0, 0xffff),

114       /* 16-bit code */

115       [GDT_ENTRY_PNPBIOS_CS16]   = GDT_ENTRY_INIT(0x009a, 0, 0xffff),

116       /* 16-bit data */

117       [GDT_ENTRY_PNPBIOS_DS]     = GDT_ENTRY_INIT(0x0092, 0, 0xffff),

118       /* 16-bit data */

119       [GDT_ENTRY_PNPBIOS_TS1]    = GDT_ENTRY_INIT(0x0092, 0, 0),

120       /* 16-bit data */

121       [GDT_ENTRY_PNPBIOS_TS2]    = GDT_ENTRY_INIT(0x0092, 0, 0),

122       /*

123        * The APM segments have byte granularity and their bases

124        * are set at run time.  All have 64k limits.

125        */

126       /* 32-bit code */

127      [GDT_ENTRY_APMBIOS_BASE]   = GDT_ENTRY_INIT(0x409a, 0, 0xffff),

128        /* 16-bit code */

129     [GDT_ENTRY_APMBIOS_BASE+1]  = GDT_ENTRY_INIT(0x009a, 0, 0xffff),

130      /* data */

131     [GDT_ENTRY_APMBIOS_BASE+2]  = GDT_ENTRY_INIT(0x4092, 0, 0xffff),

132

133     [GDT_ENTRY_ESPFIX_SS]         = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),

134     [GDT_ENTRY_PERCPU]           = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),

135     GDT_STACK_CANARY_INIT

136#endif

137} };

 

首先讲讲DEFINE_PER_CPU_PAGE_ALIGNEDinclude/linux/percpu-defs.h

#define DEFINE_PER_CPU_PAGE_ALIGNED(type, name)                         /

       DEFINE_PER_CPU_SECTION(type, name, ".page_aligned")          /

       __aligned(PAGE_SIZE)

 

#define DECLARE_PER_CPU_SECTION(type, name, sec)                    /

       extern __PCPU_ATTRS(sec) __typeof__(type) name

 

#define __PCPU_ATTRS(sec)                                          /

       __percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) /

       PER_CPU_ATTRIBUTES

 

展开就是

       extern __percpu __attribute__((section(PER_CPU_BASE_SECTION .page_aligned)))    /

       PER_CPU_ATTRIBUTES __typeof__(struct gdt_page) gdt_page __aligned(PAGE_SIZE)

 

看到了吗?在编译阶段gdt[GDT_ENTRIES]就被编译成了一个常量,在32x86体系中其值为103~135行的代码,将这个常量最后链接进PER_CPU_BASE_SECTION中。

 

另外,GDT_ENTRY_INITarch/x86/include/asm/desc_defs.h中定义

#define GDT_ENTRY_INIT(flags, base, limit) { { { /

              .a = ((limit) & 0xffff) | (((base) & 0xffff) << 16), /

              .b = (((base) & 0xff0000) >> 16) | (((flags) & 0xf0ff) << 8) | /

                     ((limit) & 0xf0000) | ((base) & 0xff000000), /

 

所以经过编译链接以后gdt就包含14个全局描述符其中

#define GDT_ENTRY_KERNEL_BASE     12

#define GDT_ENTRY_KERNEL_CS          (GDT_ENTRY_KERNEL_BASE + 0)

表示内核代码段的索引,即gdt[12]为代码段描述符。

#define GDT_ENTRY_KERNEL_DS          (GDT_ENTRY_KERNEL_BASE + 1)

表示内核数据段的索引,即gdt[13]为数据段描述符。

#define GDT_ENTRY_DEFAULT_USER_CS     14

表示用户代码段的索引,即gdt[14]为用户代码段描述符。

#define GDT_ENTRY_DEFAULT_USER_CS     14

表示用户数据段的索引,即gdt[15]为用户数据段描述符。

#define GDT_ENTRY_PNPBIOS_BASE            (GDT_ENTRY_KERNEL_BASE + 6)

#define GDT_ENTRY_PNPBIOS_CS32             (GDT_ENTRY_PNPBIOS_BASE + 0)

#define GDT_ENTRY_PNPBIOS_CS16             (GDT_ENTRY_PNPBIOS_BASE + 1)

#define GDT_ENTRY_PNPBIOS_DS         (GDT_ENTRY_PNPBIOS_BASE + 2)

#define GDT_ENTRY_PNPBIOS_TS1        (GDT_ENTRY_PNPBIOS_BASE + 3)

#define GDT_ENTRY_PNPBIOS_TS2        (GDT_ENTRY_PNPBIOS_BASE + 4)

gdt[18]gdt[19]gdt[20]gdt[21]gdt[22]分别表示PNP BIOS32位代码段、16位代码段、数据段、两个任务段

#define GDT_ENTRY_APMBIOS_BASE           (GDT_ENTRY_KERNEL_BASE + 11)

gdt[23]gdt[24]gdt[25]分别表示高级电源管理APM32位代码段、16位代码段和数据段。

#define GDT_ENTRY_ESPFIX_SS            (GDT_ENTRY_KERNEL_BASE + 14)

gdt[26]表示堆栈修复段的段描述符。

#define GDT_ENTRY_PERCPU                 (GDT_ENTRY_KERNEL_BASE + 15)

gdt[26]表示每CPU段的段描述符。

 

最后由于我们的.config没有定义CONFIG_CC_STACKPROTECTOR,所以GDT_STACK_CANARY_INIT就为空。所以,Linux启动以来自此加载的gdt已有以上若干个段的描述符在编译vmlinux的时候初始化了,其他没被初始化的地方暂时保留。

 

我们知道Linux x86的分段管理是通过GDTR来实现的,那么现在就来总结一下Linux启动以来到现在,共设置了几次GDTR

1.    第一次还是cpu处于实模式的时候,运行arch/x86/boot/pm.csetup_gdt()函数的代码。该函数,设置了两个GDT项,一个是代码段可读/执行的,另一个是数据段可读写的,都是从0-4G直接映射到0-4G,也就是虚拟地址和线性地址相等。

2.    第二次是在内核解压缩以后,用解压缩后的内核代码arch/x86/kernel/head_32.S再次对gdt进行设置,这一次的设置效果和上一次是一样的。

3.    第三次同样是在arch/x86/kernel/head_32.S中,只不过是在开启了页面寻址之后,通过分页寻址得到编译好的全局描述符表gdt的地址。这一次效果就跟前两次不一样了,为内核最终使用的全局描述符表,同时也设置了IDT

 

 

 

原创粉丝点击