Linux内核6.6 内存管理(15)代码导读——关于物理内存完整布局的思考

arm64_memblock_init

https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/kernel/setup.c#L329

1)s64 linear_region_size = PAGE_END - _PAGE_OFFSET(vabits_actual);计算内核能访问Physical memory的范围

2)适配KVM场景

3)memblock_remove(1ULL << PHYS_MASK_SHIFT, ULLONG_MAX);移除超PA支持范围的内存

4)memstart_addr = round_down(memblock_start_of_DRAM(), ARM64_MEMSTART_ALIGN);确定物理内存起始地址

5)

max(线性映射所能覆盖的最大物理地址,内核数据段结束的物理地址)

情况1:线性映射覆盖内核(A ≥ B)

┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ 物理地址范围  │ 0 ~ S-1      │ S ~ B-1      │ B ~ A-1      │ A ~ D        │ D+1 ~ ULLONG_MAX │
│ 标注         │ 低地址空闲区  │ 内核区       │ 线性映射内空闲区 │ 超线性映射高地址区 │ 无效高地址区      │
│ 内容         │ (未使用)    │ 内核代码/数据  │ 可访问物理内存  │ 不可访问内存    │ (硬件不可达)    │
│ 操作         │ 不处理        │ 保留(不可移除) │ 保留(可访问)  │ 移除(memblock_remove)│ 移除(memblock_remove)│
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 关键参数标注  │              │ ↓ S=memstart_addr │ ↓ B=__pa_symbol(_end) │ ↓ A=S+linear_size │ ↓ D=memblock_end_DRAM │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘

情况2:线性映射未覆盖内核(A < B)

┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
│ 物理地址范围  │ 0 ~ S-1      │ S ~ A-1      │ A ~ B-1      │ B ~ D        │ D+1 ~ ULLONG_MAX │
│ 标注         │ 低地址空闲区  │ 线性映射内内核区 │ 超线性映射内核区 │ 超线性映射高地址区 │ 无效高地址区      │
│ 内容         │ (未使用)    │ 内核代码/数据  │ 内核代码/数据  │ 不可访问内存    │ (硬件不可达)    │
│ 操作         │ 不处理        │ 保留(不可移除) │ 保留(不可移除) │ 移除(memblock_remove)│ 移除(memblock_remove)│
├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
│ 关键参数标注  │              │ ↓ S=memstart_addr │ ↓ A=S+linear_size │ ↓ B=__pa_symbol(_end) │ ↓ D=memblock_end_DRAM │
└──────────────┴──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘

我们最起码保证内核不会被裁剪,当内核完成早期初始化后,会建立完整的内核页表。内核会为自身所有区域(代码段 _text、数据段 _data、bss 段等,包括 A~B-1 区域)建立独立的虚拟地址映射,这些映射与线性映射无关;后续内核会通过 paging_init 等函数完成页表初始化,将内核所有物理区域的映射写入页表,彻底解决 “线性映射未覆盖” 的问题

问题1:为什么需要映射呢,我理解的是,可以直接让内核直接从内存起始地址为0的地方直接放

解答:

一.问题:直接将内核放物理地址0的硬件冲突

┌───────────────────────────────────────────── 物理地址空间 ──────────────────────────────────────────────┐
│ 0x00000000 ~ 0x0000FFFF  │ 0x00010000 ~ 0x00FFFFFF  │ 0x01000000 ~ ...  │ ...  │ 高地址物理内存       │
│ ┌─────────────────────┐  │ ┌─────────────────────┐  │ ┌─────────────┐    │      │ ┌─────────────┐    │
│ │  BIOS/ROM固件区域   │  │ │  中断向量表/外设寄存器 │  │ │  空闲内存   │    │      │ │  空闲内存   │    │
│  │  (硬件必须占用)   │  │ │  (硬件必须占用)   │  │  │             │    │      │ │             │    │
│ └─────────────────────┘  │ └─────────────────────┘  │ └─────────────┘    │      │ └─────────────┘    │
│                          │                          │                    │      │                    │
│  ↓ 若强行放内核 →        │                          │                    │      │                    │
│ ┌─────────────────────┐  │                          │                    │      │                    │
│ │  内核代码/数据       │  │                          │                    │      │                    │
│ │  (覆盖固件/硬件数据)│  │                          │                    │      │                    │
│ └─────────────────────┘  │                          │                    │      │                    │
└───────────────────────────────────────────────────────────────────────────────────────────────────────┘
→ 结果:硬件固件被覆盖→启动失败;中断向量表被改→CPU无法响应中断;外设寄存器被写→硬件瘫痪

