当前位置: 首页 > news >正文

【Note】《深入理解Linux内核》 Chapter 5 :内存地址的表示——Linux虚拟内存体系结构详解

《深入理解Linux内核》 Chapter 5 :内存地址的表示——Linux虚拟内存体系结构详解

摘要: 物理地址与虚拟地址的关系,用户态与内核态地址空间划分,页表结构及其在32位和64位体系中的实现,线性地址与段机制,Linux页表维护与映射接口分析


一、引言

Linux 内核基于现代操作系统的核心设计原则:虚拟内存(Virtual Memory)。这一机制不仅为每个进程提供了独立的地址空间,提高了安全性和稳定性,也极大简化了内存管理和资源共享。本章深入探讨了内存地址表示机制的核心概念,主要包括:

  • 物理地址与虚拟地址的映射关系
  • 虚拟地址结构:线性地址、页地址
  • Linux对用户空间和内核空间的划分
  • 页表的层级结构和访问方式
  • 分页机制与段机制的结合
  • 实现中关键结构与内核API

深入理解 Linux 如何建立进程隔离与统一内存管理机制,为后续页表操作、内存分配与调页机制打下理论基础。


二、物理地址与虚拟地址

2.1 物理地址(Physical Address)

  • 是内存芯片上的实际地址,由 CPU 通过内存总线访问;
  • 通常由内存控制器和硬件平台定义;
  • 操作系统管理整个物理内存的使用,避免进程直接访问。

2.2 虚拟地址(Virtual Address)

  • 是进程或内核中代码看到的地址;
  • 每个进程看到的虚拟地址空间是隔离的;
  • 通过内核维护的页表映射到真实物理地址。

目的:通过虚拟地址,操作系统实现了地址空间的隔离、权限控制、内存共享和需求分页等机制。


三、内核空间与用户空间

3.1 地址空间划分(以32位为例)

在32位体系结构中(如 x86),虚拟地址为32位,即最多表示 4GB 空间。Linux 对这4GB空间进行了划分:

区域范围描述
用户空间0x00000000 ~ 0xBFFFFFFF进程可见,仅能访问自己的用户空间
内核空间0xC0000000 ~ 0xFFFFFFFF所有内核代码共享,不对用户可见
  • 高端内存(High Memory):不直接映射入内核虚拟空间的物理内存;
  • 内核空间对所有进程共享,因此访问时需注意并发与上下文保护。

3.2 64位架构划分(x86_64)

在x86_64上,地址空间更广泛:

  • 虚拟地址宽度常为48位,支持 256TB 地址空间;
  • Linux 通常将内核空间放在高地址区(如从 0xffff800000000000 开始);
  • 用户空间仍然从 0 开始,进程各自独立。
#define TASK_SIZE 0x00007ffffffff000 // 用户空间最大地址(x86_64)

四、线性地址与分页机制

4.1 段机制与线性地址

尽管 Intel x86 支持段机制(段寄存器、段描述符等),Linux 实际上将段机制简化为平坦模型

  • 所有段(CS、DS、SS)都从 0 开始,长度覆盖全部地址空间;
  • 线性地址(Linear Address)= 段基址 + 虚拟地址;
  • 简化后,线性地址与虚拟地址几乎等价。

4.2 分页机制

分页机制将线性地址划分为页目录 + 页表项 + 页内偏移等多级结构。

32位 x86 分页结构:
  • 页大小:4KB(2^12)
  • 线性地址结构:
位段范围作用
31~22 (10位)页目录项索引 PDE
21~12 (10位)页表项索引 PTE
11~0 (12位)页内偏移
页表结构说明:
  • 每个页表或目录项大小为 4 字节(32位);
  • 每页 4KB,可存 1024 个项;
  • 整个页表结构可映射 1024 * 1024 * 4KB = 4GB 地址空间。
64位分页结构(x86_64):
  • 四级页表结构:PGD → PUD → PMD → PTE;
  • 每级使用9位地址偏移(加上12位页偏移,共48位);
  • 页大小仍为4KB或更大(如2MB、1GB HugePage);

五、内核页表结构与数据结构

5.1 内核关键结构

  • pgd_t:页全局目录项(Page Global Directory)
  • pud_t:页上层目录项
  • pmd_t:页中间目录项
  • pte_t:页表项

每个结构由typedef定义,包含实际硬件页项及辅助宏。

5.2 每个进程的页表

  • 每个进程结构体 task_struct 包含 mm_struct *mm 指针;
  • mm_struct 内含 pgd_t *pgd,指向该进程页表根;
  • active_mm 是当前激活的页表,在内核线程中使用。
struct mm_struct {pgd_t *pgd;...
};

5.3 虚拟地址到物理地址映射过程

  1. 从虚拟地址提取 PGD 索引 → 获取PGD项;
  2. 使用 PUD 索引 → 获取 PUD;
  3. 然后 PMD → PTE;
  4. 最后 PTE 中存储物理页帧号(PFN);
  5. 加上页内偏移,得到物理地址。

5.4 内核页表宏定义

#define pgd_index(addr)     (((addr) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
#define pte_index(addr)     (((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

六、虚拟地址映射方式

Linux提供不同的虚拟地址映射方式:

6.1 恒定映射(恒等映射)

