驱动内存映射与mmap性能调优及故障排查 - 第1节
1.1 物理内存与虚拟内存概念
在 Linux 驱动开发中,理解内存映射的首要前提是区分物理内存与虚拟内存。物理内存是硬件实际提供的 RAM 芯片,而虚拟内存是操作系统为每个进程提供的抽象地址空间。
| 特性 | 物理内存 | 虚拟内存 |
|---|---|---|
| 本质 | 硬件 RAM 芯片上的实际存储单元 | 操作系统提供的进程地址空间抽象 |
| 地址范围 | 由硬件总线宽度决定(如 4GB、64GB) | 由 CPU 架构决定(32位为 4GB,64位极大) |
| 连续性 | 物理上可能不连续(存在空洞) | 对进程表现为连续线性空间 |
| 可见性 | 所有进程共享同一物理地址空间 | 每个进程拥有独立的虚拟地址空间 |
| 驱动关注点 | DMA 缓冲区、设备寄存器地址 | 用户态与内核态地址转换、mmap 映射 |
驱动开发要点: 驱动通常操作物理地址(如设备寄存器、DMA 缓冲区),但用户程序只能通过虚拟地址访问。mmap 的核心作用就是在两者之间建立映射关系。
关键概念: 虚拟内存使每个进程拥有独立的地址空间,进程间相互隔离。驱动通过
remap_pfn_range() 或 io_remap_pfn_range() 将物理内存映射到用户虚拟空间。
1.2 MMU 工作原理
内存管理单元(MMU)是 CPU 内部负责虚拟地址到物理地址转换的硬件模块。每次 CPU 访问内存时,MMU 都会介入完成地址翻译。
MMU 地址转换流程
- CPU 发出虚拟地址:程序指令访问某个虚拟地址(如
0x00401000)。 - MMU 截获地址:MMU 将虚拟地址拆分为页号(VPN)和页内偏移(Offset)。
- 查询页表:MMU 以 VPN 为索引,在进程页表中查找对应的页表项(PTE)。
- 权限检查:MMU 检查 PTE 中的访问权限(读/写/执行)和特权级。
- TLB 加速:如果 TLB(快表)命中,直接返回物理地址,跳过页表查询。
- 生成物理地址:将 PTE 中的物理页框号(PFN)与偏移量拼接,得到最终物理地址。
驱动开发陷阱: 如果驱动在中断上下文或原子上下文中访问用户空间虚拟地址,可能触发缺页异常(page fault),导致系统崩溃。必须使用
copy_from_user/copy_to_user 或 get_user_pages 等安全接口。
1.3 页表与页框
页表是 MMU 进行地址转换的核心数据结构,页框(Page Frame)是物理内存的最小管理单元。Linux 内核以 4KB 为默认页大小(部分架构支持 64KB 或 huge page)。
页表结构(以 x86_64 四级页表为例)
| 页表级别 | 名称 | 索引位数 | 覆盖范围 |
|---|---|---|---|
| L1 | PML4(Page Map Level 4) | 9 位 | 512 GB |
| L2 | PDPT(Page Directory Pointer Table) | 9 位 | 1 GB |
| L3 | PD(Page Directory) | 9 位 | 2 MB |
| L4 | PT(Page Table) | 9 位 | 4 KB |
驱动视角: 驱动开发中,
struct page 结构体代表一个物理页框。通过 virt_to_page() 将内核虚拟地址转换为 struct page *,通过 page_to_pfn() 获取页框号。mmap 驱动中常用 remap_pfn_range() 将连续的物理页框映射到用户空间。
页表项(PTE)关键标志位
- Present(P):页框是否在物理内存中(缺页时清 0)。
- Read/Write(R/W):页框是否可写。
- User/Supervisor(U/S):用户态是否可访问。
- Dirty(D):页框是否被写入过。
- Accessed(A):页框是否被访问过。
- Page Attribute Table(PAT):内存类型(UC/WC/WT/WB)。
页框管理关键数据结构
/* Linux 内核页框管理核心结构 */
struct page {
unsigned long flags; /* 页框状态标志 */
atomic_t _refcount; /* 引用计数 */
atomic_t _mapcount; /* 映射计数 */
unsigned long private; /* 私有数据(驱动可用) */
struct address_space *mapping; /* 文件映射或匿名页 */
pgoff_t index; /* 页框在文件中的偏移 */
struct list_head lru; /* LRU 链表节点 */
void *virtual; /* 内核虚拟地址(若已映射) */
};
/* 驱动中常用的页框操作宏 */
#define page_to_pfn(page) /* struct page → 页框号 */
#define pfn_to_page(pfn) /* 页框号 → struct page */
#define virt_to_page(kaddr) /* 内核虚拟地址 → struct page */
#define page_address(page) /* struct page → 内核虚拟地址 */
性能调优提示: 在 mmap 驱动中,如果映射的是大块连续物理内存(如 CMA 或预留内存),建议使用
remap_pfn_range() 配合 huge page 映射,可显著减少 TLB miss 次数。对于非连续物理内存,使用 vm_insert_page() 逐页插入。