1. Linux内核内存管理概述:物理内存与虚拟内存、MMU的作用、页表与地址转换、内核空间与用户空间划分
在Linux设备驱动开发中,内存管理是基石。驱动程序运行在内核空间,直接操作物理内存,但现代操作系统通过虚拟内存机制为每个进程提供了独立的地址空间。理解物理内存与虚拟内存的关系、MMU(内存管理单元)的硬件转换过程、页表结构以及内核/用户空间的划分,是编写安全、高效设备驱动的前提。
1.1 物理内存与虚拟内存
物理内存是硬件实际提供的RAM,由内存条或SoC内部SRAM/DRAM构成,以字节为单位编址。物理地址(Physical Address, PA)是CPU地址总线直接发出的地址。
虚拟内存是操作系统为每个进程抽象出的连续、独立的地址空间。进程访问的地址称为虚拟地址(Virtual Address, VA)。虚拟内存使得每个进程“以为”自己独占了整个地址空间(例如32位系统下4GB),而实际物理内存可能远小于此。
| 特性 | 物理内存 | 虚拟内存 |
|---|---|---|
| 编址方式 | 物理地址(PA) | 虚拟地址(VA) |
| 空间大小 | 受限于硬件RAM容量 | 受限于CPU位数(32位: 4GB, 64位: 16EB) |
| 连续性 | 物理上可能不连续(碎片化) | 对进程呈现连续线性空间 |
| 隔离性 | 所有进程共享,无保护 | 进程间隔离,互不干扰 |
| 驱动视角 | DMA、寄存器访问必须使用物理地址 | CPU指令访问、指针操作使用虚拟地址 |
驱动开发要点: 设备驱动在访问DMA缓冲区或硬件寄存器时,必须明确当前使用的是虚拟地址还是物理地址。CPU通过虚拟地址执行指令,而DMA控制器直接使用物理地址进行数据传输。两者之间的转换是驱动内存管理的核心任务之一。
1.2 MMU的作用
MMU(Memory Management Unit,内存管理单元)是CPU内部负责地址转换的硬件模块。它位于CPU核心与物理内存之间,核心功能如下:
- 地址转换: 将CPU发出的虚拟地址实时转换为物理地址。
- 内存保护: 检查访问权限(读/写/执行),防止进程越界访问内核或其他进程的内存。
- 缓存控制: 管理TLB(Translation Lookaside Buffer,页表缓存)和Cache策略。
- 虚拟化支持: 在ARMv8等架构中支持二级地址转换(Stage-2 translation),用于虚拟机场景。
MMU工作流程(简化): CPU发出VA → MMU查询TLB/页表 → 获取PA → 检查权限 → 访问物理内存。若TLB未命中,则硬件遍历页表(Page Walk),更新TLB后继续。
1.3 页表与地址转换
页表是MMU进行地址转换的核心数据结构。Linux内核采用多级页表(如ARM64的4级页表:PGD → PUD → PMD → PTE),以节省内存并支持大地址空间。
地址转换基本单元:
- 页(Page): 虚拟内存和物理内存分割的最小块,通常为4KB(也可配置为16KB、64KB或大页)。
- 页框(Page Frame): 物理内存中与页大小相同的块。
- 页表项(PTE, Page Table Entry): 记录虚拟页到物理页框的映射关系、权限位(R/W/X)、脏位、访问位等。
| 层级 | 名称 | 作用 | ARM64示例 |
|---|---|---|---|
| L0 | PGD (Page Global Directory) | 顶级页表,指向下一级目录 | TTBR0_EL1 / TTBR1_EL1 |
| L1 | PUD (Page Upper Directory) | 第二级页表(可选,某些架构合并) | — |
| L2 | PMD (Page Middle Directory) | 中间级页表 | — |
| L3 | PTE (Page Table Entry) | 最后一级,直接映射到物理页框 | 4KB页粒度 |
驱动注意事项: 在设备驱动中,不要直接操作页表。内核提供了标准API(如
remap_pfn_range()、dma_alloc_coherent())来建立或管理映射。直接修改页表可能导致系统崩溃或安全漏洞。
1.4 内核空间与用户空间划分
Linux操作系统将虚拟地址空间划分为两部分:用户空间和内核空间。划分比例由内核配置决定(如ARM32的3G/1G或2G/2G,ARM64的固定划分)。
- 用户空间: 运行用户态进程(应用程序)。每个进程拥有独立的用户空间,互不干扰。用户空间不能直接访问内核空间的数据或硬件寄存器。
- 内核空间: 运行内核代码、内核线程和设备驱动。内核空间对所有进程共享,拥有最高权限(特权级)。
| 特性 | 用户空间 | 内核空间 |
|---|---|---|
| 特权级别 | 非特权(EL0 / Ring 3) | 特权(EL1 / Ring 0) |
| 地址范围(32位典型) | 0x00000000 – 0xBFFFFFFF (3GB) | 0xC0000000 – 0xFFFFFFFF (1GB) |
| 访问硬件 | 不允许直接访问 | 允许直接访问寄存器和DMA |
| 内存映射 | 通过系统调用触发内核服务 | 直接使用 ioremap()、kmalloc() 等 |
| 驱动交互方式 | 通过 read()/write()/ioctl() 陷入内核 |
驱动运行在此空间 |
驱动开发核心原则: 设备驱动运行在内核空间,但必须通过
copy_from_user() / copy_to_user() 安全地访问用户空间缓冲区。直接解引用用户空间指针是危险的,可能导致内核崩溃或安全漏洞。
1.5 驱动开发中的内存管理关键API
设备驱动开发中,常用的内存管理API及其与物理/虚拟地址的关系如下:
| API | 用途 | 返回地址类型 | 物理连续性 |
|---|---|---|---|
kmalloc() |
分配内核堆内存(小内存,< 128KB) | 虚拟地址 (VA) | 物理连续 |
__get_free_pages() |
分配以页为单位的内存 | 虚拟地址 (VA) | 物理连续 |
vmalloc() |
分配大块虚拟连续内存 | 虚拟地址 (VA) | 物理不连续 |
dma_alloc_coherent() |
分配DMA一致性缓冲区 | 虚拟地址 + 物理地址 | 物理连续 |
ioremap() |
将物理外设寄存器映射到内核虚拟空间 | 虚拟地址 (VA) | 映射物理地址 |
实践建议: 对于DMA操作,优先使用
dma_alloc_coherent() 或 dma_map_single() 系列API,它们自动处理Cache一致性、物理地址获取和边界对齐,避免手动管理带来的Bug。