Professional Linux Kernel Architecture(二)内存管理

1 物理内存模型

参考了:https://www.cnblogs.com/xelatex/p/3491301.html

物理内存模型主要分为两种:UMA(Uniform Memory Access,一致性内存访问)和NUMA(Non-Uniform Memory Access,非一致性内存访问)。UMA模型是指物理内存是连续的,SMP系统中的每个处理器访问各个内存区都是同样快的;而NUMA模型则是指SMP中的每个CPU都有自己的物理内存区,虽然CPU可以访问其他CPU的内存区,但是要比访问自己的内存区慢得多。我们一般使用的物理模型都是UMA模型。为了NUMA模型,Linux提供了三种可能的内存布局配置:Flat Memory, Sparse Memory, Discontiguous Memory。

物理内存的组织主要分为两个部分:节点(node)和内存与内存域(zone)。node主要针对NUMA设计,在NUMA的SMP系统中,每个处理器都有一个自己的node,而在UMA模型中则只有一个node。对于每个node中的内存,Linux分成了若干内存域,定义在mmzone.h的zone_type中,常用的有ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM和ZONE_MOVABLE。
其中ZONE_NORMAL是最为常用的,表示内核能够直接映射的一般内存区域;
ZONE_DMA表示DMA内存区;
ZONE_DMA32表示64位系统中对于32位DMA设备使用的内存;
ZONE_HIGHMEM表示在32位系统中,高地址内存的区域;
ZONE_MOVABLE与伙伴系统的内存碎片消除有关。

2 内核在内存中的布局

在初始化时,内核代码被加载到内存区的固定位置(0x100000)

内存第一个页框不使用,主要被BIOS用来初始化;
之后的连续640KB内存也不被内核使用;
0x9e800到0x100000主要用来映射各种ROM(通常是BIOS和显卡ROM);
在0x100000开始为内核部分,分别是代码段、数据段和附加段。

3 页表

在0.11版本的内核中,实模式下,我们经常看到如下的内存表示方式:逻辑地址:ds:si = 0x07c0:0x0000 = 0x07c00(物理地址),因为386处理器只有20位地址线,因此只能寻址2^20=1M内存,段寄存器用于存储段基址(0x07c0)。实模式下,逻辑地址到物理地址的表示方式是段基址+偏移量。实模式下的地址都是真实的物理地址,不存在虚拟地址一说。

在内核启动的过程中,地址空间的低位地址将被替换为用户空间代码,将内核代码移动到高位部分。

切换到用户空间之后,内存的表示方式发生变化,段寄存器不再存放段基址,而是存放段选择符,且有了页目录和页表的概念。此时才有虚拟地址的概念。

言归正传。Linux内核通常将处理器的虚拟地址空间划分为两部分。其中,较低且较大的部分可供用户进程使用,而较高部分则保留给内核。在上下文切换(两个用户进程之间)期间,虽然较低部分会被修改,但虚拟地址空间的内核部分始终保持不变。

3.1 页框

在Linux系统中,物理内存(RAM)被划分为固定大小的块,这些块被称为页框。内核以页框为基本单位来管理物理内存,确保内存资源的有效利用
页框的大小通常是4KB。
页框与虚拟内存之间通过页表建立映射关系
当物理内存不足时,内核会选择一些不常用的页框将其内容写入磁盘(称为换出),以释放内存空间供其他页框使用。当需要访问这些被换出的页框时,再将它们从磁盘读回内存(称为换入)。
内核会定期扫描内存中的页框,回收不再使用的内存资源(比如引用计数为0时)

3.2 页表

页表是一种数据结构,用于在用户进程的虚拟地址空间和系统物理内存(RAM)之间建立映射关系
虚拟地址空间:这是操作系统为每个进程提供的逻辑内存布局。尽管在物理内存中它可能并不是连续的,但是在虚拟地址空间中内存被视为一个连续的地址范围。虚拟地址空间使得每个进程可以认为自己独占整个内存空间,从而简化了内存管理并提高了安全性。
物理内存(RAM,页框Page Frames):物理内存是计算机中实际的内存硬件,用于存储正在执行的程序和数据。在内存管理中,物理内存被划分为固定大小的块,称为页框(Page Frames)。每个页框可以存储一个虚拟内存页的内容。

在内核代码中,页表项被分为4级,如下图

页全局目录索引(PGD Index)
页上级目录索引(PUD Index)
页中间目录索引(PMD Index)
页表索引(PT Index)

需要注意的是,目前的很多架构只使2级用页表结构。但是为了更广泛的兼容性,内核中会将2级页表结构模拟为4级页表结构。

