./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) |