为了执行linux内的C语言main函数,上一篇讲到了,为了从汇编语言环境跳转到C语言环境下执行,将CPU工作模式从16位转变到32位模式(C语言是32位的),并且重新建立了GDT与IDT,但是此时GDT和IDT中并没有内容,所以不能进行内存寻址与中断,接下来就是初始化GDT和IDT了。
进入32位模式后,寄存器也将变为32位寄存器,下面的汇编语法和之前的intel汇编有些不同,为AT&T汇编,至于差别不在赘述。
Head.S
startup_32:
//重设段寄存器内容
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
//初始化栈指针,即将栈顶指针指向栈底
lss _stack_start,%esp
//建立IDT
call setup_idt
//建立GDT
call setup_gdt
//建立好GDT和IDT后重新设置段寄存器
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
xorl %eax,%eax
//检查A20是否成功打开
1: incl %eax
movl %eax,0x000000
cmpl %eax,0x100000
je 1b
//检查8087数学协处理器后,开始进行内存内页
call check_x87
jmp after_page_tables
after_page_tables:
//以下为将main函数的参数入栈
pushl $0
pushl $0
pushl $0
pushl $L6
//将main函数的地址入栈
pushl $_main
//开始进行内存分页
jmp setup_paging
上面的push _main操作很重要,因为在之后的ret指令后将会将栈中的_main函数地址装入cs,和ip中,参数也会装载到相应的寄存器中,这些操作都将在setup_paging后完成
setup_paging:
//初始化前5k的内存
movl $1024*5,%ecx
xorl %eax,%eax
xorl %edi,%edi
cld;
rep;
stosl
//将0x0000处赋值为0x1000 + 7,7的意思是111代表访问权限,0x1000作为页表基地址,接下来就分别映射0x2000,0x3000,0x4000
movl $pg0+7,_pg_dir
movl $pg1+7,_pg_dir+4
movl $pg2+7,_pg_dir+8
movl $pg3+7,_pg_dir+12
movl $pg3+4092,%edi
movl $0xfff007,%eax
//上述代码其实是建立4096字节的内核页表,每一个字节可以寻址到一个4k的页面,所以可以寻址的空间为16M
std
1: stosl
subl $0x1000,%eax
jge 1b
xorl %eax,%eax
movl %eax,%cr3
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0
//调用ret开始执行main函数
ret