32位启动代码,暂时不考虑SMP的情况。关键代码分析
页目录表的起始地址在0x101000,由于目前仍然处于实模式,地址都是
物理地址
开始启动内核
startup_32:
清方向标志位
cld
用内核数据段的地址来初始化ds,es,fs,gs寄存器
宏__KERNEL_DS在segment.h中有定义,对于i386体系结构来说__KERNEL_DS=0x18
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
movl %eax,%fs
movl %eax,%gs
初始化页表,由于程序中实用的符号的地址都是虚拟地址,所以$pg0 - __PAGE_OFFSET就是pg0的物理地址
movl $pg0-__PAGE_OFFSET,%edi
页表项的值:该页在内存中,用户可写
movl $007,%eax
进行初始化页表
2: stosl
索引值加1
add $0x1000,%eax
初始化pg0和pg1两张表
cmp $empty_zero_page-__PAGE_OFFSET,%edi
jne 2b
3:
页目录表的物理地址:$swapper_pg_dir-__PAGE_OFFSET
起始于0x101000
movl $swapper_pg_dir-__PAGE_OFFSET,%eax
页目录表的物理地址存入cr3寄存器中
movl %eax,%cr3
开启分页机制,重置cr0控制寄存器
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0
这样做只是为了刷新指令流水线,486之后采用了2条流水线,保证操作数是虚拟地址
这样才能平稳过渡到保护模式
jmp 1f
1:
movl $1f,%eax
jmp *%eax
1:
初始化堆栈指针寄存器,内核堆栈结构:
task_union+内核数据段。task_union占用8k
lss stack_start,%esp
初始化eax寄存器为0
xorl %eax,%eax
将为初始化数据段的其实地址存入edi寄存器中
movl $ SYMBOL_NAME(__bss_start),%edi
将内核映像的结束地址存入ecx中。
movl $ SYMBOL_NAME(_end),%ecx
将offset存入ecx中。
subl %edi,%ecx
对这段内存区域进行初始化操作,初始化为0
rep
stosb
设置中断描述符表
call setup_idt
利用push/pop指令初始化eflags寄存器
pushl $0
popfl
将第三张页表的首地址存入edi寄存器中
movl $ SYMBOL_NAME(empty_zero_page),%edi
一共需要初始化4k的内存。
前2k内存存放引导参数,后2kb内存存放命令行参数
movl $512,%ecx
cld
rep
movsl
将后2kb初始化为0
xorl %eax,%eax
movl $512,%ecx
rep
stosl
设置中断描述符表子程序
setup_idt:
将默认中断处理函数的有效地址放入edx寄存器中
lea ignore_int,%edx
初始化中断门描述符
中断处理程序入口地址放在0-15位
movl $(__KERNEL_CS << 16),%eax
内核代码段选择符存放在16-31位
movw %dx,%ax
movw $0x8E00,%dx
将中断描述符表的地址存入edi中
lea SYMBOL_NAME(idt_table),%edi
设置256项中断描述符表项
mov $256,%ecx
rp_sidt:
设置表项,一个表项占8字节
movl %eax,(%edi)
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt
ret
定义内核栈
ENTRY(stack_start)
.long SYMBOL_NAME(init_task_union)+8192
.long __KERNEL_DS
int_msg:
.asciz "Unknown interrupt, stack: %p %p %p %p/n"
ALIGN
定义缺省中断处理过程,仅仅打印"Unknown interrupt, stack: %p %p %p %p/n"
ignore_int:
cld
movl $(__KERNEL_DS),%eax
movl %eax,%ds
movl %eax,%es
pushl 12(%esp)
pushl 12(%esp)
pushl 12(%esp)
pushl 12(%esp)
pushl $int_msg
call SYMBOL_NAME(printk)
1: hlt
jmp 1b
定义中断描述符表表项数量
#define IDT_ENTRIES 256
定义页目录表,首先定一个了2张页表,用来映射内核内存空间
分别用于内核和用户区使用,并且映射到相同的物理地址空间(0-8M),
但是不能通过用户地址空间的虚拟地址来访问内核空间,这样做的原因是保证实模式到保护模式的平稳过渡。
.org 0x1000
ENTRY(swapper_pg_dir)
.long 0x00102007
.long 0x00103007
.fill BOOT_USER_PGD_PTRS-2,4,0
.long 0x00102007
.long 0x00103007
.fill BOOT_KERNEL_PGD_PTRS-2,4,0
第一张页表
ljmpl $__KERNEL_CS, $0x00100000
.org 0x2000
ENTRY(pg0)
第二张页表
.org 0x3000
ENTRY(pg1)
两张页表映射8M空间
.org 0x4000
ENTRY(empty_zero_page)
定义全局表述符表,因为linux采用的是分页机制,所以在全局描述符表中设置4个表项,
简化分段到分页的地址转换,这时虚拟地址空间和线性地址空间是一样的,都能表示4G
空间。在虚拟空间中,内核起始地址和用户地址空间起始位置相同。
ENTRY(gdt_table)
.quad 0x0000000000000000 空描述表项,一般不用
.quad 0x0000000000000000 同上
.quad 0x00cf9a000000ffff 内核代码段
.quad 0x00cf92000000ffff 内核数据段
.quad 0x00cffa000000ffff 用户代码段
.quad 0x00cff2000000ffff 用户数据段