📄 head.s
字号:
/** 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: # 18-22 行设置各个数据段寄存器。movl $0x10,%eax # 对于GNU 汇编来说,每个直接数要以'$'开始,否则是表示地址。# 每个寄存器名都要以'%'开头,eax 表示是32 位的ax 寄存器。# 再次注意!!! 这里已经处于32 位运行模式,因此这里的$0x10 并不是把地址0x10 装入各个# 段寄存器,它现在其实是全局段描述符表中的偏移值,或者更正确地说是一个描述符表项# 的选择符。有关选择符的说明请参见setup.s 中193 行下的说明。这里$0x10 的含义是请求# 特权级0(位0-1=0)、选择全局描述符表(位2=0)、选择表中第2 项(位3-15=2)。它正好指在当前的Linux 操作系统中,gas 和gld 已经分别更名为as 和ld。# 向表中的数据段描述符项。(描述符的具体数值参见前面setup.s 中212,213 行)# 下面代码的含义是:置ds,es,fs,gs 中的选择符为setup.s 中构造的数据段(全局段描述符表# 的第2 项)=0x10,并将堆栈放置在数据段中的_stack_start 数组内,然后使用新的中断描述# 符表和全局段描述表.新的全局段描述表中初始内容与setup.s 中的完全一样。mov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss _stack_start,%esp # 表示_stack_start??ss:esp,设置系统堆栈。# stack_start 定义在kernel/sched.c,69 行。call setup_idt # 调用设置中断描述符表子程序。call setup_gdt # 调用设置全局描述符表子程序。movl $0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. CS was alreadymov %ax,%es # reloaded in 'setup_gdt'mov %ax,%fs # 因为修改了gdt,所以需要重新装载所有的段寄存器。mov %ax,%gs # CS 代码段寄存器已经在setup_gdt 中重新加载过了。lss _stack_start,%esp# 32-36 行用于测试A20 地址线是否已经开启。采用的方法是向内存地址0x000000 处写入任意# 一个数值,然后看内存地址0x100000(1M)处是否也是这个数值。如果一直相同的话,就一直# 比较下去,也即死循环、死机。表示地址A20 线没有选通,结果内核就不能使用1M 以上内存。xorl %eax,%eax1: incl %eax # check that A20 really IS enabledmovl %eax,0x000000 # loop forever if it isn'tcmpl %eax,0x100000je 1b # '1b'表示向后(backward)跳转到标号1 去(33 行)。# 若是'5f'则表示向前(forward)跳转到标号5 去。/** NOTE! 486 should set bit 16, to check for write-protect in supervisor* mode. Then it would be unnecessary with the "verify_area()"-calls.* 486 users probably want to set the NE (#5) bit also, so as to use* int 16 for math errors.*//** 注意! 在下面这段程序中,486 应该将位16 置位,以检查在超级用户模式下的写保护,* 此后"verify_area()"调用中就不需要了。486 的用户通常也会想将NE(#5)置位,以便* 对数学协处理器的出错使用int 16。*/# 下面这段程序(43-65)用于检查数学协处理器芯片是否存在。方法是修改控制寄存器CR0,在# 假设存在协处理器的情况下执行一个协处理器指令,如果出错的话则说明协处理器芯片不存在,# 需要设置CR0 中的协处理器仿真位EM(位2),并复位协处理器存在标志MP(位1)。movl %cr0,%eax # check math chipandl $0x80000011,%eax # Save PG,PE,ET/* "orl $0x10020,%eax" here for 486 might be good */orl $2,%eax # set MPmovl %eax,%cr0call check_x87jmp after_page_tables # 跳转到135 行。/** We depend on ET to be correct. This checks for 287/387.*//** 我们依赖于ET 标志的正确性来检测287/387 存在与否。*/check_x87:fninitfstsw %axcmpb $0,%alje 1f /* no coprocessor: have to set bits */movl %cr0,%eax # 如果存在的则向前跳转到标号1 处,否则改写cr0。xorl $6,%eax /* reset MP, set EM */movl %eax,%cr0ret.align 2 # 这里".align 2"的含义是指存储边界对齐调整。"2"表示调整到地址最后2 位为零,# 即按4 字节方式对齐内存地址。1: .byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */ # 287 协处理器码。ret/** setup_idt** sets up a idt with 256 entries pointing to* ignore_int, interrupt gates. It then loads* idt. Everything that wants to install itself* in the idt-table may do so themselves. Interrupts* are enabled elsewhere, when we can be relatively* sure everything is ok. This routine will be over-* written by the page tables.*//** 下面这段是设置中断描述符表子程序 setup_idt** 将中断描述符表idt 设置成具有256 个项,并都指向ignore_int 中断门。然后加载中断* 描述符表寄存器(用lidt 指令)。真正实用的中断门以后再安装。当我们在其它地方认为一切* 都正常时再开启中断。该子程序将会被页表覆盖掉。*/# 中断描述符表中的项虽然也是8 字节组成,但其格式与全局表中的不同,被称为门描述符# (Gate Descriptor)。它的0-1,6-7 字节是偏移量,2-3 字节是选择符,4-5 字节是一些标志。setup_idt:lea ignore_int,%edx # 将ignore_int 的有效地址(偏移值)值??edx 寄存器movl $0x00080000,%eax # 将选择符0x0008 置入eax 的高16 位中。movw %dx,%ax /* selector = 0x0008 = cs */# 偏移值的低16 位置入eax 的低16 位中。此时eax 含有#门描述符低4 字节的值。movw $0x8E00,%dx /* interrupt gate - dpl=0, present */# 此时edx 含有门描述符高4 字节的值。lea _idt,%edi # _idt 是中断描述符表的地址。mov $256,%ecxrp_sidt:movl %eax,(%edi) # 将哑中断门描述符存入表中。movl %edx,4(%edi)addl $8,%edi # edi 指向表中下一项。dec %ecxjne rp_sidtlidt idt_descr # 加载中断描述符表寄存器值。ret/** setup_gdt** This routines sets up a new gdt and loads it.* Only two entries are currently built, the same* ones that were built in init.s. The routine* is VERY complicated at two whole lines, so this* rather long comment is certainly needed :-).* This routine will beoverwritten by the page tables.*//** 设置全局描述符表项 setup_gdt* 这个子程序设置一个新的全局描述符表gdt,并加载。此时仅创建了两个表项,与前* 面的一样。该子程序只有两行,“非常的”复杂,所以当然需要这么长的注释了?。setup_gdt:lgdt gdt_descr # 加载全局描述符表寄存器(内容已设置好,见232-238 行)。ret/** I put the kernel page tables right after the page directory,* using 4 of them to span 16 Mb of physical memory. People with* more than 16MB will have to expand this.*//* Linus 将内核的内存页表直接放在页目录之后,使用了4 个表来寻址16 Mb 的物理内存。* 如果你有多于16 Mb 的内存,就需要在这里进行扩充修改。*/# 每个页表长为4 Kb 字节,而每个页表项需要4 个字节,因此一个页表共可以存放1000 个表项,# 如果一个表项寻址4 Kb 的地址空间,则一个页表就可以寻址4 Mb 的物理内存。# 页表项的格式为:项的前0-11 位存放一些标志,如是否在内存中(P 位0)、读写许可(R/W 位1)、# 普通用户还是超级用户使用(U/S 位2)、是否修改过(是否脏了)(D 位6)等;表项的位12-31 是# 页框地址,用于指出一页内存的物理起始地址。.org 0x1000 # 从偏移0x1000 处开始是第1 个页表(偏移0 开始处将存放页表目录)。pg0:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -