1. 内存管理概述:Linux内核内存管理层次结构、物理内存与虚拟内存、内存分配器简介
Linux内核作为现代操作系统的核心,其内存管理子系统是驱动开发中最关键、最复杂的部分之一。对于编写设备驱动的开发者而言,理解内核如何组织、分配和回收内存,是正确使用kmalloc、dma_alloc_coherent等API的前提。本章从宏观视角梳理内核内存管理的层次结构、物理与虚拟内存的映射关系,以及主要内存分配器的定位。
1.1 内核内存管理层次结构
Linux内核的内存管理并非单一模块,而是由多个层次协同工作的子系统。从硬件到上层接口,大致可分为四个层次:
| 层次 | 核心组件 | 职责描述 |
|---|---|---|
| 硬件层 | MMU、TLB、DMA控制器 | 提供物理地址空间、页表转换、缓存一致性协议 |
| 内核核心层 | 页分配器 (Buddy)、slab分配器、vmalloc | 管理物理页帧、小对象缓存、非连续虚拟地址映射 |
| 驱动接口层 | kmalloc/kfree、dma_alloc_coherent、ioremap | 为驱动提供标准化的内存申请与释放API |
| 用户空间层 | mmap、brk、页缓存 | 通过系统调用与内核交互,驱动通常不直接涉及 |
驱动开发视角: 驱动主要工作在内核核心层与驱动接口层。理解Buddy分配器如何管理连续物理页,以及slab分配器如何高效分配小对象,是掌握
kmalloc行为的关键。
1.2 物理内存与虚拟内存
在驱动开发中,区分物理内存与虚拟内存至关重要。CPU通过MMU将虚拟地址转换为物理地址,这种机制为每个进程提供了独立的地址空间,同时也带来了内存管理的复杂性。
1.2.1 物理内存
- 定义: 硬件RAM芯片上真实的存储单元,以页帧(Page Frame)为单位管理,通常大小为4KB。
- 特点: 资源有限,多个进程/驱动共享;DMA操作通常需要物理连续的内存块。
- 内核视角: 内核维护
struct page数组描述每个物理页帧的状态(空闲、使用、保留等)。
1.2.2 虚拟内存
- 定义: CPU通过页表映射给进程或内核线程的地址空间,每个进程拥有独立的虚拟地址空间(例如32位下4GB)。
- 特点: 虚拟地址可以大于物理内存;通过页表实现按需分配、共享内存、写时复制等高级功能。
- 内核空间: 内核自身也运行在虚拟地址空间中,但通常使用直接映射区(线性映射)访问物理内存。
| 对比维度 | 物理内存 | 虚拟内存 |
|---|---|---|
| 地址空间大小 | 受限于硬件容量(如8GB) | 受限于CPU位数(如64位下16EB) |
| 连续性 | 物理上可能不连续(外部碎片) | 虚拟地址可以连续,映射到分散的物理页 |
| DMA适用性 | 需要物理连续(除非使用SMMU/IOMMU) | 通常不能直接用于DMA,需转换 |
| 驱动常用API | __get_free_pages、dma_alloc_coherent |
kmalloc、vmalloc、ioremap |
常见误区: 很多驱动新手误以为
kmalloc返回的地址就是物理地址。实际上,kmalloc返回的是内核虚拟地址(直接映射区),需要通过virt_to_phys()转换为物理地址才能用于DMA。在启用IOMMU的平台上,情况更为复杂。
1.3 内存分配器简介
内核提供了多种内存分配器,每种分配器针对不同的使用场景进行了优化。驱动开发者需要根据内存大小、连续性要求、分配频率和上下文(进程上下文 vs 中断上下文)选择合适的分配器。
1.3.1 主要分配器对比
| 分配器 | 分配粒度 | 物理连续性 | 适用场景 | 典型API |
|---|---|---|---|---|
| Buddy分配器 | 2^order 个物理页(4KB ~ 4MB) | 物理连续 | 大块内存、DMA缓冲区、页表分配 | __get_free_pages(), alloc_pages() |
| slab分配器 | 字节级(8B ~ 8KB) | 物理连续(基于Buddy) | 频繁分配/释放的小对象(如struct inode) |
kmem_cache_alloc(), kmalloc() |
| vmalloc | 页级(物理页不连续) | 虚拟连续,物理不连续 | 需要大块虚拟连续内存,但物理可分散(如模块加载) | vmalloc(), vfree() |
| DMA分配器 | 页级或更大 | 物理连续(且满足DMA掩码) | DMA缓冲区、设备内存映射 | dma_alloc_coherent(), dma_pool_alloc() |
1.3.2 分配器选择决策流程
- 是否需要物理连续?
- 是 → 进入步骤2
- 否 → 考虑
vmalloc(大块)或kmalloc(小块,通常物理连续但非保证)
- 是否用于DMA?
- 是 → 使用
dma_alloc_coherent或dma_pool_alloc - 否 → 进入步骤3
- 是 → 使用
- 分配大小?
- < 8KB → 使用
kmalloc(基于slab) - ≥ 8KB → 使用
__get_free_pages(基于Buddy)
- < 8KB → 使用
- 分配频率高?
- 是 → 考虑创建专用slab缓存(
kmem_cache_create) - 否 → 使用通用分配器即可
- 是 → 考虑创建专用slab缓存(
关键提示:
kmalloc底层基于slab分配器,而slab分配器又基于Buddy分配器获取物理页。因此,kmalloc分配的内存始终是物理连续的(对于小于一个页面的分配),但分配大块内存时可能失败。对于驱动而言,kmalloc是使用最频繁的分配函数,后续章节将深入剖析其实现细节。
1.4 本章小结
本章建立了内核内存管理的整体视图:
- 层次结构 从硬件MMU到驱动API,每一层都有明确的职责和抽象边界。
- 物理 vs 虚拟内存 是驱动开发的核心概念,理解页表映射机制有助于避免地址转换错误。
- 分配器选择 需要根据物理连续性、大小、DMA需求和分配频率综合判断。