Chinaunix首页 | 论坛 | 博客
  • 博客访问: 345388
  • 博文数量: 88
  • 博客积分: 2011
  • 博客等级: 大尉
  • 技术积分: 885
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-21 14:50
文章分类

全部博文(88)

文章存档

2010年(88)

我的朋友

分类: LINUX

2010-05-25 20:28:17

./arch/i386/kernel/head.S
本文记录head.S的一个主要流程以及关键点,以前看head.S写的点文档,现在贴到博客上来,代码是2.4的,有点老,但是原理基本上差不多。

1. 首先用_KERNEL_DS初始化ds,es,fs,gs四个寄存器,这里_KERNEL_DS的值为0x18,此值为段选择子的值,表示在GDT中,下表为3的段描述符,并且特权级为0;
  ENTRY(gdt_table)
     .quad 0x0000000000000000  
     .quad 0x0000000000000000
     .quad 0x0000000000000000
     .quad 0x00cf9a000000ffff  /* 0x10 kernel 4GB code */
     .quad 0x00cf92000000ffff  /* 0x18 kernel 4GB data */
     ...

2. 在0x102000,0x103000,0x104000处初始化2个页表(pg0,pg1)以及一个零页empty_zero_page,分别填充描述符0x7,01007,0x2007...,其实就是设置页表项的内容,从pg0开始,映射的物理地址为0x0,0x1000,0x2000...,后面的7标示用户页面,可写,页在内存中,页表占两个页面,所以可以映射8MB的物理地址(下面定义的地址为相对于程序本身的地址,内核映像加载在1M开始的地方)
.org 0x2000
      ENTRY(pg0)
      .org 0x3000
      ENTRY(pg1)
      .org 0x4000
      ENTRY(_empty_zero_page)
设置页目录首地址为0x101000,页目录用swapper_pg_dir表示,定义如下
      .org 0x1000
      ENTRY(swapper_page_dir)
      .long 0x00102007
      .long 0x00103007
      .fill BOOT_USER_PGD_PTRS-2,4,0
      /* default : 766 entries */
      .long 0x00102007
      .long 0x00103007
      .fill BOOT_KERNEL_PGD_PTRS-2,4,0
页目录是这样安排的,pg0,pg1,766个空项,pg0,pg1,254个空项,这样共1024项,这样安排主要是为了过渡:本来进入head.S的时候CPU是以物理地址取指令的,一旦分页开启,CPU的IP指针肯定还在低地址区,还会以物理地址取地址,一直到以某个符号地址为目标作绝对转移或者调用子程序为止,如果不为用户区作相同的映射,程序就没法执行下去了。

3. 设置CR3,CR0,开启分页模式
      movl $swapper_pg_dir-_PAGE_OFFSET.%eax
      movl %eax,%cr3 /* 为CR3装载内核页目录基址 */

      movl %cr0,%eax
      orl $0x80000000,%eax
      movl %eax,%cr0 /* cr0最高位置位,开启分页 */

这里_PAGE_OFFSET大小为3G,上面使用pg0,pg1的时候也需要减去_PAGE_OFFSET,其实这个操作就是获取相应的物理地址,内核地址和物理地址有3G的偏差,./arch/i386/vmlinux.lds(vmlinux.lds是gld的链接脚本,用于安排ld输出)中有如下定义:
      SECTIONS{
      . = 0xC0000000 + 0x100000 ; /* 段的偏移量 */
      ... ...
      }

4. 转入系统空间;
      jmp 1f
   1:
      movl $1f,%eax
      jmp *%eax
   1:
   这里有两次跳转:第一次跳转的距离很近,市个相对跳转,虽说CPU还是以物理地址取指令,但是可以起到丢弃已近在CPU的取指令流水线中内容的作用,从这里开始,CPU引用数据段的符号已经不需要在减去_PAGE_OFFSET的偏移了;第二次跳转时绝对跳转,*%eax是第二个标号1的绝对地址,这样以后EIP都取系统空间的指令了,CPU也就顺利切换到系统空间。

5. 初始化堆栈,设置SS段位_KERNEL_DS,ESP指针则定义在了一个stack[2048]数组的末尾处,其实这就是第一个进程的内核栈,这儿已经在为创建第一个进程做准备了。

6. 初始化内核的BSS段,其实就是吧从_bss_start开始到_end为止的区间清0。

7. 调用setup_idt服务子程序初始化中断向量表,中断向量表共256个表项,每项8个字节,初始状态256个表项都相同,都执行同一个中断响应程序ignore_int,这个函数吴实质性的中断处理,对于到来的中断只是在屏幕上打印"Unkonwn interrupt"。

8. 初始化标志寄存器,只要pushl $0,popf1就可以了。

9. 将setup.S中获取的引导参数信息移动到empty_zero_page的前2KB的地方,后2KB清0,用于存放命令行参数。

10. 检验CPU的类型并保存相关的参数。代码中要求CPU至少是386的,386和486可以通过检验EFLAGS的AC(18位,对其检查)位来区分,因为386还没有设置AC为;而486和Pentium则看ID位(21位,是否支持CPUID指令),486是不支持CPUID的;下面的处理器就可以通过CPUID指令在寄存器eax中返回CPU芯片的信息了。

11. 设置CPU的GDTR和IDTR,直接lgdt gdt_descr和lidt idt_descr就可以了;
     idt_descr:
          .word IDT_ENTRIES*8-1  /* 256 entries*/
     gdt_descr:
          .word GDT_ENTRIES*8-1

12. 重新加载所有的寄存器:CS为_KERNEL_CS,DS,ES,FS,GS都设置为_KERNEL_DS,SS还是和前面再设置下,LDT设为空,因为Linux不用;

13. 调用start_kernel进行更搞层次的初始化,satrt_kernel都是C初始化代码了。
阅读(1371) | 评论(0) | 转发(0) |
0

上一篇:排序算法小结

下一篇:堆排序实现

给主人留下些什么吧!~~