Chinaunix首页 | 论坛 | 博客
  • 博客访问: 323677
  • 博文数量: 28
  • 博客积分: 609
  • 博客等级: 中士
  • 技术积分: 925
  • 用 户 组: 普通用户
  • 注册时间: 2011-02-17 13:32
文章分类

全部博文(28)

文章存档

2022年(1)

2016年(1)

2015年(2)

2012年(1)

2011年(23)

分类: LINUX

2011-02-28 11:40:54

现在就让我们看一下到底是怎么进入保护模式。
  1. void go_to_protected_mode(void)
  2. {
  3.     /* Hook before leaving real mode, also disables interrupts */
  4.     realmode_switch_hook();

  5.     /* Enable the A20 gate */
  6.     if (enable_a20()) {
  7.         puts("A20 gate not responding, unable to boot...\n");
  8.         die();
  9.     }

  10.     /* Reset coprocessor (IGNNE#) */
  11.     reset_coprocessor();

  12.     /* Mask all interrupts in the PIC */
  13.     mask_all_interrupts();

  14.     /* Actual transition to protected mode... */
  15.     setup_idt();
  16.     setup_gdt();
  17.     protected_mode_jump(boot_params.hdr.code32_start,
  18.              (u32)&boot_params + (ds() << 4));
  19. }
1. 首先需要执行由bootloader指定的hook。
  1. /*
  2.  * Invoke the realmode switch hook if present; otherwise
  3.  * disable all interrupts.
  4.  */
  5. static void realmode_switch_hook(void)
  6. {
  7.     if (boot_params.hdr.realmode_swtch) {
  8.         asm volatile("lcallw *%0"
  9.              : : "m" (boot_params.hdr.realmode_swtch) //hook由bootloader指定,并不是所有bootloader都会指定hook。详细的可以参见Documentation/x86/boot.txt里的Advanced Boot Loader Hooks
  10.              : "eax", "ebx", "ecx", "edx");
  11.     } else {
  12.         asm volatile("cli");
  13.         outb(0x80, 0x70); /* Disable NMI */ //NMI的具体介绍可以参见http://wiki.osdev.org/Non_Maskable_Interrupt
  14.         io_delay();
  15.     }
  16. }
2. 然后就需要把A20打开。A20是x86系统里的第21根地址线,它存在于80286以后的CPU。打开A20就能在实模式下访问最大至16M的地址空间。CPU进入保护模式必须把A20打开。

3. reset_coprocessor(),显然就是重置协处理器。实际是向0xf0和0xf1都写入0。没有查到0xf0和0xf1具体的定义。不好多说些什么。

4. mask_all_interrupts(),很显然,由于马上要进入保护模式了,一切的中断都屏蔽掉。

5. setup_idt(),将idt初始化为0。即当前不使用IDT.

6. setup_gdt(),将初始化GDT和GDT里面的内容。其代码如下:
  1. static void setup_gdt(void)
  2. {
  3.     /* There are machines which are known to not boot with the GDT
  4.      being 8-byte unaligned. Intel recommends 16 byte alignment. */
  5.     static const u64 boot_gdt[] __attribute__((aligned(16))) = {
  6.         /* CS: code, read/execute, 4 GB, base 0 */
  7.         [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
  8.         /* DS: data, read/write, 4 GB, base 0 */
  9.         [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
  10.         /* TSS: 32-bit tss, 104 bytes, base 4096 */
  11.         /* We only have a TSS here to keep Intel VT happy;
  12.          we don't actually use it for anything. */
  13.         [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
  14.     };
  15.     /* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
  16.      of the gdt_ptr contents. Thus, make it static so it will
  17.      stay in memory, at least long enough that we switch to the
  18.      proper kernel GDT. */
  19.     static struct gdt_ptr gdt;

  20.     gdt.len = sizeof(boot_gdt)-1;
  21.     gdt.ptr = (u32)&boot_gdt + (ds() << 4);

  22.     asm volatile("lgdtl %0" : : "m" (gdt));
  23. }
首先我们需要了解的是GDT entry的结构。

A GDT Entry

GDT Bits

GDT_ENTRY的定义如下:
  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) & 0xff000000ULL) << (56-24)) |    \
  5.      (((flags) & 0x0000f0ffULL) << 40) |        \
  6.      (((limit) & 0x000f0000ULL) << (48-16)) |    \
  7.      (((base) & 0x00ffffffULL) << 16) |        \
  8.      (((limit) & 0x0000ffffULL)))
