MyOS 之 分页

实现分页要做以下三件事:

  1. 在内存某位置写好页表
  2. 页目录地址赋值给 cr3 寄存器
  3. 将 cr0 寄存器的 pg 位置 1

我们可以类比段的转化,我们最初给的地址是 段选择子:段内偏移值,在保护模式下,用段选择子去内存中的段描述符表中,找到段描述符,取出段基址,再+段内偏移地址,得到最终的物理地址。

页的转化也是类似的,上一步通过段描述符得到的“物理地址”,再开启分页后叫做逻辑地址。这个逻辑地址也是分成 前半部分:后半部分 这种形式,用前半部分的值在页表中寻找并换出一个页地址(也可以理解成基址这个概念),然后再拼接上后半部分的值,得到最终的物理地址。

只不过,现在的页表方案一般是二级页表(而且一个进程一个页表),第一级叫页目录表(PDE),第二级叫页表(PTE)。然后这个逻辑地址就是被看成 高10位:中间10位:后12位。高10位负责再页目录表中找到一个页目录项,这个页目录项的值加上中间10位拼接后的地址去页表中去寻找一个页表项,这个页表项的值,再加上后12位,拼接后的地址就是最终的物理地址。

分页也只是映射了一下,是在4KB为单位上的映射,就算不分页也是4GB。32位就是4GB。重新映射而已。这里就会有问题。

就说4G吧,内核代码放到哪里?这就用到了内核地址和用户地址,内核地址放到上面好还是放到下面好呢?

再者说,页表是每个进程都有一份,它应该是动态的或者别的,好难懂啊。

页表(页目录表,一样的):

我设计低3GB是用户空间,高1GB是内核空间,挺好的,Linux也是这样做的。Windows 32操作系统默认内核空间和用户空间的比例是1:1,即2G内核空间、2G内存空间,32位Linux系统中默认比例则是1:3,即1G内核空间,3G内存空间。

页目录表中最多有 1024 个页目录项,一个页表中最多有 1024 个页表项,理论上1024 * 1024 * 4KB,

第 768 项是第3GB的开始,页目录表的第 769~1022 项,分别往后对应 254 个页表,不过这些页表还没有写,先空着,页目录表的第 1023 项,其地址指向该页目录表本身(也就是把页目录表当作页表去理解了),通过这种方式可以访问页目录表本身。无非就是想用虚拟地址访问到这个页表本身嘛。

因为我们分页之前的代码(loader)都在低端 1MB 范围内,所以开启分页之后的逻辑地址开始的 1M 也要一一对应上物理地址的开始 1M(因为有些固定了的地址),所以有了第 0 个页目录项。第 768 个页目录项对应着逻辑地址 3G 以上的 4M( 0xc0000000~0xc03fffff 不过我们页表只写了 256 项也就是规划了 1M),这是因为我们决定将操作系统内核写在 3G 以上的 1M 空间(逻辑地址)里

现在是还没有分页,所以暂且不用管分页,但是现在要管了。那么按理说,每个页都是要自己制定的?

如果仅仅有段的话,物理地址实际上是连续的,如果分页的话,就可以间插充分利用小片段(4K),这样首次分配法就不是很有意义了是吧,因为每个程序最多不浪费4K的空间,一个电脑不会同时运行成千上万个进程。分配空间也快得多了。

页目录表就相当于GDT,页表是进程自己实现的,当一个进程创建,它自己分配好(实际上系统给它分配好)形成页表,注册进页目录表中,跟TSS差不多了,都是进程开辟时创建。

豁然开朗。

因此,随便找一个地方填入页目录表,就这样,当然,最多就支持1024个进程了,就是这样。

豁然开朗。

那么现在就要好好规划一下,

4K是什么?是0x1000,第一个1024*4K(页表)4194304B=4194KB=4MB,这4MB存着至关重要的信息,

0x280000 2621440B=2621KB=2MB

0x400000 4194304B=4194KB=4MB,我感觉写几MB够呛,只要不作死的话,就几KB甚至几十KB。

内核为高3G,映射好这4MB足矣,剩下的4GB-4MB就随机分配了。

页目录表是1024*4字节=4KB的页目录表,页表也是4KB,因为每个进程都能享用4GB的虚拟内存空间,但是只有有限的项是可用的,一旦超过了,就产生了一个缺页中断,内存分配,但是进程是感觉不出来的。就是这样。

