我的OS | 分段和分页

今天这篇文章很长,但是如果静心读下来,一定会有所收获的。

最近,笔者一直在忙着编写切换到保护模式的代码,但由于能力不足,一直没有编写出来。

在上一篇文章中有这么一句:

我的OS | 分段和分页_第1张图片

; 设置bits31为0(为了禁止分页,我们想要使用分段)

写那个文章的时候,笔者才刚刚了解了伟大的8086CPU是怎么用16根数据总线访问1MB的内存(分段),连更伟大的386 486都没见过,更别说分页了。今天笔者也是才搞明白了到底什么是分页。

为什么到今天才明白呢。。。其实,笔者以前就听说过分页(在《30天自制操作系统》这本书中提到过),但是这本书讲的是分段。

后来又看《一个64位操作系统的设计与实现》,讲了分页,但是以笔者那时候的水平完全看不懂。

然后,笔者又借助伟大的百度谷歌不停搜索,只能搜到一些讲的很朦胧的句段。

笔者觉得,很肯能是因为只有大神才知道这种东西,而我们又看不懂大神讲的是什么。。。所以才闹到现在还是根据《一个64位操作系统的设计与实现》上讲的才明白过来

讲了这么多的废话,我们快点开始吧。首先是分段。

分段

为啥要有分段

在分段诞生之前,人们直接用绝对地址访问内存。这种方法的优点就是简单明了,比如说,你想要访问0x1234这个地方的地址,可以直接写成

0x1234

当然,有优点就肯定有缺点。这种模式的最大的缺点就是浪费。

8086CPU的配置是这样的

地址总线:20根

数据总线:16根

内存:1MB

如果没有分段机制,8086CPU要可以表示出来的最大内存地址是

(bin)1111 1111 1111 1111 = (hex)ffff

(CPU要用数据总线传输数据)

看起来还可以,但这相当于多大呢?

(bin)1111 1111 1111 111B = 32KB

明明有1MB却只能用32KB,简直太坑了。于是,人们想出来可以将地址分开表示。

如果你要表示0x1234,可以写成

0x12 0x34

这样也行,但不便于阅读。一般人们这样写

0x12:0x34

当然,写成

0x123:0x4

也可以的。

这里面,类似0x1的就是段地址,另一个就是偏移地址。

我的OS | 分段和分页_第2张图片

这样就诞生了伟大的分段系统。

保护模式下的分段系统

这种模式虽然好,但是现在的内存越来越大了,只有1MB肯定是不够的。当然,你会说换个更大的数据总线,也行。但是没有从根上解决问题。32位CPU首先就解决了这个访问内存的问题。

32位CPU创建了一个叫做GDT的东西(Global Segment Descriptor Table,全局段描述符)用来描述内存。

在32位模式下,内存的段地址不直接为段地址,而是经过GDT找到对应的段然后再获取该GDT的起始地址作为段地址。

可能光用文字不好理解,下面是图片,可能一看就懂了。

我的OS | 分段和分页_第3张图片

这里面,段寄存器的值也叫做段选择子。

有人可能会奇怪:段选择子那里明明写着0x10,为什么对应的是0x01号段呢?

这是因为CPU在处理段选择子的时候,低四位是有特殊用处,要留白。0x10转换成二进制是10000,对应的刚好是第一号段。

段信息里面大约有以下几个信息

段的起始地址

段的大小

段的属性(系统用或用户用)

基本上笔者觉得有用的就是这几个,还有几个类似于是否访问啊,几乎没有用,就不累赘了。

保护模式下面的分段模式很像一个文件夹,我们来看看

我的OS | 分段和分页_第4张图片

好,关于分段模式,我们就先讲这么多,现在来看分页模式。

分页

为什么要有个分页模式

既然已经有了分段模式了,为什么还要有个分页模式呢?

大家想象一下,如果已经有很多个程序在运行,又有一个程序要进来,它需要整整1GB的大小。

整个内存可以容纳下这个程序,但是全都是一块一块的碎片。如果没有分页模式,只能报出一个No enough memory的错误。

分页模式就可以让看起来连续的内存地址变得不连续。比如,0x7c00和0x7e00看起来只有512字节的距离,但启动分页模式后,他们可能在完全不同的地方。

分页模式是怎么工作的呢?

分页模式是建立于分段模式之上的,所以,CPU要先将地址通过分段,然后再执行分页。通过分段的结果叫做线性地址。在只开分段模式的情况下,这个地址直接作为内存物理地址,而在开启分页模式下,要再通过分页才可以得出物理地址。

假设我们有一个叫做function的函数,它的偏移地址是0x1234(相对于当前段描述符)。分页机制会先将它转换为一个8位的十六进制数字,就是

0x00001234

然后,分页机制会将它分成三部分

0x000 0x001 0x234

分别对应页表1的第0项,页表2的第一项,偏移地址为0x234。

页表的格式和GDT的格式差不多,基本是一样的。其中的页表1又叫做目录项,页表2又叫做页表项。

既然它叫做目录项,我们就用文件夹来模拟它。

我们模拟一下CPU将0x7c00转换为物理地址

0x00007c00

目录项: 0x000

页表项: 0x007

偏移地址: 0xc00

我的OS | 分段和分页_第5张图片

找到了目录项就进去

我的OS | 分段和分页_第6张图片

接下来,CPU要合成出物理地址。我们看一看页表(类似于GDT)

我的OS | 分段和分页_第7张图片

然后CPU再去找页表项0x007的页表

我的OS | 分段和分页_第8张图片


注意,这里写的是相对于上一层目录项而不是绝对地址。也就是说它的绝对地址要和页目录项结合后才能转换成物理地址

我们可以用分段机制的方法转换

段地址: 0xffff

偏移地址: 0x1000

结果: 0xffff * 16 + 0x1000 = 0xffff1000

结果是0xffff1000,就是说这个页表项的物理地址是0xffff1000,加上最开始的偏移地址0xc00得到了

段地址: 0xffff1000

偏移地址: 0xc00

结果: 0xffff1000 * 16 + 0xc00 = 0xffff1000c00

就是说,在开启分页机制后,如果页表和上面是一样的话,CPU在访问0x7c00时会得到物理地址0xffff1000c00处的内容。

怎么样,明白了吗?

你可能感兴趣的:(我的OS | 分段和分页)