/*
*注意!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 !