页图 每一个4K用1位表示,那么就会有 4GB/4KB=(4x1024x1024KB)/4KB=1024x1024bit=131072B=131KB=0x20000

页表 1024x4字节=4096字节=4KB,页表比页图要小。。。

第一个页的4KB里,光代码地址就到了2MB附近了,因此一个页表是不够的,至少4MB了。

在这段4MB中,

空白 页图 页目录表 中断向量表 段表 代码段

如果采用首次适配法就不能用分页的优势了,我采用的内存分配是可变动态规划,分开存储,我一定要用上分页的优势。

但最终还是倾向于越来越细碎。

分为3个指针,第一部分是大块,第二部分是中块,最后一部分是小块

4GB-4MB-4KB

4GB~1GB:1GB~1MB:1MB~4KB

不行,它仍然也会出现,以100MB,1MB,4KB为三个极端呢?分成3部分,动态配置,100MB从底部上升,4KB从顶部下降,1MB从中间向两边拓展,采用首次适配法就可以了。基本上是不会遇到问题的,这样甚至可以分成4份,算了分成两份吧,4MB和4KB,比较简单。1B-1KB-1MB-1GB分配。

首先还有页图指针。嗯。

接下来注册一个页表。怎么注册,返回什么?

首先呢,返回页表地址,一个4KB地址,里面第一位就是自己了,然后,参数是想要分配的内存,比如4MB,内部函数就是通过位图,采取首部适配法,搜索page map,超过4MB的就拆成好几个4MB,同样的道理类似于4KB的

删除呢,就是用页表删除page map里面的项,然后注销这个页表就可以了。

Memory_use!我感觉不错,很配。

Memory_free

好,接下来就是页表的学习,接下来要分配4MB的页表,一个页4K,那么一个页表最多就4M,加上页目录表才是4G,不是所有进程共用一个页目录表吧?是一个进程一个页目录表。。。进程切换同时切换cr3,可见并没有问题。

我为什么要把内核1GB内存放到后面?我就放到前面,用户进程就是从1GB之后开始的,怎么了?反正往后也用不到实际地址了,虚拟地址为0反而让人看不清,对,就这样。内核态与用户态反而重新凝聚在一起了。

页目录表和页表的数据结构都是页表项(页描述子):

整体大概是 00000000000000000000 000 1 0 0 0 1 1 1 1 1,页目录表与页表的控制位是一样的,都是0x11f,前20位是地址。

0000,0000,0000,0000,0000,0001,0001,1111,4k是0x1000,就是赋给一个ebx然后左移12位就可以了。

 

P--位0是存在(Present)标志,用于指明表项对地址转换是否有效。用于指示该页是否已调入内存,供程序访问时参考;P=1表示有效;P=0表示无效。在页转换过程中,如果说涉及的页目录或页表的表项无效,则会导致一个异常(缺页异常)。必须写1.

如果P=0,那么除表示表项无效外,其余位可供程序自由使用(毕竟无效了嘛,但是现在你不要用)。例如,操作系统可以使用这些位来保存已存储在磁盘上的页面的序号。

R/W--位1是读/写(Read/Write)标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行(内核程序专用)。当处理器运行在超级用户特权级(级别0、1或2)时,则R/W位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。写1就可以了。

U/S--位2是用户/超级用户(User/Supervisor)标志。如果为1,那么运行在任何特权级上的程序都可以访问该页面。如果为0,那么页面只能被运行在超级用户特权级(0、1或2)上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。写1就可以了。

PWT: Page Write Through
PWT= 1时,写数据到缓存(Cache)的时候也要将数据写入内存中。

PCD: Page Cache Disable
PCD = 1时,禁止某个页写入缓存,直接写内存。
比如,做页表用的页,已经存储在TLB中了,可能不需要再缓存了。因此必须为1

TLB是translation lookaside buffer的简称。转译后备缓冲器,也被翻译为页表缓存转址旁路缓存,为CPU的一种缓存,由存储器管理单元用于改进虚拟地址到物理地址的转译速度。当前所有的桌面型及服务器型处理器(如 x86)皆使用TLB。现在64位的系统都用4级页表!TLB就是通过命中加快这个进度的。TLB从线性地址直接命中到物理地址。

A--位5是已访问(Accessed)标志。当处理器访问页表项映射的页面时,页表表项的这个标志就会被置为1。当处理器访问被页目录表项映射的任何页面时,页目录表项的这个标志就会被置为1。处理器只负责设置该标志,操作系统可通过定期地复位该标志来统计页面的使用情况。写0就可以了。

D--位6是页面已被修改(Dirty)标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。写0即可。

AVL--该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。设为00就可以了。

从另外一个角度来说,页表也实现了向磁盘的转化,这是段表所不能的。

好,接下来填充页表,页表第一项就是它自己,页表一共1024项对应物理地址。这个是最真实的页表了,也对应着开头4MB,结尾4KB的。。。简直不要太棒!

好,找到段的线性地址后,就开始分页处理

分别为10*10*12=32位,但为什么留出了20位的空位?可能是64位的原因吧。

页目录中每条页目录项指向一个页表,这肯定没问题,页表保存在物理内存中,这也没问题。页目录项的高 20 位等于它对应的页表所在位置的物理地址的高 20 位。这样1024个页目录项刚好对应 1024 个页表。那么低12位呢?由于4K对齐,所以用20位可以表示一个地址。也就是说,页表只有前20位有效,低12位都是0。页表本身就是一个页,所以需要4K对齐。

重新来,总的地址为 0x0028,0000 (4字节,一个字节8位,用两个十六进制数表示,4字节就0个16进制数。) 前20位是00280,也就是说,,,,前10位是序号,还行,都是0,找的是第一个页表,找到第一个页表后,接下来的序号是280,1010000000,这个序号,后面是3个0,,,你可以x一下看看是不是。

明白了,它跟显存给重了,,,,必须重新指定缓存。。。

显存地址为0x000a0000,首部4K页表为0~0x1000,

不要忘了page map。

再重新复习一下:

0x280000前,是一段GDT,紧接着是一段IDT,在前面,应该没有了,毕竟GDTR和IDTR都由280000之后实现了,(也就是说,IDT也必须在之后再重新赋值一次)。再在前面放上4K页目录表也没有什么大不了的,然后目前所有代码都被限制在4MB内(实际上也没什么大不了的)。

最后还有一个内存地图,这个内存地图掌管着分配,页表是看不出分配的效果的。毕竟1024*1024*4K=4G,那么就是1024*1024位=131072字节,也就是0x20000

对于如何分配,怎样做,才能有效防止内存碎片呢?

开启分页后,对于分段有什么影响呢?是先开启分页还是先开启分段,还是二者没有任何影响呢?

0~0x00001000, 4kb, 内核的4kb页表,全用上,1024*4k=4MB的内核空间。

0x000a0000~0x000afa00, 200*320=0xfa00, 显存映射地址。

0x0024e800~0x0026e800, 0x00020000, 内存位图地址。
0x0026e800~0x0026f800,  0x00001000, 4kb, 内核页目录表,只有第一项可用。
0x0026f800~0x00270000, 0x00000200, IDT表。
0x00270000~0x00280000, 0x00010000,GDT表。

启动的时候,一切都没关系,等到了0x00280000处,

先分页,先初始化页表,填满,然后初始化页目录表,填入这唯一的页表,登记页目录表,打开cr3,分页完毕后,理论上没有任何变化。

然后初始化GDT,初始化IDT,初始化外设,初始化时钟,打开中断什么的好好安排一下。

00000000000000000000,000100011111

4k是0x1000,应该跟12位进行对齐,这样页表充分利用20位,可以抵达4G的位置。

                  0000 0000 0000

         0001 0000 0000 0000

那么第一个是01貌似就没有什么问题了。但是8位最多256个啊?1024需要10位,整体是20位,后面是12位,其实整体仍然是个地址,后面12位就是一个4k,因此中间10位才是有用的。那么页目录表呢?按理说,它就要高10位,整个地址是怎么构成的呢?这里一个线性地址,它的高10位(1024项)找页目录表(由cr3指定),得到页表地址,那么就是前20位有效了,是线性地址的前10位,索引到页目录表的一项20位,加上12个0就是页表地址,找到页表后,用中间10位索引表项,得到真正的20位高地址,中间10位找页表,低12位找偏移量,就是页目录表的高10位+页表的低10位+偏移量。

现在的确可以运作了。

注意,程序可用意味着无效的。

0000 0000 0111

0x007

  • P 位0是存在标志,用于指明表项对地址转换是否有效。 P=1 表示有效;P=0 表示无效。在页转换过程中,如果涉及的 页目录或页表的表项无效,则会导致一个异常。 P必为1。

  • R/W 位1是读/写标志。如果等于1,表示页面可以被读、写或执行。如果为0,表示页面只读或可执行。当处理运行在超级用户特权级(级别0、1或2)时,则R/W位不起作用。页目录项中的R/W位对其所映射的所有页面起作用。虽然不起作用,但是必须为1。

  • U/S 位2是用户/超级用户标志。如果为1,那么运行在任何特权级的程序都可以访问该页面,如果是0,那么页面只能被允许在超级特权级上的程序访问。页目录项中的U/S位对其所映射的所有页面起作用。必须为1。

  • A 位5是已访问标志。当处理器访问页表项映射的页面时,页表项的这个标志就会被置为1.当处理器访问页目录表项映射的任何页面时,页表目录项的这个标志就会被置为1。处理器只负责设置该标志位,操作系统可通过定期地复位该标志来统计页面的使用情况。操作系统用,因为操作系统初期并不会访问任何页表和页面,置0.

  • D 位6是页面已被修改标志。当处理器对一个页面执行写操作时,就会设置对应页表表项的D标志。处理器并不会修改页目录项中的D标志。因为最初处理器不会对一个页面执行写操作,因此置0.

  • AVL 该字段保留专供程序使用。处理器不会修改这几位,以后的升级处理器也不会。程序用的,大概就是随便用的意思,置00吧。

还是不行,难道是分段在里面被清空了吗?没有,4Kb才0x1000,都到了0x8200地方了,根本没摸到,要是真摸到了,绝对卡机。

实际上没有任何关系。。。可能恰巧不能造成实质性伤害。。。

之所以没关系,是因为ip只是一直下去了,jmp一下就完蛋了吧。

来推算一下吧,倘若要jmp 0x00280023地址,前10位都是0,找页目录表的第0位,内容为0,找到地址为0的页表,然后找中间10位,中间10位是 10 1000 0000,进行查表,发现应该就是它。这个表项的地址为 1010000000B*4=0x280*4=0xA00,在这个地址上,我们将可以看到0x00280007

推断完全正确!

GDT被load进去后,好像会重新编排啊。。。。而且用了这么就也没有出现什么问题。。。不管了,现在是分页。。。

1024*4=4096,4K后就是0了。

重新弄一下qemu吧,这个qemu是黑盒,添加什么命令都是错。

下个qemu都这么难。

分段必然在分页之前,只有分段才能进入32位。

发现GDT被随便赋值后一点问题都没有。这究竟是怎么做到了啊?

但是之前载入GDT,那个惨啊。。。现在又因为为什么可以而。。。过犹不及。感觉一旦解析不成功就用原来的。。。

不不不,虽然暂时看不出来,但是只要有跳跃,那就完蛋了。

现在两个页表肯定没问题,是不是页表的理解有误?

至少在这里,线性地址=物理地址,还是4K分页后的。你要知道,页目录内的和页表内的项都是物理地址!

没分段,但是我有分页行不行啊?分页就是分不了,现在有什么症状?

只要置1,就完蛋。在16位的时候,稍微一长就闪退,说是能用32位寄存器其实用不了,

按理说分页后并没有什么不同啊,线性地址映射后应该还是线性地址,

CR0中包含了6个预定义标志,

第0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE位置1,则保护模式启动,如果PE=0,则在实模式下运行。

第1位是监控协处理位MP(Moniter coprocessor),它与第3位一起决定:当TS=1时操作码WAIT是否产生一个“协处理器不能使用”的出错信号。

第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器。

第3位是任务转换位(Task Switch),当一个任务转换完成之后,自动将它置1。随着TS=1,就不能使用协处理器。

第4位是微处理器的扩展类型位ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是387浮点协处理器。CR0的第31位是分页允许位(Paging Enable),它表示芯片上的分页部件是否允许工作。

0026e800,这个,最后12位不能用,除非26e000才对吧。。。

解决了,可恶心死我了。

操作系统的4K与其他4K不同,操作系统的4K是分页前实现的,更不会用到Memory_use_4k,而其他内存正是用这个 函数分配的。

奇怪了,这个函数根本没用到,为什么还会。。。

放在其他地方可以,放在那个文件里就不行。。。只能说绝了。先放到PDT这里吧。

我明白了,boot要用。。。这个文件,太大就会溢出。。。行,问题解决了。

好了,Memory_use_4K也解决了,现在用一下,它应该正好是4M位置,0x400000

每日小常识:

1MB从0x10 0000开始,记住是5个0哦。

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值