4 内存初始化步骤

4.1 BIOS引导阶段内存的映射-machine_specific_memory_setup

引导过程中,会调用machine_specific_memory_setup来获取内存区域布局信息列表,它通过int 15H调用BIOS来实现的。

在内核引导阶段,刚开始没有操作系统,但是此阶段仍然需要掌握系统内存的基本信息,此阶段获取内存信息的途径一般通过BOIS实现的。

当CPU发出int 15H且其功能号ax=e820H时,其作用是向BIOS发起查询内存基本布局信息的请求。此时我们可以将BIOS看作内核启动前的临时的简版操作系统(因为我们可以通过BIOS访问硬盘、内存、设备,linux内核本质上也是做这个工作)。

e820H调用查询的是如下形式的内存布局信息:

wolfgang@meitner> dmesg
...
BIOS-provided physical RAM map:
BIOS-e820: 0000000000000000 - 000000000009e800 (usable) #最前面637Kib被标记为可用
BIOS-e820: 000000000009e800 - 00000000000a0000 (reserved) #480byte被标记为保留,可能用于BIOS或者硬件
BIOS-e820: 00000000000c0000 - 00000000000cc000 (reserved)
BIOS-e820: 00000000000d8000 - 0000000000100000 (reserved)
BIOS-e820: 0000000000100000 - 0000000017cf0000 (usable) #372M被标记为可用
BIOS-e820: 0000000017cf0000 - 0000000017cff000 (ACPI data) #ACPI专用
BIOS-e820: 0000000017cff000 - 0000000017d00000 (ACPI NVS)
BIOS-e820: 0000000017d00000 - 0000000017e80000 (usable)
BIOS-e820: 0000000017e80000 - 0000000018000000 (reserved)
BIOS-e820: 00000000ff800000 - 00000000ffc00000 (reserved)
BIOS-e820: 00000000fff00000 - 0000000100000000 (reserved)
...

如果较老的BIOS系统没有提供e820内存映射,内核将产生自己的内存映射。0–640 KiB 和 1 MiB 以上的内存被标记为 usable

同时,在引导过程中也可以通过命令行参数约束内存,如下:

  • mem=XXX[KkmM]:

指定可用内存的总大小(例如,mem=1G 表示 1 GiB 的 RAM)。
对于配置不寻常的系统,可用于限制内存使用量。

  • highmem=XXX[kKmM]:

高端内存是指 1 GiB 界限以上的内存(在 32 位系统上)。此参数可以覆盖检测到的高端内存大小。
可用于限制可用高端内存的数量,在某些情况下可能会提高性能

  • memmap=XXX[KkmM]@XXX[KkmM]:

在特定地址手动定义一个内存区域。
示例:memmap=128M@1G 会从 1 GiB 开始保留 128 MiB 的内存。

4.2 setup_memory

❑ 确定可用的物理页面数量(每个节点)。
❑ bootmem分配器已初始化。
❑ 然后保留各种内存区域,例如,用于运行第一个用户空间进程时所需的初始RAM磁盘。

4.3 paging_init

paging_init 函数在 Linux 内核中主要用于初始化系统的分页机制,为虚拟内存管理奠定基础。

通过调用pagetable_init,物理内存到内核地址空间的直接映射被初始化。这个过程中,低内存区域(即物理地址较低的一部分内存)中的所有页框(page frames)都被直接映射到虚拟内存空间中 PAGE_OFFSET 以上的某个区域。这种直接映射的好处是,内核可以非常方便地访问大部分可用的物理内存,而无需通过复杂的页表(page tables)进行地址转换。

举例:

假设内核直接映射的物理内存为1G,系统内存为4G,则内核地址空间:1GB(从 0xC0000000 到 0xFFFFFFFF)。

内核相对地址 0x00000000 映射到虚拟地址 0xC0000000
内核相对地址 0x00001000 映射到虚拟地址 0xC0001000
内核相对地址 0x00100000 映射到虚拟地址 0xC0100000
依此类推,直到内核相对地址 0x3BFFFFFF 映射到虚拟地址 0xFFFFFFFF

当内核需要访问地址 0x00100000 的内存时。直接访问虚拟地址 0xC0100000 即可。这就是内核页表的直接映射,此时逻辑地址不代表段选择子,它不经过页目录和页表的转换,通过一定的偏移就可以访问到真实的物理地址

posted @ 2025-04-11 16:29  zhenjingcool  阅读(49)  评论(0)    收藏  举报