标题:
探索 Linux 内存模型(4)
[打印本页]
作者:
look_w
时间:
2018-5-18 21:13
标题:
探索 Linux 内存模型(4)
Linux 中的分页模型虽然 Linux 中的分页与普通的分页类似,但是 x86 架构引入了一种三级页表机制,包括:
页全局目录 (Page Global Directory)
,即 pgd,是多级页表的抽象最高层。每一级的页表都处理不同大小的内存 —— 这个全局目录可以处理 4 MB 的区域。每项都指向一个更小目录的低级表,因此 pgd 就是一个页表目录。当代码遍历这个结构时(有些驱动程序就要这样做),就称为是在“遍历”页表。
页中间目录 (Page Middle Directory)
,即 pmd,是页表的中间层。在 x86 架构上,pmd 在硬件中并不存在,但是在内核代码中它是与 pgd 合并在一起的。
页表条目 (Page Table Entry)
,即 pte,是页表的最低层,它直接处理页(参看 PAGE_SIZE),该值包含某页的物理地址,还包含了说明该条目是否有效及相关页是否在物理内存中的位。
为了支持大内存区域,Linux 也采用了这种三级分页机制。在不需要为大内存区域时,即可将 pmd 定义成“1”,返回两级分页机制。
分页级别是在编译时进行优化的,我们可以通过启用或禁用中间目录来启用两级和三级分页(使用相同的代码)。32 位处理器使用的是 pmd 分页,而 64 位处理器使用的是 pgd 分页。
图 7. 三级分页
如您所知,在 64 位处理器中:
21 MSB 保留未用
13 LSB 由页面偏移量表示
其余的 30 位分为:
10 位用于页表
10 位用于页全局目录
10 位用于页中间目录
我们可以从架构中看到,实际上使用了 43 位进行寻址。因此在 64 位处理器中,可以有效使用的内存是 2 的 43 次方。
每个进程都有自己的页目录和页表。为了引用一个包含实际用户数据的页框,操作系统(在 x86 架构上)首先将 pgd 加载到 cr3 寄存器中。Linux 将 cr3 寄存器的内容存储到 TSS 段中。此后只要在 CPU 上执行新进程,就从 TSS 段中将另外一个值加载到 cr3 寄存器中。从而使分页单元引用一组正确的页表。
pgd 表中的每一条目都指向一个页框,其中中包含了一组 pmd 条目;pdm 表中的每个条目又指向一个页框,其中包含一组 pte 条目;pde 表中的每个条目再指向一个页框,其中包含的是用户数据。如果正在查找的页已转出,那么就会在 pte 表中存储一个交换条目,(在缺页的情况下)以定位将哪个页框重新加载到内存中。
图 8 说明我们连续为各级页表添加偏移量来映射对应的页框条目。我们通过进入作为分段单元输出的线性地址,再划分该地址来获得偏移量。要将线性地址划分成对应的每个页表元素,需要在内核中使用不同的宏。本文不详细介绍这些宏,下面我们通过图 8 来简单看一下线性地址的划分方式。
图 8. 具有不同地址长度的线性地址
预留页框Linux 为内核代码和数据结构预留了几个页框。这些页
永远不会
被转出到磁盘上。从 0x0 到 0xc0000000 (PAGE_OFFSET) 的线性地址可由用户代码和内核代码进行引用。从 PAGE_OFFSET 到 0xffffffff 的线性地址只能由内核代码进行访问。
这意味着在 4 GB 的内存空间中,只有 3 GB 可以用于用户应用程序。
如何启用分页Linux 进程使用的分页机制包括两个阶段:
在启动时,系统为 8 MB 的物理内存设置页表。
然后,第二个阶段完成对其余物理地址的映射。
在启动阶段,startup_32() 调用负责对分页机制进行初始化。这是在 arch/i386/kernel/head.S 文件中实现的。这 8 MB 的映射发生在 PAGE_OFFSET 之上的地址中。这种初始化是通过一个静态定义的编译时数组 (swapper_pg_dir) 开始的。在编译时它被放到一个特定的地址(0x00101000)。
这种操作为在代码中静态定义的两个页 —— pg0 和 pg1 —— 建立页表。这些页框的大小默认为 4 KB,除非我们设置了页大小扩展位(有关 PSE 的更多内容,请参阅 一节)。这个全局数组所指向的数据地址存储在 cr3 寄存器中,我认为这是为 Linux 进程设置分页单元的第一阶段。其余的页项是在第二阶段中完成的。
第二阶段由方法调用 paging_init() 来完成。
在 32 位的 x86 架构上,RAM 映射到 PAGE_OFFSET 和由 4GB 上限 (0xFFFFFFFF) 表示的地址之间。这意味着大约有 1 GB 的 RAM 可以在 Linux 启动时进行映射,这种操作是默认进行的。然而,如果有人设置了 HIGHMEM_CONFIG,那么就可以将超过 1 GB 的内存映射到内核上 —— 切记这是一种临时的安排。可以通过调用 kmap() 实现。
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0