📄 head.s
字号:
.org 0x2000pg1:.org 0x3000pg2:.org 0x4000pg3:.org 0x5000 # 定义下面的内存数据块从偏移0x5000 处开始。/** tmp_floppy_area is used by the floppy-driver when DMA cannot* reach to a buffer-block. It needs to be aligned, so that it isn't* on a 64kB border.*//* 当DMA(直接存储器访问)不能访问缓冲块时,下面的tmp_floppy_area 内存块* 就可供软盘驱动程序使用。其地址需要对齐调整,这样就不会跨越64kB 边界。*/_tmp_floppy_area:.fill 1024,1,0 # 共保留1024 项,每项1 字节,填充数值0。# 下面这几个入栈操作(pushl)用于为调用/init/main.c 程序和返回作准备。# 前面3 个入栈指令不知道作什么用的,也许是Linus 用于在调试时能看清机器码用的?。# 139 行的入栈操作是模拟调用main.c 程序时首先将返回地址入栈的操作,所以如果# main.c 程序真的退出时,就会返回到这里的标号L6 处继续执行下去,也即死循环。# 140 行将main.c 的地址压入堆栈,这样,在设置分页处理(setup_paging)结束后# 执行'ret'返回指令时就会将main.c 程序的地址弹出堆栈,并去执行main.c 程序去了。after_page_tables:pushl $0 # These are the parameters to main :-)pushl $0 # 这些是调用main 程序的参数(指init/main.c)。pushl $0pushl $L6 # return address for main, if it decides to.pushl $_main # '_main'是编译程序对main 的内部表示方法。jmp setup_paging # 跳转至第198 行。L6:jmp L6 # main should never return here, but# just in case, we know what happens./* This is the default interrupt "handler" :-) *//* 下面是默认的中断“向量句柄”? */int_msg:.asciz "Unknown interrupt\n\r" # 定义字符串“未知中断(回车换行)”。.align 2 # 按4 字节方式对齐内存地址。ignore_int:pushl %eaxpushl %ecxpushl %edxpush %ds # 这里请注意!!ds,es,fs,gs 等虽然是16 位的寄存器,但入栈后# 仍然会以32 位的形式入栈,也即需要占用4 个字节的堆栈空间。push %espush %fsmovl $0x10,%eax # 置段选择符(使ds,es,fs 指向gdt 表中的数据段)。mov %ax,%dsmov %ax,%esmov %ax,%fspushl $int_msg # 把调用printk 函数的参数指针(地址)入栈。call _printk # 该函数在/kernel/printk.c 中。# '_printk'是printk 编译后模块中的内部表示法。popl %eaxpop %fspop %espop %dspopl %edxpopl %ecxpopl %eaxiret # 中断返回(把中断调用时压入栈的CPU 标志寄存器(32 位)值也弹出)。/** Setup_paging** This routine sets up paging by setting the page bit* in cr0. The page tables are set up, identity-mapping* the first 16MB. The pager assumes that no illegal* addresses are produced (ie >4Mb on a 4Mb machine).** NOTE! Although all physical memory should be identity* mapped by this routine, only the kernel page functions* use the >1Mb addresses directly. All "normal" functions* use just the lower 1Mb, or the local data space, which* will be mapped to some other place - mm keeps track of* that.** For those with more memory than 16 Mb - tough luck. I've* not got it, why should you :-) The source is here. Change* it. (Seriously - it shouldn't be too difficult. Mostly* change some constants etc. I left it at 16Mb, as my machine* even cannot be extended past that (ok, but it was cheap :-)* I've tried to show which constants to change by having* some kind of marker at them (search for "16Mb"), but I* won't guarantee that's all :-( )*//** 这个子程序通过设置控制寄存器cr0 的标志(PG 位31)来启动对内存的分页处理功能,* 并设置各个页表项的内容,以恒等映射前16 MB 的物理内存。分页器假定不会产生非法的* 地址映射(也即在只有4Mb 的机器上设置出大于4Mb 的内存地址)。* 注意!尽管所有的物理地址都应该由这个子程序进行恒等映射,但只有内核页面管理函数能* 直接使用>1Mb 的地址。所有“一般”函数仅使用低于1Mb 的地址空间,或者是使用局部数据* 空间,地址空间将被映射到其它一些地方去 -- mm(内存管理程序)会管理这些事的。* 对于那些有多于16Mb 内存的家伙 - 太幸运了,我还没有,为什么你会有?。代码就在这里,* 对它进行修改吧。(实际上,这并不太困难的。通常只需修改一些常数等。我把它设置为* 16Mb,因为我的机器再怎么扩充甚至不能超过这个界限(当然,我的机器很便宜的?)。* 我已经通过设置某类标志来给出需要改动的地方(搜索“16Mb”),但我不能保证作这些* 改动就行了??)。*/.align 2 # 按4 字节方式对齐内存地址边界。setup_paging: # 首先对5 页内存(1 页目录 + 4 页页表)清零movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */xorl %eax,%eaxxorl %edi,%edi /* pg_dir is at 0x000 */# 页目录从0x000 地址开始。cld;rep;stosl# 下面4 句设置页目录中的项,我们共有4 个页表所以只需设置4 项。# 页目录项的结构与页表中项的结构一样,4 个字节为1 项。参见上面113 行下的说明。# "$pg0+7"表示:0x00001007,是页目录表中的第1 项。# 则第1 个页表所在的地址 = 0x00001007 & 0xfffff000 = 0x1000;# 第1 个页表的属性标志 = 0x00001007 & 0x00000fff = 0x07,表示该页存在、用户可读写。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 /* --------- " " --------- */# 下面6 行填写4 个页表中所有项的内容,共有:4(页表)*1024(项/页表)=4096 项(0 - 0xfff),# 也即能映射物理内存 4096*4Kb = 16Mb。# 每项的内容是:当前项所映射的物理内存地址 + 该页的标志(这里均为7)。# 使用的方法是从最后一个页表的最后一项开始按倒退顺序填写。一个页表的最后一项在页表中的# 位置是1023*4 = 4092。因此最后一页的最后一项的位置就是$pg3+4092。movl $pg3+4092,%edi # edi??最后一页的最后一项。movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */# 最后1 项对应物理内存页面的地址是0xfff000,# 加上属性标志7,即为0xfff007.std # 方向位置位,edi 值递减(4 字节)。1: stosl /* fill pages backwards - more efficient :-) */subl $0x1000,%eax # 每填写好一项,物理地址值减0x1000。jge 1b # 如果小于0 则说明全添写好了。# 设置页目录基址寄存器cr3 的值,指向页目录表。xorl %eax,%eax /* pg_dir is at 0x0000 */ # 页目录表在0x0000 处。movl %eax,%cr3 /* cr3 - page directory start */# 设置启动使用分页处理(cr0 的PG 标志,位31)movl %cr0,%eaxorl $0x80000000,%eax # 添上PG 标志。movl %eax,%cr0 /* set paging (PG) bit */ret /* this also flushes prefetch-queue */# 在改变分页处理标志后要求使用转移指令刷新预取指令队列,这里用的是返回指令ret。# 该返回指令的另一个作用是将堆栈中的main 程序的地址弹出,并开始运行/init/main.c 程序。# 本程序到此真正结束了。.align 2 # 按4 字节方式对齐内存地址边界。.word 0idt_descr: #下面两行是lidt 指令的6 字节操作数:长度,基址。.word 256*8-1 # idt contains 256 entries.long _idt.align 2.word 0gdt_descr: # 下面两行是lgdt 指令的6 字节操作数:长度,基址。.word 256*8-1 # so does gdt (not that that's any.long _gdt # magic number, but it works for me :^).align 3 # 按8 字节方式对齐内存地址边界。_idt: .fill 256,8,0 # idt is uninitialized # 256 项,每项8 字节,填0。# 全局表。前4 项分别是空项(不用)、代码段描述符、数据段描述符、系统段描述符,其中# 系统段描述符linux 没有派用处。后面还预留了252 项的空间,用于放置所创建任务的# 局部描述符(LDT)和对应的任务状态段TSS 的描述符。# (0-nul, 1-cs, 2-ds, 3-sys, 4-TSS0, 5-LDT0, 6-TSS1, 7-LDT1, 8-TSS2 etc...)_gdt: .quad 0x0000000000000000 /* NULL descriptor */.quad 0x00c09a0000000fff /* 16Mb */ # 代码段最大长度16M。.quad 0x00c0920000000fff /* 16Mb */ # 数据段最大长度16M。.quad 0x0000000000000000 /* TEMPORARY - don't use */.fill 252,8,0 /* space for LDT's and TSS's etc */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -