接下来,我们看看linux首次进入的保护模式的内存映射方式,然后再看一下linux是如何实现保护模式的进入,进入前做了什么准备和设置。

&nbsp; &nbsp;&nbsp;还是借用<span style="-ms-word-wrap: break-word;">Intel</span>文档中的图来说明这个保护模式的保护功能:

&nbsp;

&nbsp; &nbsp;&nbsp;根据不同的段寄存器内容查找到对应的段描述符,描述符指明了此时的环境的可以通过段访问到内存基地址、空间大小和访问权限。访问权限则点明了哪些内存可读、哪些内存可写。这就是保护模式了。不过实际上,<span style="-ms-word-wrap: break-word;">linux</span>并不使用段保护,待会儿会有说明的。

&nbsp; &nbsp;&nbsp;那么此保护模式是如何做地址映射的呢?再借用<span style="-ms-word-wrap: break-word;">Intel</span>文档的图片来说明:

&nbsp;

&nbsp; &nbsp;&nbsp;可以看到逻辑地址还是那样表现方式:&ldquo;段选择:地址偏移&rdquo;,现在的段寄存器值真实地起到了选择符的作用了,通过<span style="-ms-word-wrap: break-word;">GDTR</span>寄存器找到段描述符表,根据段选择符找到对应的表项,结合描述符里的基地址(<span style="-ms-word-wrap: break-word;">Base Address</span>)加上地址偏移(<span style="-ms-word-wrap: break-word;">Offset</span>),最终得到了线性地址。此时的线性地址就确切地是物理地址了,因为没有开启页式映射。<span style="line-height: 1.5; -ms-word-wrap: break-word;">那么我们可以推测一下如果要切换到该保护模式需要做什么:</span>

1、 要有一个段描述符表;

2、 GDTR指向该描述符表首地址;


3、 设置段寄存器以便查找段描述符;


4、 开启保护模式功能;
&nbsp; &nbsp;&nbsp;接下来我们看一下<span style="-ms-word-wrap: break-word;">linux</span>代码吧,进入保护模式的函数<span style="-ms-word-wrap: break-word;">go_to_protected_mode:</span>


