/*
* 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,%eax
xorl %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,%eax
orl $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 0
idt_descr: #下面两行是lidt 指令的6 字节操作数:长度,基址。
.word 256*8-1 # idt contains 256 entries
.long _idt
.align 2
.word 0
gdt_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 */