自学操作系统一年多以来,遇到的最大的坎就是内存管理。最近捧着《深入Linux内核架构》还有《Understand the Linux Kernel》这两本大块头的书死命地吭,然后联系之前的已学的内容以及模糊的地方,总算是有点清晰的感觉了,赶紧记下来。
各种地址
内存管理一开始让人很困惑的一点就是有很多以“地址”结尾的术语,让人有点摸不着北,所以我想根据自己的理解总结一下让人困惑的“各种地址”:
逻辑地址(Logical Address)
为了保证向后的兼容性,Intel 根据 8086 在 16bit 时代的寻址模式——段加偏移,设计出了分段模式,所有的段地址都保存在了 GDT 里(当然还有 LDT 和 IDT 等,不再详述),偏移地址则一般在通用寄存器,在开启了保护模式之后,由于所有的通用寄存器的字长都是 32 bit,所以可以寻址的地址区间增加到了 4G 。这里的段地址是 16bit,偏移地址是 32bit,所以最终这两个地址共 48bit 组成了逻辑地址。虚拟地址/线性地址 (Virtual Address/Linear Address)
看了很多资料,既有说这两者是一样的,又有说这两者有些许的区别。不管,我个人是倾向于前者的。要理解这两个概念,我们得先来理解另外一个有点类似的概念:地址空间和线性地址空间(Address space/Linear Address)。关于这个概念,CSAPP 书中的解释十分的精彩,这里让我来重新表述一下:
地址空间,就是一个集合,集合的成员是说能够寻址的地址。如果成员是呈线性增长的,那么就成为线性地址空间。
有了这个定义之后,我们以后遇到类似的说法就不怕不怕啦。例如后文会出现的内核线性地址空间(Kernel linear address space) 我们就可以清楚地知道它代表的就是内核说能够寻址的范围。
在理解了上面的两个重要概念之后,虚拟地址与线性地址的含义也应该很清楚了。一般而言,在 32bit 的平台里,虚拟地址/线性地址也是 32bit 长的,同样的 64bit 的也是如此。
然后再说一下,上面的逻辑地址的两个部分直接相加起来(没错,跟 8086 的先偏移在相加是不一样的)就是虚拟地址/线性地址。
- 物理地址(Physical Address)
物理地址就是机器本身 RAM 的真实地址。也是我们寻址的最终意图。这里有一点要提一下,物理地址空间跟线性地址空间是没有关心的,如果你的机器是 32bit 的,那么无论你的 RAM 是多大,线性地址空间都是 0 ~ 0xffffffff 。
分段与分页
这两种模式其实都是为了满足日益增长的内存容量而提出的寻址模式。不过现在分页模式已经可以很好地满足需要了,但是为了保证兼容性,IA 平台还是保留了分段机制。不过,为了不与分页机制相冲突,Intel 提出了平坦内存模式(flat mode),具体做法是将段地址全部置为零。不过这里有一个问题,为了保证内核线性地址都是在 3GB 以上(关于这个我下面会详细说明),在编译链接的时候,所有的符号都加上 3GB 的偏移(具体可以看一下内核的编译脚本 vmlinux.lds.S)。但是这就出现问题了:在开启分页模式之前,线性地址 = 物理地址,即段地址 + 偏移地址 = 物理地址,但是我们提到,在平坦模式下,段地址都是 0 ,所以这里偏移地址都是物理地址,从而导致了最终的物理地址都大了 3GB ,所以内核在未开启分页模式前,为了保证正确寻址,使用了一个宏 pa
来减去这多出来的 3GB。
然后,开启了分页模式之后,便可以放心的使用虚拟地址了。在分页机制中使用页表来管理虚拟地址,一般页表大小为 4KB (也有 4MB 的大页表,不过这里不讨论),这样问题就来了,如果 RAM 有 4GB 的话,那么将需要一百万个页表,这样就造成了巨大的空间浪费。所以现行采用的是多级页表的方式。在 32bit 一般是 2 个页表,一个称作页目录(Page Directory),一个称作页表(Page Table Entry);64 bit则是 4 个,其中三个页目录(Page Global Directory,简称 PGD,Page Upper Directory/PUD,Page Middle Directory/PMD),一个页表。多级页表和单个页表其实可以用乘法和加法来类比,前者相当于 1024x1024,后者则是 1048676 个 1 相加,这样前者只需 1024x2 = 2048 个页表,后者则需要 1048576 个页表!
然后,一个虚拟地址就被分割成 n + 1 段了,n 是页表的个数,最后的那个 1 是偏移(offset )。这样,各个页表的内容都指向了下一个页表的地址,当然,页表项指向的是物理页。哦,前面忘记说了,物理页我们成为页帧(frame )。
Author: simowce
Permalink: https://blog.simowce.com/linux-kernel-memory-management/
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可。