二.解决:通过“虚拟地址映射”隔离硬件与内核的示意图

1.物理地址空间(硬件真实地址)

┌───────────────────────────────────────────── 物理地址空间 ──────────────────────────────────────────────┐
│ 0x00000000 ~ 0x0000FFFF  │ 0x00010000 ~ 0x00FFFFFF  │ 0x01000000 ~ 0x02FFFFFF  │ 0x03000000 ~ ...     │
│ ┌─────────────────────┐  │ ┌─────────────────────┐  │ ┌─────────────────────┐  │ ┌─────────────┐     │
│ │  BIOS/ROM固件区域   │  │ │  中断向量表/外设寄存器│  │ │  内核实际存放区域   │  │ │  其他内存   │     │
│ │  (硬件保留,不可写)│  │ │  (硬件保留,不可写)│  │ │  (空闲物理内存)   │  │ │             │     │
│ └─────────────────────┘  │ └─────────────────────┘  │ └─────────────────────┘  │ └─────────────┘     │
└───────────────────────────────────────────────────────────────────────────────────────────────────────┘

2.内核虚拟地址空间(内核看到的地址)

┌───────────────────────────────────────────── 内核虚拟地址空间 ────────────────────────────────────────────┐
│ 0xFFFF000000000000 ~ 0xFFFF000001FFFFFF  │ 0xFFFF000002000000 ~ ...  │ ...  │  其他虚拟地址区域        │
│ ┌─────────────────────────────────────┐  │ ┌─────────────┐           │      │ ┌─────────────┐        │
│ │  内核虚拟地址区域(线性映射)       │  │ │  其他虚拟   │           │      │ │  动态映射   │        │
│ │  → 映射到物理地址 0x01000000 ~ ...  │  │ │  内存区域   │           │      │ │  区域       │        │
│ └─────────────────────────────────────┘  │ └─────────────┘           │      │ └─────────────┘        │
└───────────────────────────────────────────────────────────────────────────────────────────────────────┘
→ 核心:内核代码在虚拟地址空间中“逻辑连续”,但实际映射到物理地址中“远离硬件保留区”的空闲内存,既不冲突又安全

3.映射关系表(MMU硬件维护)

内核虚拟地址范围

对应的物理地址范围

权限设置

0xFFFF000000000000 ~ ...

0x01000000 ~ ...

内核可读写,用户不可访问

...

0x00000000 ~ 0x00FFFFFF

只读(仅内核可访问硬件区域)

...

其他物理内存

按需分配权限

6)

通过上移线性映射的物理起点(memstart_addr 和裁剪低地址内存,让最终管理的物理内存大小刚好等于线性映射范围,确保内核能通过线性映射访问所有可用内存。

问题2:把内核地址上移动到物理内存地址上限,然后裁剪低地址部分,那么低地址裁剪的部分用来干嘛?那为什么不能让内核用低地址,非要让内核用高地址呢?

1.硬件/固件的专属区域(bios/bootloader/cpu终端向量表)优先占用低地址的极小固定区域,剩余地址交给用户态使用

2.地址空间划分:如 64 位系统通常将 0xFFFF000000000000 以上作为内核空间),低地址留给用户程序(如应用、进程)使用。内核用高地址,能避免与用户程序的地址冲突,简化内存管理逻辑。

┌─────────────────────────────────────────────────────────────────┐
│ 物理地址空间(真实硬件地址)                                     │
│ ┌───────────┐ ┌───────────────┐ ┌───────────────┐              │
│ │ 硬件专属区 │ │ 用户可用物理区 │ │ 内核专属物理区 │              │
│ │ (低地址) │ │ (中低地址)   │ │ (高地址)     │              │
│ └───────────┘ └───────────────┘ └───────────────┘              │
        ↑                ↑                  ↑
        │                │                  │
        ▼                ▼                  ▼