可以清楚得看到,base, limit和flag通过位移和或组成了GDT_ENTRY。其中flags代表了40-47位的access byte和52-55位的flags。
CS和DS的Flags=0xc0, 所以Gr=1(4K为一个页面) Sz=1(该内存段是保护模式下可访问)
CS的Access Byte = 0x9b,意味着Pr=1(合法的Entry Pr必须为1), Privl=0(ring=0)Ex=1(该段可执行),DC=0(该段只能在Privl设定的级别代码访问,这里该段只能在Ring 0下访问),RW=1(该段是代码段,所以RW=1指明该段可读)
DC的Access Byte=0x93,意味着Pr=1(合法的Entry Pr必须为1), Privl=0(ring=0)Ex=0(该段为数据段),DC=0(这是数据段,DC指明该段以低地址段为起点,高地址方向是段地址增长方向)RW=1(该段是数据段,RW=1指明该段为可写)

7. 接下来就跳转到protected_mode_jump(在pmjump.S里)这段汇编代码里面去。传入两个参数。
第一个是参数是hdr.code32_start。这个参数缺省由编译器产生,指向0x100000即1M的位置,kernel的32位保护模式启动代码就在那里。当然bootloader可以改变这个值以指向bootloader的hook,或者bootloader没有把code32_start定位在0x100000的位置。
第二个参数是(u32)&boot_params + (ds() << 4),实际上是把boot_params在实模式下的地址转换到保护模式的地址。因为CS和DS指的都是从0到4G的地址空间,所以这样的转换就足够了。

有一个问题是我们在前面讨论的时候没有涉及到的,那就是bootloader怎么找到32位启动代码的?我们可以看一下arch/x86/boot/Makefile. 我们会发现kernel实际由setup.bin和vmlinux.bin组成。而vmlinux.bin实际上在arch/x86/boot/compressed下面。打开vmlinux.lds.S,你可以看到以下定义
  1. #ifdef CONFIG_X86_64
  2. OUTPUT_ARCH(i386:x86-64)
  3. ENTRY(startup_64)
  4. #else
  5. OUTPUT_ARCH(i386)
  6. ENTRY(startup_32)  //启动入口是startup_32即code32_start
  7. #endif

  8. SECTIONS
  9. {
  10.     /* Be careful parts of head_64.S assume startup_32 is at
  11.      * address 0.
  12.      */
  13.     . = 0; //从相对地址0开始,由于bootloader一般会把32位保护模式代码装载到0x100000的位置所以启动的入口就在这里。汇编代码在header_32.S中
  14.     .head.text : {
  15.         _head = . ;
  16.         HEAD_TEXT
  17.         _ehead = . ;
  18.     }
  19.     .rodata..compressed : { //压缩过的kernel代码
  20.         *(.rodata..compressed)
  21.     }
  22.     .text :    { //数据段
  23.         _text = .;     /* Text */
  24.         *(.text)
  25.         *(.text.*)
  26.         _etext = . ;
  27.     }
  28.     .rodata : {
  29.         _rodata = . ;
  30.         *(.rodata)     /* read-only data */
  31.         *(.rodata.*)
  32.         _erodata = . ;
  33.     }
  34.     .got : {
  35.         _got = .;
  36.         KEEP(*(.got.plt))
  37.         KEEP(*(.got))
  38.         _egot = .;
  39.     }
  40.     .data :    {
  41.         _data = . ;
  42.         *(.data)
  43.         *(.data.*)
  44.         _edata = . ;
  45.     }
  46.     . = ALIGN(L1_CACHE_BYTES);
  47.     .bss : {
  48.         _bss = . ;
  49.         *(.bss)
  50.         *(.bss.*)
  51.         *(COMMON)
  52.         . = ALIGN(8);    /* For convenience during zeroing */
  53.         _ebss = .;
  54.     }
  55. #ifdef CONFIG_X86_64
  56.        . = ALIGN(PAGE_SIZE);
  57.        .pgtable : {
  58.         _pgtable = . ;
  59.         *(.pgtable)
  60.         _epgtable = . ;
  61.     }
  62. #endif
  63.     _end = .;
  64. }
所以bootloader只要找到startup_32就知道哪里是32位保护模式代码了。直接把从startup32开始的kernel文件剩余部分装载进内存就好了。

明天我们将真正地走到保护模式,又一次进入汇编的世界。
阅读(4556) | 评论(0) | 转发(5) |
给主人留下些什么吧!~~