1. 【file:/arch/x86/boot/pm.c】
2.  
3. /
4.   Actual invocation sequence
5.  /
6. void go_to_protected_mode(void)
7. {
8.     / Hook before leaving real mode, also disables interrupts /
9.     realmode_switch_hook();
10.  
11.     / Enable the A20 gate /
12.     if (enable_a20()) {
13.         puts("A20 gate not responding, unable to boot…\n");
14.         die();
15.     }
16.  
17.     / Reset coprocessor (IGNNE#) /
18.     reset_coprocessor();
19.  
20.     / Mask all interrupts in the PIC /
21.     mask_all_interrupts();
22.  
23.     / Actual transition to protected mode... */
24.     setup_idt();
25.     setup_gdt();
26.     protected_mode_jump(boot_params.hdr.code32_start,
27.                 (u32)&boot_params + (ds() << 4));
28. }
&nbsp; &nbsp;&nbsp;里面的函数略带一下吧,<span style="-ms-word-wrap: break-word;">realmode_switch_hook()</span>根据注释和函数命名可以知道这是在实模式切换前的钩子函数调用的地方;<span style="-ms-word-wrap: break-word;">enable_a20()</span>这个太熟悉了,就开启<span style="-ms-word-wrap: break-word;">A20</span>;<span style="-ms-word-wrap: break-word;">reset_coprocessor()</span>是把协处理器重置一下;<span style="-ms-word-wrap: break-word;">mask_all_interrupts()</span>则是把中断关了,避免切换过程中出现状况。

&nbsp; &nbsp;&nbsp;<span style="line-height: 1.5; -ms-word-wrap: break-word;">好,详细说一下</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">setup_idt()</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">和</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">setup_gdt()</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">,函数名字告诉我们这是设置</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">idt</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">和</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">gdt</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">的,看一下两者具体代码吧:</span>


1. 【file:/arch/x86/boot/pm.c】
2. /
3.   Set up the IDT
4.  */
5. static void setup_idt(void)
6. {
7.     static const struct gdt_ptr null_idt = {0, 0};
8.     asm volatile("lidtl %0" : : "m" (null_idt));
9. }
&nbsp; &nbsp;&nbsp;根据<span style="-ms-word-wrap: break-word;">setup_idt()</span>的实现,可以明显看到这没做什么,纯粹置一下<span style="-ms-word-wrap: break-word;">idt</span>为空的描述符表。


1. 【file:/arch/x86/boot/pm.c】
2.  
3. static void setup_gdt(void)
4. {
5.     / There are machines which are known to not boot with the GDT
6.        being 8-byte unaligned. Intel recommends 16 byte alignment. /
7.     static const u64 boot_gdt[] attribute((aligned(16))) = {
8.         / CS: code, read/execute, 4 GB, base 0 /
9.         [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
10.         / DS: data, read/write, 4 GB, base 0 /
11.         [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
12.         / TSS: 32-bit tss, 104 bytes, base 4096 /
13.         / We only have a TSS here to keep Intel VT happy;
14.            we don't actually use it for anything. /
15.         [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
16.     };
17.     / Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
18.        of the gdt_ptr contents. Thus, make it static so it will
19.        stay in memory, at least long enough that we switch to the
20.        proper kernel GDT. /
21.     static struct gdt_ptr gdt;
22.  
23.     gdt.len = sizeof(boot_gdt)-1;
24.     gdt.ptr = (u32)&boot_gdt + (ds() << 4);
25.  
26.     asm volatile("lgdtl %0" : : "m" (gdt));
27. }
<span style="line-height: 1.5; -ms-word-wrap: break-word;">&nbsp; &nbsp; 而</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">setup_gdt()</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">则可以看到做了不少事情了。先看一下</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">GDT_ENTRY(0xc09b, 0, 0xfffff)</span><span style="line-height: 1.5; -ms-word-wrap: break-word;">做了什么转换,其结果是怎样的。</span>


1. / Constructor for a conventional segment GDT (or LDT) entry /
2. / This is a macro so it can be used in initializers /
3. #define GDT_ENTRY(flags, base, limit) \
4.     ((((base) & _AC(0xff000000,ULL)) << (56-24)) | \
5.      (((flags) & _AC(0x0000f0ff,ULL)) << 40) | \
6.      (((limit) & _AC(0x000f0000,ULL)) << (48-16)) | \
7.      (((base) & _AC(0x00ffffff,ULL)) << 16) | \
8.      (((limit) & _AC(0x0000ffff,ULL))))
<span style="text-align: left; color: rgb(102, 102, 102); text-transform: none; text-indent: 0px; letter-spacing: normal; font-family: 宋体, Arial; font-size: 16px; font-style: normal; font-weight: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; orphans: 2; widows: 2; background-color: rgb(255, 255, 255); font-variant-ligatures: normal; font-variant-caps: normal; -webkit-text-stroke-width: 0px;">&nbsp; &nbsp;&nbsp;这是一个</span><span style="text-align: left; color: rgb(102, 102, 102); text-transform: none; text-indent: 0px; letter-spacing: normal; font-family: 宋体, Arial; font-size: 16px; font-style: normal; font-weight: normal; word-spacing: 0px; white-space: normal; -ms-word-wrap: break-word; orphans: 2; widows: 2; background-color: rgb(255, 255, 255); font-variant-ligatures: normal; font-variant-caps: normal; -webkit-text-stroke-width: 0px;">64bit</span><span style="text-align: left; color: rgb(102, 102, 102); text-transform: none; text-indent: 0px; letter-spacing: normal; font-family: 宋体, Arial; font-size: 16px; font-style: normal; font-weight: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; orphans: 2; widows: 2; background-color: rgb(255, 255, 255); font-variant-ligatures: normal; font-variant-caps: normal; -webkit-text-stroke-width: 0px;">的数据,稍微算一下,这个转换后的值应该为:</span>

0x00CF9B000000FFFF

&nbsp; &nbsp;&nbsp;这就是段描述符表项的内容。我们对比一下段描述符的格式:

&nbsp;

&nbsp; &nbsp;&nbsp;稍微把数据和格式对齐一下:

&nbsp;

&nbsp; &nbsp;&nbsp;先看一下基地址是多少(黄色部分):

&nbsp;

&nbsp; &nbsp;&nbsp;是的,就是<span style="-ms-word-wrap: break-word;">0x00000000</span>。而段限长为<span style="-ms-word-wrap: break-word;">0xfffff</span>(绿色部分)<span style="line-height: 1.5; -ms-word-wrap: break-word;">:</span>

&nbsp;

&nbsp; &nbsp;&nbsp;由于基地址是<span style="-ms-word-wrap: break-word;">0x00000000</span>,所以逻辑地址的偏移量就是实际内存地址空间。所以也就说<span style="-ms-word-wrap: break-word;">linux</span>并不使用段保护<span style="-ms-word-wrap: break-word;">,</span>因为并没有明显地区分各个段(仔细看一下<span style="-ms-word-wrap: break-word;">GDT_ENTRY_BOOT_DS</span>表项的设置,其基地址同样是<span style="-ms-word-wrap: break-word;">0x00000000</span>)。

&nbsp; &nbsp;&nbsp;至于其他的位则分别用来表示该表项指向的段内存的访问权限了,具体此时段权限这里就不详细说明了,感兴趣的可以根据手册分析。以上的是<span style="-ms-word-wrap: break-word;">GDT_ENTRY_BOOT_CS</span>的情况,<span style="-ms-word-wrap: break-word;">GDT_ENTRY_BOOT_DS</span>同理,至于<span style="-ms-word-wrap: break-word;">GDT_ENTRY_BOOT_TSS</span>,注释也说了,这里用不到,何况现在还没有任务的概念,不可能会有什么所谓的任务切换出现。<span style="-ms-word-wrap: break-word;">OK</span>,理解完了段描述符,那么现在不难明白<span style="-ms-word-wrap: break-word;">GDT_ENTRY(flags, base, limit)</span>做了些什么,实际上就是在把各项数据设置到段描述符对应的位上面。继续往下看<span style="-ms-word-wrap: break-word;">setup_gdt()</span>的函数实现,可以看到<span style="-ms-word-wrap: break-word;">boot_gdt</span>是定义了段描述符表,然后设置<span style="-ms-word-wrap: break-word;">gdt</span>,最后通过汇编指令<span style="-ms-word-wrap: break-word;">lgdtl</span>把<span style="-ms-word-wrap: break-word;">gdt</span>的表项设置上去。至此段描述符表有了,而且随着<span style="-ms-word-wrap: break-word;">lgdtl</span>指令的执行,<span style="-ms-word-wrap: break-word;">GDTR</span>也随之设置上了。设置上去了,那是不是就生效了?不是的,还有关键的保护模式功能还没开启呢。

[http://blog.chinaunix.net/uid-26859697-id-4408666.html](http://blog.chinaunix.net/uid-26859697-id-4408666.html)