内存分页分段段页式,三者有何不同?
摘要:你好,我是 yes。 关于内存访问你可能听过分段,分页,还有段页式。 但是为什么要分段?又为什么要分页? 有了分页为什么还要分段? 这就需要看一看历史的发展,知晓历史之后就知道这一切其实都是自然而然的。 这些概念也不是硬塞出来的。 正文 1
你好,我是 yes。
关于内存访问你可能听过分段,分页,还有段页式。
但是为什么要分段?又为什么要分页?
有了分页为什么还要分段?
这就需要看一看历史的发展,知晓历史之后就知道这一切其实都是自然而然的。
这些概念也不是硬塞出来的。
正文
1971 年 11 月 15 日,Intel 推出世界第一块个人微型处理器 4004(4位处理器)。
随后又推出了 8080(8 位处理器)。
那时候访问内存就只有直白自然的想法,用具体物理地址。
所有的内存访问就是通过绝对物理地址去访问的,那时候还没有段的概念。
段的概念是起源于 8086,这个 16 位处理器。
限于当时的技术背景和经济,寄存器只有 16 位,而地址总线是 20 位。
那 16 的位的寄存器如何能访问 20 位的地址?
2 的16 次方如果直着来如何能访问到 2 的 20 次方所表达的数?
直着来是不可能的,因此就需要操作一下。
也就是引入段的概念,让 CPU 通过「段基地址+段内偏移」来访问内存。
有人可能就问你这都只有 16 位,两个 16 位加起来最多只能表示 17 位呀。
你说的没错。
所以再具体一点的计算规则其实是:段基地址左移 4 位(就是乘16)再加上段内偏移,这样得到的就是 20 位的地址。
比如现在的要访问的内存地址是0x05808,那么段基地址可以是 0x0580,偏移量就是 0x0008。
这样内存的寻址空间就扩大到 20 位了。
至于为什么称之为段,其实就是因为寄存器只有 16 位一段只能访问 64 KB,所以需要移动基地址,一段一段的去访问所有的内存空间。
对了,专门为分段而生的寄存器为段寄存器,当时里面直接存放段基地址。
不过渐渐地人们就考虑到安全问题,因为在这个时候程序之间的地址没有隔离,我的程序可以访问你的程序地址,这就很不安全。
于是在 1982 年 80286 推出时,就有了保护模式。
其实就是 CPU 在访问地址的时候做了约束,会判断地址是否在允许的范围内,会判断当前的程序对目的地址是否有访问权限。
搞了个 GDT (全局描述符表)存放所有段描述符。
段寄存器里面也不是直接放段基地址了,而是放了一个叫选择子的东西。
大致可以认为就是段描述符的索引,也就是通过这个索引去找到段描述符,所以叫选择子。
这个选择子里面还有一点属性。
这个 T1 就是标明要去哪个表找,而 RPL 就是特权级了,一共分为四层,0 为最高特权级,3 为最低特权级。
当地址访问时,如果 RPL 的权限低于目标特权级(DPL)时,就会拒绝访问,于是就起到了保护的作用。
所以称之为保护模式,之前的那种没有判断权限的称之为实模式。
当时 80286 的地址总线已经是 24 位,但是用于寻址的通用寄存器还是 16 位,虽然段基地址的位数已经足够访问到 24 位(因为已经放到 GDT 中,且有 24位)。
但是因每次一段只有 64 KB,这样访问就很不方便,需要不断的更换段基地址,于是 80286 很快就被淘汰,换上了 80386。
这是 Intel 第一代 32 位处理器。
除了段寄存器还是 16 位之外,地址总线和寄存器都是 32 位,这就意味着以前为了寻址搞的段机制其实没用了。
因为单单段内偏移就可以访问到 4GB 空间,但是为了向前兼容段机制还是保留了下来,段寄存器还是 16 位是因为够用了,所以没必要扩充。
不过上有政策,下有对策。
虽说段机制保留了,但是咱可以“忽悠”着用,把段基值都设置为 0 ,就用段内偏移地址来访问内存空间就好了。
这其实就意味着每个段的起始地址都是一样的,那就等于不分段了,这就叫平坦模式。
Linux 就是这样实现的。
那为什么要分页?
因为分段粒度太粗了,导致内存碎片大,不利于管理。
当时加载到内存等于一个段都得搞到内存中,而段的范围过大,举个例子。
假设此时你有 200M 内存,此时有 3 个应用在运行,分别是 LOL、chrome、微信。
此时内存中明明有 30MB 的空闲,但是网易云加载不进来,这内存碎片就有点大了。
然后就得把 chrome 先换到磁盘中,然后再让 chrome 加载进来到微信的后面,这样空闲的 30MB 就连续了,于是网易云就能加载到内存中了。
但是这样等于要把 50MB 的内存来个反复横跳,磁盘的访问太慢了,所以效率就很低。
总体而言可以认为分段内存的管理粒度太粗了,所以随着 80386 就出来了个分页管理,一个更加精细化的内存管理方式。
简单地说就是把内存等分成一页一页,每页 4KB 大小,按页为单位来管理内存。
