Arm32 Memory Model:内核空间与用户空间的真相解析

Arm32 Memory Model:内核空间与用户空间的真相解析

📘 支持作者新书:《Yocto项目实战教程:高效定制嵌入式Linux系统》
👉 京东购买链接:https://item.jd.com/15020438.html

malloc() 一次 8 字节的小分配,到 CPU 如何找到对应物理页帧,本篇带你用 ARM32 架构梳理一遍完整流程与背后机制,并回答一个长期困惑嵌入式工程师的问题:为什么每个进程要有自己的页表?为什么内核页表却能共享?


第一部分:基本概念清晰化

我们首先厘清四个常被混淆的概念:

名称本质所属常见场景
用户态(User Mode)CPU 特权等级(PL0)CPU 运行态执行用户程序代码
内核态(Kernel Mode)CPU 特权等级(PL1)CPU 运行态执行系统调用、中断服务
用户空间(User Space)虚拟地址范围(如 0x0000_0000 ~ 0xBFFF_FFFF)进程虚拟地址空间malloc、栈、共享库等
内核空间(Kernel Space)虚拟地址范围(如 0xC000_0000 ~ 0xFFFF_FFFF)全局虚拟地址空间内核代码、内核数据结构、内核模块

✔ 注意:用户态/内核态 = CPU 运行权限;用户空间/内核空间 = 虚拟内存区域。


第二部分:虚拟地址空间划分(ARM32)

在经典的 ARMv7 架构(32-bit)中,Linux 默认使用 3GB/1GB 分割:

┌────────────────────────────┐ 0xFFFF_FFFF
│      内核空间(1GB)         │
│   内核映射、内核代码段等    │
├────────────────────────────┤ 0xC000_0000 = PAGE_OFFSET
│      用户空间(3GB)         │
│    用户程序代码/数据/堆栈    │
└────────────────────────────┘ 0x0000_0000
  • 用户空间每个进程独立:只有自己能访问其 0x00000000 - 0xBFFFFFFF 的内容。
  • 内核空间由所有进程共享映射,内容一致(只在内核态访问)。
    在这里插入图片描述

第三部分:为什么需要两套页表?

页表是虚拟地址 → 物理地址 的映射结构。ARMv7 Linux 使用二级页表(PGD → PTE)。

❶ 用户空间的页表(每进程唯一)

  • 用于映射用户态下的虚拟地址。
  • 每个进程在创建时 copy_mm()dup_mm() 时创建独立页表。
  • 有利于隔离:每个进程不能访问别的进程空间。

❷ 内核空间的页表(所有进程共享)

  • 映射高地址(如 0xC000_0000 起),用于访问内核代码、数据结构。
  • 创建于内核启动早期(paging_init()),写入所有进程页表高位部分。

✅ Linux 的“共享 + 隔离”策略是节省内存与保护隔离的折中产物。


第四部分:页表结构与寄存器关系

ARM 页表结构(以 1MB section 映射为例)

  • PGD(页全局目录)为一级表,1KB 大小,共有 4096 项,每项映射 1MB,共 4GB 空间。
// ARM Linux 页表结构(经典情况)
pgd_t *pgd;             // 每个进程独立分配
pgd[0] ~ pgd[3071]      // 映射用户空间(0~3GB)
pgd[3072] ~ pgd[4095]   // 映射内核空间(3GB~4GB)→ 所有进程共享部分

TTBR0 / TTBR1(页表基地址寄存器)

  • ARM 使用 TTBR0 保存用户空间页表基地址。
  • TTBR1 通常被 Linux 保留为内核映射。
  • 上下文切换时,Linux 内核只需要更新 TTBR0 即可实现进程切换。
write_ttbr0(pgd_base);  // 切换页表,进程调度时执行

第五部分:谁初始化、谁负责

时间点行为负责函数
内核早期启动建立内核页表、固定映射paging_init() / map_kernel()
第一个进程创建独立用户空间页表mm_init() / copy_mm()
fork 新进程拷贝父页表或写时复制dup_mm() / copy_page_range()
进程切换替换页表基地址context_switch()switch_mm()cpu_switch_mm()

第六部分:页表占用的空间与优化

  • 每个进程页表占用(粗略估算):

    • 一级页表(PGD)大小:4KB
    • 二级页表(PTE)按页分配,按使用情况动态增长
    • 共享部分(内核空间)不用重复分配
  • 优化:Linux 使用 page_table_cache 回收页表页


小结:这一切是为了什么?

目的措施
内存隔离每进程独立用户页表 + 用户态访问限制
资源节省内核页表共享 + 按需创建页表项(延迟分配)
安全性保障CPU 特权等级控制 + 页表权限保护

下一篇预告

将在《第二篇:从 malloc 到页表:一次用户态内存访问背后的全流程》中,完整展示:

  • malloc(8) 的 glibc 分配过程
  • mmap/brk 系统调用建立 VMA
  • 首次访问触发 Page Fault
  • do_page_fault → handle_mm_fault → do_anonymous_page
  • 最终到 alloc_page() → 页帧分配

敬请期待!

📘 支持作者新书:《Yocto项目实战教程:高效定制嵌入式Linux系统》
👉 京东购买链接:https://item.jd.com/15020438.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值