head.s全注释

/*
*注意!32位启动代码是从绝对地址0x0000 0000开始的,这里同样也是页目录
*将要存在的地方,因此启动代码会被页目录覆盖掉
*/
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:     !页目录将会放在这里
startup_32: 
/*
*注意!!!这里已处于32位运行模式,因此这里的0x10并不是把地址0x10放入到各个段寄存器中,它现在**其实是
全局段描述符表中的偏移值,或者更正确的说是一个描述符项的选择符。这里0x10含义是请求
*特权级0,选择全局描述符表,选择表中的第二项。正好指向表中的数据段描述符项。
*下面代码含义是ds,es,fs,gs中的选择符为setup中构造的数据段。并将堆栈放置在stack_start指向*的
user_stack数组区.然后使用本程序后面定义的新中断描述符表和全局段描述符表.新全局段描述**符表中初始
内容和setup中基本一样,仅段限长修改成了16M,stack_start定义在kernel/sched.s中**,
是指向user_stack数组末端的一个长指针。
*/    
        movl $0x10,%eax             !对GNU汇编来说,每个直接数要以"$"开始要不就表示地址
        mov %ax,%ds           !
        mov %ax,%es           !
        mov %ax,%fs           !
        mov %ax,%gs           !
        lss _stack_start,%esp !表示_stack_start->ss:esp,设置系统堆栈
        call setup_idt        !调用设置中断描述符表子程序
        call setup_gdt        !调用设置全局描述符表子程序
        movl $0x10,%eax       !因为修改了gdt,所以重新装载所有的段寄存器
        mov %ax,%ds           !
        mov %ax,%es           !
        mov %ax,%fs           !
        mov %ax,%gs           !
        lss _stack_start,%esp !
        xorl %eax,%eax        !
                              !
1:                            !
        incl %eax             !检测A20地址线是否开启.采用的方法是向0x000000处写入任意一
        movl %eax,0x000000    !个数值,然后看内存0x100000处是否也是这个数值.如果一直相同
        cmpl %eax,0x100000    !的话,就一直比较下去,即死机.表示没有选通A20,结果内核不能
        je 1b                 !使用1M以上的内存
                              
                              !检查数学协处理器是否存在.方法是修改控制寄存器CR0,在假设存
                              !在处理器的情况下执行一个协处理器指令,出错就不存在,需要   
                              !设置CR0中的协处理器仿真位EM,并复位协处理器存在标志MP     
        movl %cr0,%eax        !
        andl $0x80000011,%eax !save PG,PE,ET
        orl $2,%eax           !set MP
        movl %eax,%cr0        !
        call check_x86        !
        jmp after_page_tables !
                                      !
check_x86:                    !
        fninit                !
        fstsw %ax             !
        cmpb $0,%al           !
        je 1f                 !
        movl %cr0,%eax        !
        xorl $6,%eax          !
        movl %eax,%cr0        !
        ret                   !
.align 2                      !存储边界对齐,2表示调整到地址最后2位为零,即4字节对齐
1:                            !
        .byte 0xDB,0xE4       !
        ret                   !
!中断描述符表idt有256项,并都指向ignore_int中断门,然后加载中断描述符表寄存器,真正实用
!的中断门以后再安装.认为其他地方都正常时在开启中断.该子程序会被也表覆盖掉
!中断描述符表中断是8字节构成,格式与全局表不同,被称为们描述符(Gate Descriptor).0-1,6-7字
!节是偏移量,2-3是选择符,4-5字节是标志
set_idt:                      !
        lea ignore_int,%edx   !ignore_int有效地址->edx寄存器
        movl $0x00080000,%eax !选择符放入eax的高16位,selector=0x0008=cs
        movw %dx,%ax          !偏移值低16位放入eax的低16位中.eax含有们描述符低4字节的值
        movw $0x8E00,%dx      !edx含有门描述符高4字节的值
                                      !
        lea _idt,%edi         !_idt是中断描述符表的地址
        mov $256,%ecx         !
rp_sidt:                      !
        movl %eax,(%edi)      !将哑中断门描述符存入表中
        movl %edx,4(%edi)     !
        addl $8,%edi          !edi指向表中下一项
        dec %ecx              !
        jne rp_sidt           !
        lidt idt_descr        !加载中断描述符表寄存器值
        ret                   !
                              !
setup_gdt:                    !
        lgdt gdt_descr        !加载全局描述符表寄存器
        ret                   !
!内核的内存也表直接放在页目录之后,使用了4个表来寻址16M物理内存.每个也表长4KB字节,每个页表需要4自己,
!因此一个页表可以存放1024个表项,如果一个页表项寻址4KB,则一个页表可以寻址4M,
!页表项格式:项前0-11位存放一些标志,例如是否存在内存中(P位0),读写许可(R/W位1),普通用户还
!是超级用户(U/S位2),是否修改过(是否脏了D位6).表项位12-31是页框地址,用于指出一页内存物理
!起始地址
.org 0x1000                   !偏移0x1000处开始时第一个页表(偏移0是页表目录)
pg0                           !
                              !
.org 0x2000                   !
pg1                           !
                              !
.org 0x3000                   !
pg2                           !
                              !
.org 0x4000                   !
pg3                           !
                              !
.org 0x5000                   !定义下面的内存数据从偏移0x5000开始
!当DMA不能访问缓冲块时,_tmp_floppy_area内存块就可供软盘驱动使用,其地址需要对齐调整,这样!!就不会跨越64K边界
!
_tmp_floppy_area:             !
        .fill 1024,1,0    !