┌─────────────────────────────────────────────────────────────────┐
│ 虚拟地址空间(内核+用户)                                       │
│ ┌───────────────┐                ┌───────────────────────────┐ │
│ │ 用户虚拟空间  │                │ 内核虚拟空间              │ │
│ │ (映射用户区) │                │ (映射硬件+用户+内核区) │ │
│ └───────────────┘                └───────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

7)

这是给initrd页面对齐的代码

问题1.此时尚未开启页面管理,为什么需要页面对齐?

1. 为后续 MMU 页表映射做准备(MMU 只认整页);

2. 避免与后续 memblock / 页分配器的内存块冲突;

3. 简化后续 initrd 解压 / 读取的跨页处理,提升效率。

问题2.如何使得页面对齐?

以PAGE_SIZE=4KB为例

  • phys_initrd_start = 0x80001234(未对齐起始地址)
  • phys_initrd_size = 0x5678(未对齐大小)
  • mask = 0xFFFPAGE_SIZE-1),~mask = 0xFFFFF000PAGE_MASK

  • PAGE_SIZE:4KB = 0x1000(十六进制),即页的最小单位;
  • PAGE_MASK:4KB 对应的页掩码为 ~(PAGE_SIZE - 1) = 0xFFFFF000(十六进制),作用是 “屏蔽低 12 位地址”,实现向下对齐

PAGE_MASK:https://elixir.bootlin.com/linux/v6.11/source/arch/arm64/include/asm/page-def.h#L16

PAGE_ALIGH的本质: https://elixir.bootlin.com/linux/v6.11/source/include/uapi/linux/const.h#L32

步骤 1:计算 base(向下对齐基地址)
base = phys_initrd_start & PAGE_MASK  
= 0x80001234 & 0xFFFFF000  // 按位与运算,保留高地址,清除低12位  
= 0x80001000  // 结果为4KB对齐的基地址

步骤 2:计算 phys_initrd_start + phys_initrd_size(原始结束地址)
phys_initrd_start + phys_initrd_size  
= 0x80001234 + 0x5678  
= 0x800068AC  // 未对齐的结束地址

步骤 3:用 __ALIGN_KERNEL_MASK 对齐结束地址
PAGE_ALIGN(0x800068AC)  
= ((0x800068AC) + 0xFFF) & 0xFFFFF000  // x + mask 后与 ~mask 按位与  
= (0x800068AC + 0xFFF) & 0xFFFFF000  
= 0x800078AB & 0xFFFFF000  
= 0x80007000  // 对齐后的结束地址(4KB对齐)

计算 size(对齐后的总大小)
size = 0x80007000(对齐后结束地址) - 0x80001000(base)  
= 0x6000  // 24KB,正好是6个4KB页(4KB×6=24KB)

问题3.initrd放在哪?initrd 是 “内核专属物理区的临时子区域”

┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 地址范围                │ 归属          │ 子区域/用途说明                          │ 访问权限               │
├─────────────────────────┼───────────────┼───────────────────────────────────────────┼────────────────--------┤
│ 0x00000000 ~ 0x0000FFFF │ 硬件专属区    │ BIOS/UEFI 启动代码、中断向量表            │ 只读(内核可访问,用户不可)│
│ 0x00010000 ~ 0x000FFFFF │ 硬件专属区    │ 外设寄存器(UART/GPIO/定时器等)          │ 读写(仅内核可操作)      │
│ 0x00100000 ~ 0x7FFFFFFFF │ 用户可用物理区 │ 用户程序未来的物理内存(堆/栈/代码)      │ 读写(内核管控,用户可访问)│
│ 0x80000000 ~ 0x8FFFFFFF  │ 内核专属物理区 │ ① 内核代码段(_text)、数据段(_data)    │ 读写(仅内核可访问)      │
│                         │               │ ② initrd 物理区域(base ~ base+size)     │ 读写(仅内核可访问,保留)│
│ 0x90000000 ~ 0xFFFFFFFF │ 内核专属物理区 │ 线性映射覆盖的其他高地址内存(如内核堆)  │ 读写(仅内核可访问)      │
└─────────────────────────────────────────────────────────────────────────────────────────┘

#C++##嵌入式##嵌入式软开##通信硬件人笔面经互助##牛客创作赏金赛#
全部评论

相关推荐

评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务