《Linux内核完全注释》学习笔记:2.5 Linux内核对内存的使用方法
在Linux 0.11内核中,为了有效地使用机器中的物理内存,
内存被划分成几个功能区域,如图2-9所示。
图2-9 物理内存使用的功能区域分布图
- Linux内核程序占据在物理内存的开始部分,
- 接下来是用于供硬盘或软盘等块设备使用的高速缓冲区部分。
当一个进程需要读取块设备中的数据时,系统会首先将数据读到高速缓冲区中;
当有数据需要写到块设备上去时,系统也是先将数据放到高速缓冲区中,然后由块设备驱动程序写到设备上。
- 最后部分是供所有程序可以随时申请使用的主内存区。
- 内核程序在使用主内存区时,也同样要首先向内核的内存管理模块提出申请,在申请成功后方能使用。
- 对于含有RAM虚拟盘的系统,主内存区头部还要划去一部分,供虚拟盘存放数据。
由于计算机系统中所含的实际物理内存容量是有限的,因此CPU中通常都提供了内存管理机制对系统中的内存进行有效的管理。
在Intel CPU中,提供了两种内存管理(变换)系统:
- 内存分段系统(Segmentation System)
- 分页系统(Paging System)。
而分页管理系统是可选择的,由系统程序员通过编程来确定是否采用。为了能有效地使用这些物理内存,Linux系统同时采用了Intel CPU的内存分段和分页管理机制。
在Linux 0.11内核中,当进行地址映射时,我们需要首先分清3种地址以及它们之间的变换概念:
- 程序(进程)的逻辑地址;
- CPU的线性地址;
- 实际物理内存地址。
1.逻辑地址(Logical Address)
是指由程序产生的与段相关的偏移地址部分。
在Intel保护模式下就是指程序执行代码段限长内的偏移地址(假定代码段、数据段完全一样)。
应用程序员仅需与逻辑地址打交道,而分段和分页机制对他来说是完全透明的,仅由系统编程人员涉及。
2.线性地址(Linear Address)
是逻辑地址到物理地址变换之间的中间层。
程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。
如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4GB。
3.物理地址(Physical Address)
是指出现在CPU外部地址总线上的寻址物理内存的地址信号,是地址变换的最终结果地址。
如果启用了分页机制,那么线性地址会使用页目录和页表中的项变换成物理地址。如果没有启用分页机制,那么线性地址就直接成为物理地址了。
虚拟内存(Virtual Memory)
是指计算机呈现出要比实际拥有的内存大得多的内存量。
因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在具有有限内存资源的系统上实现。
一个很恰当的比喻是:你不需要很长的轨道就可以让一列火车从上海开到北京。你只需要足够长的铁轨(比如说3km)就可以完成这个任务。采取的方法是把后面的铁轨立刻铺到火车的前面,只要你的操作足够快并能满足要求,列车就能像在一条完整的轨道上运行。
这也就是虚拟内存管理需要完成的任务。在Linux 0.11内核中,给每个程序(进程)都划分了总容量为 64MB 的虚拟内存空间。因此程序的逻辑地址范围是 0x0000000 到 0x4000000。
64MB = 2^(6+10+10)
0x4000'000 6*4+2=26 位
有时我们也把逻辑地址称为虚拟地址。因为与虚拟内存空间的概念类似,逻辑地址也是与实际物理内存容量无关的。
在内存分段系统中,一个程序的逻辑地址是通过分段机制自动地映射(变换)到中间层的线性地址上。每次对内存的引用都是对内存段中内存的引用。当一个程序引用一个内存地址时,通过把相应的段基址加到程序员看得见的逻辑地址上就形成了一个对应的线性地址。此时若没有启用分页机制,则该线性地址就被送到CPU的外部地址总线上,用于直接寻址对应的物理内存。
若采用了分页机制,则此时线性地址只是一个中间结果,还需要使用分页机制进行变换,再最终映射到实际物理内存地址上。与分段机制类似,分页机制允许我们重新定向(变换)每次内存引用,以适应我们的特殊要求。
使用分页机制最普遍的场合是当系统内存实际上被分成很多凌乱的块时,它可以建立一个大而连续的内存空间的映像,好让程序员不用操心和管理这些分散的内存块。
分页机制增强了分段机制的性能。页地址变换是建立在段变换基础之上的。任何分页机制的保护措施并不会取代段变换的保护措施,而只是进行更进一步的检查操作。
因此,CPU进行地址变换(映射)的主要目的是为了解决虚拟内存空间到物理内存空间的映射问题。
虚拟内存空间的含义是指一种利用二级或外部存储空间,使程序能不受实际物理内存量限制而使用内存的一种方法。通常虚拟内存空间要比实际物理内存量大得多。
那么虚拟内存空间管理是怎样实现的呢?
原理与上述列车运行的比喻类似。
- 首先,当一个程序需要使用一块不存在的内存时(即在内存页表项中已标出相应内存页面不在内存中),CPU就需要一种方法来得知这个情况。
这是通过80386的页错误异常中断来实现的。
- 当一个进程引用一个不存在页面中的内存地址时,就会触发 CPU 产生页出错异常中断,并把引起中断的线性地址放到 CR2 控制寄存器中。
- 因此处理该中断的过程就可以知道发生页异常的确切地址,从而可以把进程要求的页面从二级存储空间(比如硬盘上)加载到物理内存中。
- 如果此时物理内存已经被全部占用,那么可以借助二级存储空间的一部分作为交换缓冲区(swapper)把内存中暂时不使用的页面交换到二级缓冲区中,然后把要求的页面调入内存中。
这就是内存管理的缺页加载机制,在Linux 0.11内核中是在程序 mm/memory.c 中实现的。
Intel CPU使用段(segment)的概念来对程序进行寻址。
每个段定义了内存中的某个区域以及访问的优先级等信息。
而每个程序都可由若干个内存段组成。
程序的逻辑地址(或称为虚拟地址)即是用于寻址这些段和段中具体地址位置。
在Linux 0.11中,程序逻辑地址到线性地址的变换过程使用了 CPU 的全局段描述符表 GDT 和局部段描述符表 LDT 。
- 由 GDT 映射的地址空间称为全局地址空间,
- 由 LDT 映射的地址空间则称为局部地址空间,
而这两者构成了虚拟地址空间。具体的使用方式见图2-10。
图中画出了具有两个任务时的情况。对于中断描述符表 IDT ,它是保存在内核代码段中的。由于在Linux 0.11内核中,内核和各任务的代码段和数据段都分别被映射到线性地址空间中相同基址处,且段限长也一样,因此内核和任务的代码段和数据段都分别是重叠的。另外,Linux 0.11内核中没有使用系统段描述符。
内核和各任务的代码段和数据段都分别被映射到线性地址空间中相同基址处,且段限长也一样,因此内核和任务的代码段和数据段都分别是重叠的。
这段话理解上很奇怪,线性地址空间怎么会是重叠的呢?按照 2.4 节的说法是指任务 0 与内核的重叠吗?可是前面说的又是内核和各任务的……还是说这个线性地址空间实际上应该理解位虚拟地址或者说逻辑地址才对。
图2-10 Linux系统中虚拟地址空间分配图
内存分页管理的基本原理是将整个主内存区域划分成以 4096B 为一页的内存页面。
程序申请使用内存时,就以内存页为单位进行分配。在使用这种内存分页管理方法时,每个执行中的进程(任务)可以使用比实际内存容量大得多的连续地址空间。对于Intel 80386系统,其CPU可以提供多达4GB的线性地址空间。
对于Linux 0.11内核,系统设置全局描述符表GDT中的段描述符项数最大为256,其中两项空闲、两项系统使用,每个进程使用两项。
因此,此时系统可以最多容纳 (256-4)/2=126 个任务,并且虚拟地址范围是 (256-4)/2×64MB 约等于 8GB。
但0.11内核中人工定义最大任务数 NR_TASKS=64 个,每个进程虚拟地址范围是64MB,并且各个进程的虚拟地址起始位置是任务号×64MB。因此所使用的虚拟地址空间范围是 64MB×64=4GB,如图2-11所示。4GB正好与CPU的线性地址空间范围或物理地址空间范围相同,因此在0.11内核中比较容易混淆三种地址概念。
图2-11 Linux 0.11虚拟地址空间的使用示意图
进程的虚拟地址需要
- 首先通过其局部段描述符变换为 CPU 整个线性地址空间中的地址,
- 然后再使用页目录表PDT(一级页表)和页表PT(二级页表)映射到实际物理地址页上。因此两种变换不能混淆。
为了使用实际物理内存,每个进程的线性地址通过二级内存页表动态地映射到主内存区域的不同内存页上。因此每个进程最大可用的虚拟内存空间是64MB。每个进程的逻辑地址通过加上任务号×64MB,即可转换为线性地址。不过在注释中,我们通常将进程中的地址简单地称为线性地址。
有关内存分页管理的详细信息,可参见第10章开始部分的有关说明。
从Linux内核0.99版以后,对内存空间的使用方式发生了变化。每个进程可以单独享用整个4GB的地址空间范围。由于篇幅所限,这里对此不再说明。
说实话笔记做完也还是有一堆疑惑不解的,有些地方的问题我都在想到底是错字还是我没有理解的关系。
下面给出的参考文章多了一部分的内容有助于理解但也依然没有完全解惑,希望读下去后能解惑吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~