在启动阶段,内核将物理地址映射到固定虚拟地址区域(如 PAGE_OFFSET + phys_addr),常用于低端内存。

  • 示例:__va(p) / __pa(v) 宏可在虚拟与物理地址间转换。

6.2 高端内存映射(Highmem)

由于32位系统虚拟地址有限,不能映射所有物理内存。内核使用以下机制:

  • kmap() / kunmap():临时将高端页映射入内核空间;
  • kmap_atomic():不可被中断打断的高速映射;
  • 用于访问超过直接映射范围的页帧。

6.3 ioremap 映射外设内存

I/O设备(PCI、MMIO)的物理地址不能通过常规内存页访问,需使用 ioremap() 创建虚拟映射:

void __iomem *vaddr = ioremap(phys_addr, size);

这种映射不可被缓存,常用于寄存器访问。


七、Linux地址宏与辅助工具

常用宏

宏/函数功能
__pa(vaddr)虚拟地址转物理地址
__va(paddr)物理地址转虚拟地址
virt_to_phys()与__pa类似,但适用于内核地址
phys_to_virt()物理地址映射回虚拟地址
page_address()页结构体转为虚拟地址

八、内核如何初始化页表

  1. boot阶段:引导加载器加载内核后,启动代码(如 arch/x86/boot/compressed/head_64.S)初始化临时页表;
  2. setup_arch():设置内核页表映射;
  3. paging_init():创建初始页表,包括内核空间、IO区映射;
  4. memblock:记录系统物理内存布局,辅助建立映射;
  5. 内核页表重定位:64位内核将自身映射到高地址段。

九、多种内存页大小支持(HugeTLB)

Linux 支持更大页大小(如 2MB、1GB),减少页表项数量,提升TLB命中率,适合大内存数据库、图像处理等应用。

  • 通过 mmap() + MAP_HUGETLB 使用;
  • 内核支持 Transparent Huge Pages(THP)自动使用大页。

十、小结

第五章深入剖析了 Linux 内核的虚拟地址管理体系:

  1. 区分了物理地址与虚拟地址,说明其在内核中的转换关系;
  2. 探讨了用户空间与内核空间的划分及其意义;
  3. 详细介绍了 x86 32/64位 的多级页表结构;
  4. 描述了从虚拟地址到物理地址的页表查找过程
  5. 阐明了高端内存映射、高速访问、IO映射等机制;
  6. 给出了重要结构体(pgd_t, mm_struct)与宏接口;
  7. 简要说明了内核启动时页表的初始化与扩展流程。

本章是理解 Linux 内存管理、页表操作、缺页中断处理与内核模块开发的前提基础。


十一、实践建议

  1. 查看进程地址空间
cat /proc/<pid>/maps
  1. 使用 pagemap 分析页表
cat /proc/<pid>/pagemap
  1. 调试内核页表
  • 使用 cr3, pgd, pte 调试命令(gdb / qemu);
  • 在模块中通过 virt_to_phys 查看映射关系;
  1. 编写实验模块
  • 使用 __get_free_page() 分配页并观察其物理地址;
  • kmap_atomic() 访问高端页并打印虚拟地址。
http://www.dtcms.com/a/264616.html

相关文章:

  • Minio安装配置,桶权限设置,nginx代理 https minio
  • (nice!!!) (LeetCode 每日一题) 3333. 找到初始输入字符串 II (贪心+动态规划dp+前缀和)
  • 如何解决wordpress批量删除媒体库中的图片很慢甚至卡死问题
  • 音视频会议服务搭建(设计方案-两种集成方案对比)-03
  • U+平台配置免密登录、安装Hadoop配置集群、Spark配置
  • OpenLayers 入门指南【一】:WebGIS基础与OpenLayers概述
  • Chart.js 安装使用教程
  • AI自动化神器-DroidRun使用体验
  • OpenCASCADE学习|点云可视化深度优化指南
  • 【数字后端】- tcbn28hpcplusbwp30p140,标准单元库命名含义
  • 记一次事务中更新与查询数据不一致的问题分析
  • HTTP 协议深入理解
  • Git 分支与远程仓库基础教学总结
  • sudo本地提权漏洞(CVE-2025-32462)
  • S7-1200 PN与G120变频器控制起停及调速PROFINET实现详解
  • 微信小程序能不能获取物联网的上的设备数据
  • 在 proteus8或者proteus 9 中查看 micropython 的 print 输出
  • Redis搭建集群模式
  • 【WEB】Polar靶场 笔记
  • C++主流编辑器特点比较
  • 【HDMI CEC Menu Tunneling (菜单穿越) 功能详解
  • Stereolabs ZED系列与ZED X立体相机系列对比:如何根据项目需求选择?
  • AI大模型如何重塑软件开发流程?从自动化革命到人机共生范式
  • 小架构step系列01:小架构初衷
  • SQLMesh中的SQL模型:从基础定义到高级应用
  • 【网工|知识升华版|实验】1 登录华为设备并配置
  • 【Maven】Maven深度避坑指南:依赖冲突全维度解决方案与工业级实战(超万字解析)
  • 移动conda虚拟环境的安装目录
  • 超低功耗语音芯片有哪些?
  • 构建下一代云原生大模型多租户平台:架构设计与关键挑战