第三次启动保护模式
来源:互联网 发布: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_page在arch/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加载到寄存器cs、ss、ds/es、fs。注意这里一个细节,加载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_ALIGNED宏,在include/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]就被编译成了一个常量,在32位x86体系中其值为103~135行的代码,将这个常量最后链接进PER_CPU_BASE_SECTION中。
另外,GDT_ENTRY_INIT宏,在arch/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 BIOS的32位代码段、16位代码段、数据段、两个任务段。
#define GDT_ENTRY_APMBIOS_BASE (GDT_ENTRY_KERNEL_BASE + 11)
gdt[23]、gdt[24]、gdt[25]分别表示高级电源管理APM的32位代码段、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.c下setup_gdt()函数的代码。该函数,设置了两个GDT项,一个是代码段可读/执行的,另一个是数据段可读写的,都是从0-4G直接映射到0-4G,也就是虚拟地址和线性地址相等。
2. 第二次是在内核解压缩以后,用解压缩后的内核代码arch/x86/kernel/head_32.S再次对gdt进行设置,这一次的设置效果和上一次是一样的。
3. 第三次同样是在arch/x86/kernel/head_32.S中,只不过是在开启了页面寻址之后,通过分页寻址得到编译好的全局描述符表gdt的地址。这一次效果就跟前两次不一样了,为内核最终使用的全局描述符表,同时也设置了IDT。
- 第三次启动保护模式
- 第一次启动保护模式
- 第二次启动保护模式
- Android启动保护模式实践
- 启动保护模式的程序
- [IE技巧] 使IE8以隐私保护模式启动
- Linux Kernel 2.6.37 启动过程:漫步进入保护模式
- 3.保护模式8-页式存储(启动分页机制)
- 在保护模式下启动分页机制,并且使…
- X86内核启动分析四 打开保护模式之门
- 保护模式
- 保护模式
- 保护模式
- 保护模式
- 保护模式
- 保护模式
- 保护模式
- 保护模式
- 加载全局/中断描述符表
- effective stl 读书笔记
- JQuery Plugins
- 用编译选项帮你挑错
- XML应用样例:Atom到RSS转换
- 第三次启动保护模式
- c#使用指针快速操作图片
- 2010过去,2011开始了
- 掌握Dojo工具包,第1部分:Dojo入门简介(1)
- VS2010能否取代VS6?微软能否再创辉煌!
- 启动x86虚拟机
- 尴尬的今天……
- 不会积分
- VS2010文本编辑器快捷键