!这几个入栈操作(pushl)用于为调用/init/main.c程序和返回作准备,前面3个入栈0应该分别是envp,
!argv,argc值,但main没有用到.pushl $L6是模拟调用main程序时首先将返回地址入栈的操作,所以
!main真退出时,就会返回到这里L6继续执行下去.入栈完成后就进行分页处理,分页处理完成后执行
!ret指令,此时就会将main程序地址弹出堆栈,并执行main程序去了
after_page_tables:            !
        pushl $0          !
        pushl $0          !
        pushl $0          !
        pushl $L6         !
        pushl $_main      !
        jmp setup_paging  !
L6:                           !
        jmp L6              !
                    
int_msg:
        .asciz "Unknown interrupt\n\r"
.align 2
ignore_int:
        pushl %eax        !    
        pushl %ecx        !
        pushl %edx        !
        push %ds          !
        push %es          !
        push %fs          !这里ds es fs gs虽然是16位寄存器,但入栈后以32位保存
        movl $0x10,%eax   !置段选择符(使ds,es,fs指向gdt表中的数据段)
        mov %ax,%ds       !
        mov %ax,%es       !
        mov %ax,%fs       !
        pushl $int_msg    !把调用printk函数的参数指针入栈
        call _printk      !
        popl %eax         !
        pop %fs           !
        pop %es           !
        pop %ds           !
        popl %edx         !
        popl %ecx         !
        popl %eax         !
        iret              !
 
!这个程序通过设置控制寄存器cr0的标志PG位31来启动对分页的分页处理功能,并设置各个页表项
!的内容,以恒等于前16M的物理内存,分页器假定不会产生非法的地址映射.注意,尽管所有的物理地址
!都应该由这个子程序进行恒等映射,但只有内核页面管理函数能直接使用>1M的地址,所有"一般"
!函数仅使用低于1M的地址空间,或者是使用局部数据空间,地址空间将被映射到其他一些地方去,
!mm(内存管理程序)会管理这些事.
!在内存物理地址0x0处开始存放1页页目录表和4页页表.页目录表是系统所有进程公用,而这里的4页
!表示内核专用的.对于新进程,系统会再主内存区为其申请页面存放页表.
!
.align 2                      !
setup_paging:                 !
        movl $1024*5,%ecx !首先对5页内存清零
        xorl %eax,%eax    !
        xorl %edi,%edi    !
        cld               !
        rep               !
        stosl             !
!下面4句是设置目录表中的项,因为内核共有4个页表所以只需要设置4项,页目录项的结构与页表中
!项的结构一样,4字节为1项,$pg0+7表示:0x0000 1007 是页目录中的第一项
!则第一个页表所在的地址0x0000 1007 & 0xffff f000=0x1000
!第一个页表属性标志 0x0000 1007 & 0x0000 0fff=0x07 表示该页存在,用户可读写
        movl $pg0+7,_pg_dir !
        movl $pg1+7,_pg_dir !
        movl $pg2+7,_pg_dir !
        movl $pg3+7,_pg_dir !
!下面6行填写4个页表中所有项的内容,共有4(页表)*1024(项/页表)=4096项(0-0xfff)
!也即能映射物理内存4096*4kb=16M
!每项的内容是:当前项所映射的物理内存地址+该页的标志(这里都是7)
!使用的方法是从最后一个页表的最后一项开始按倒退顺序填写,一个页表的最后一项在页表中的位置
!是1023*4=4092.因此最后一页的最后一项位置是$pg3+4092
!                       
        movl $pg3+4092,%edi !edi->指向最后一页最后一项
        movl $0xfff007,%eax !
        std                 !方向位,edi递减
1:      stosl               !
        subl $0x1000,%eax   !每填好一项,物理地址值减0x1000
        jge 1b              !如果小于0则说明全添写好了
!设置页目录基地址寄存器cr3的值,指向页目录表
        xorl %eax,%eax      !页目录表在0x0000
        movl %eax,%cr3      !cr3=0
        movl %cr0,%eax      !启动分页标志
        orl $0x80000000,%eax!
        movl %eax,%cr0      !
        ret                 !去执行main
.align 2                        !
.word 0                         !
idt_descr:                      !下面是lidt指令6字节操作数:
        .word 256*8-1         !长度
        .long _idt            !基址
.align 2                        !
.word 0                         !
gdt_descr:                      !下面是lgdt指令的6字节操作数
        .word 256*8-1         !长度
        .long _gdt            !基址
.align 3                        !
_idt:           .fill 256,8,0         !256项,每项8字节,填0
!全局表.前4项是空项(不用),代码段描述符,数据段描述符,系统段描述符.其中系统段描述符linux没
!有用上.后面预留了252项空间,用户放置所创建人物的局部描述符LDT和对应的任务状态段TSS描述符
!0-null  1-cs  2-ds  3-sys 4-TSS0 5-LDT0 6-TSS1 7-LDT1 8-TSS2 etc...
_gdt:           .quad 0x0000 0000 0000 0000 !
                .quad 0x00c0 9a00 0000 0fff !
                .quad 0x00c0 9200 0000 0fff !
                .quad 0x0000 0000 0000 0000 !
                .fill 252,8,0               !

你可能感兴趣的:(head.s全注释)