linux0.11中head.s分析

/*
*注意!32位启动代码是从绝对地址0x0000 0000开始的,这里同样也是页目录
*将要存在的地方,因此启动代码会被页目录覆盖掉
*/
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:     !页目录将会放在这里
startup_32: 
/*
*注意!!!这里已处于3位运行模式,因此这里的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运行完成后,内存的分配示意图如下:



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值