linux2.4 启动代码head.S分析

来源:互联网 发布:mac 预览 目录 编辑:程序博客网 时间:2024/04/28 14:11

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

第一张页表

由于在进入starup_32之前,reamponline.S已经将内核代码段设置成从1M开始了。实模式
flush_instr:
 ljmpl $__KERNEL_CS, $0x00100000
符号地址在实模式下:cs:offset
.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 用户数据段

原创粉丝点击