您的位置:首页 > 运维架构 > Linux

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

2015-10-04 14:48 573 查看
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 用户数据段
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: