功能
head.s 程序在被编译后,会被连接成system模块的最前面开始部分 Linux 内核使用描述符表的示意图
代码
/*
* linux/boot/head.s
*
* ( C) 1991 Linus Torvalds
*/
/*
* head.s contains the 32 -bit startup code.
*
* NOTE! ! ! Startup happens at absolute address 0x00000000, which is also where
* the page directory will exist. The startup code will be overwritten by
* the page directory.
*/
/*
* head.s 含有32 位启动代码。
* 注意! ! ! 32 位启动代码是从绝对地址0x00000000 开始的,这里也同样是页目录将存在的地方,
* 因此这里的启动代码将被页目录覆盖掉。
*/
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:
startup_32:
movl $0x10 ,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
! ———————————————————————— 设置IDT和GDT ————————————————————————
call setup_idt
call setup_gdt
movl $0x10 ,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp
! ———————————————————————— 测试 A20 地址线是否已经开启 ————————————————————————
xorl %eax,%eax
1 : incl %eax
movl %eax,0x000000
cmpl %eax,0x100000
je 1b
! ———————————————————————— ————————————————————————
/*
* 注意! 在下面这段程序中,486 应该将位16 置位,以检查在超级用户模式下的写保护,
* 此后"verify_area()" 调用中就不需要了。486 的用户通常也会想将NE(
* 对数学协处理器的出错使用int 16 。
*/
movl %cr0,%eax
andl $0x80000011 ,%eax
/* "orl $0x10020 ,%eax" here for 486 might be good */
orl $2 ,%eax
movl %eax,%cr0
call check_x87
jmp after_page_tables
! ———————————————————————— 检测 287 /387 存在与否 ————————————————————————
/*
* 我们依赖于ET 标志的正确性来检测287/387 存在与否。
*/
check_x87:
fninit
fstsw %ax
cmpb $0 ,%al
je 1f /* no coprocessor: have to set bits */
movl %cr0,%eax
xorl $6 ,%eax /* reset MP, set EM */
movl %eax,%cr0
ret
.align 2
1 : .byte 0xDB,0xE4 /* fsetpm for 287 , ignored by 387 */
ret
! ———————————————————————— 建立IDT ————————————————————————
/*
* 下面这段是设置中断描述符表子程序 setup_idt
*
* 将中断描述符表idt 设置成具有256 个项,并都指向 ignore_int 中断门。然后加载中断
* 描述符表寄存器( 用lidt 指令) 。真正实用的中断门以后再安装。当我们在其它地方认为一切
* 都正常时再开启中断。该子程序将会被页表覆盖掉。
*/
setup_idt:
lea ignore_int,%edx
movl $0x00080000 ,%eax
movw %dx,%ax
movw $0x8E00 ,%dx /* interrupt gate - dpl = 0 , present */
lea _idt, %edi
mov $256 , %ecx
rp_sidt:
movl %eax,( %edi)
movl %edx,4( %edi)
addl $8 ,%edi
dec %ecx
jne rp_sidt
lidt idt_descr
ret
! ———————————————————————— 建立GDT ————————————————————————
/*
* 设置全局描述符表项 setup_gdt
* 这个子程序设置一个新的全局描述符表gdt,并加载。此时仅创建了两个表项,与前
* 面的一样。该子程序只有两行,“非常的”复杂,所以当然需要这么长的注释了?。
*/
setup_gdt:
lgdt gdt_descr
ret
! ———————————————————————— 建立页目录表( 0x0000) 和页表( 0x1000 0x2000 0x3000 0x4000) ————————————————————————
/* Linus 将内核的内存页表直接放在页目录之后,使用了4 个表来寻址16 Mb 的物理内存。
* 如果你有多于16 Mb 的内存,就需要在这里进行扩充修改。
*/
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
! ———————————————————————— 从 0x5000 开始处设置软盘缓冲区(1K) ————————————————————————
/* 当DMA(直接存储器访问)不能访问缓冲块时,下面的tmp_floppy_area 内存块
* 就可供软盘驱动程序使用。其地址需要对齐调整,这样就不会跨越64kB 边界。
*/
_tmp_floppy_area:
.fill 1024,1 ,0
! ———————————————————————— 为调用main程序和返回做准备 ————————————————————————
after_page_tables:
pushl $0
pushl $0
pushl $0
pushl $L6
pushl $_main
jmp setup_paging
L6:
jmp L6
/* This is the default interrupt "handler" :-) */
/* 下面是默认的中断“向量句柄”? */
int_msg:
.asciz "Unknown interrupt\n \r "
.align 2
ignore_int:
pushl %eax
pushl %ecx
pushl %edx
push %ds
push %es
push %fs
movl $0x10 ,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
pushl $int_msg
call _printk
popl %eax
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret
! ———————————————————————— 启动分页机制 ————————————————————————
/*
* 这个子程序通过设置控制寄存器cr0 的标志(PG 位31)来启动对内存的分页处理功能,
* 并设置各个页表项的内容,以恒等映射前16 MB 的物理内存。分页器假定不会产生非法的
* 地址映射(也即在只有4Mb 的机器上设置出大于4Mb 的内存地址)。
* 注意!尽管所有的物理地址都应该由这个子程序进行恒等映射,但只有内核页面管理函数能
* 直接使用> 1Mb 的地址。所有“一般”函数仅使用低于1Mb 的地址空间,或者是使用局部数据
* 空间,地址空间将被映射到其它一些地方去 -- mm( 内存管理程序) 会管理这些事的。
* 对于那些有多于16Mb 内存的家伙 - 太幸运了,我还没有,为什么你会有?。代码就在这里,
* 对它进行修改吧。(实际上,这并不太困难的。通常只需修改一些常数等。我把它设置为
* 16Mb,因为我的机器再怎么扩充甚至不能超过这个界限(当然,我的机器很便宜的?)。
* 我已经通过设置某类标志来给出需要改动的地方(搜索“16Mb”),但我不能保证作这些
* 改动就行了)。
*/
.align 2
setup_paging:
movl $1024 *5,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi
cld ; rep; stosl
movl $pg0 +7,_pg_dir /* set present bit/user r/w */
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 /* 16Mb - 4096 + 7 ( r/w user,p) */
std
1 : stosl /* fill pages backwards - more efficient :-) */
subl $0x1000 ,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000 ,%eax
movl %eax,%cr0 /* set paging ( PG) bit */
ret /* this also flushes prefetch-queue */
.align 2
.word 0
! ———————————————————————— idt 和 gdt 描述符 ————————————————————————
idt_descr:
.word 256 *8-1
.long _idt
.align 2
.word 0
gdt_descr:
.word 256 *8-1
.long _gdt
.align 3
! ———————————————————————— idt 和 gdt 表的实际地址 ————————————————————————
_idt: .fill 256,8 ,0
_gdt:
.quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a0000000fff /* 16Mb */
.quad 0x00c0920000000fff /* 16Mb */
.quad 0x0000000000000000 /* TEMPORARY - don't use */ # 系统段描述符
.fill 252,8,0 /* space for LDT' s and TSS's etc */