Linux 驱动内存子系统核心知识精要(纯干货版)
Linux 驱动内存子系统核心知识精要(纯干货版)
一、 内存分配策略标志:GFP_KERNEL vs GFP_ATOMIC
核心区别:是否允许睡眠(Can Block)。
| 标志 | 行为 | 使用场景 | 内存来源 |
|---|---|---|---|
| GFP_KERNEL | 允许睡眠。分配器会主动回收内存或交换页,进程阻塞直到分配成功。 | 进程上下文:probe、read/write、ioctl、工作队列(workqueue)。 | 常规内存池 + Swap 交换区。 |
| GFP_ATOMIC | 绝不睡眠。若内存不足,立即返回NULL。 | 原子上下文:中断顶/底半部(Tasklet)、自旋锁临界区、定时器回调。 | 系统预留的紧急内存池(Emergency Pool),容量极小。 |
致命错误:在持有自旋锁或中断上下文中使用
GFP_KERNEL,会触发BUG: scheduling while atomic,导致内核 Panic。
二、 内存分配函数:kmalloc vs vmalloc
核心区别:物理内存的连续性及虚拟地址映射方式(依赖 MMU)。
| 特性 | kmalloc (kzalloc) | vmalloc |
|---|---|---|
| 物理地址 | 连续(伙伴系统分配)。 | 不连续(离散物理页框拼接)。 |
| 虚拟地址 | 直接映射区(线性偏移),转换快。 | 动态映射区(非线性),需更新多级页表,TLB 命中率低,开销大。 |
| 最大分配量 | 较小(受限于伙伴系统,单次通常 < 4MB)。 | 较大(可达数百 MB)。 |
| 硬件 DMA | 适合(外设需要物理连续地址)。 | 不适合(物理地址分散,DMA 无法寻址)。 |
实战原则:自定义结构体(如
ds18b20)用kzalloc;加载大模块或帧缓冲(Framebuffer)用vmalloc。
三、 硬件基石:MMU(内存管理单元)
核心使命:将程序使用的虚拟地址(VA)实时翻译为物理内存条的物理地址(PA)。
- 页表翻译:以 4KB 为页粒度查找页表。若页表无映射,触发
Unable to handle kernel paging request崩溃。 - 权限隔离(Ring 0 / Ring 3):页表中包含 R/W 和 User/Supervisor 权限位。用户态访问内核空间地址,MMU 直接触发权限异常。
- TLB 与 Cache 一致性陷阱:
- 现象:CPU 从 Cache 取旧数据,而 DMA 外设已更新物理内存。
- 对策:操作 DMA 缓冲区前后必须调用
dma_map_single或dma_sync_single_for_cpu刷新 Cache/MMU 缓存。
四、 权限隔离:内核空间 vs 用户空间
CPU 特权级:内核空间(Ring 0,执行所有指令) vs 用户空间(Ring 3,禁止特权指令)。
地址视野(32位系统示例):
- 用户空间:
0x0000 0000 ~ 0xC000 0000(3GB),每个进程独立。 - 内核空间:
0xC000 0000 ~ 0xFFFFFFFF(1GB),所有进程共享。
数据交互绝对法则(摆渡函数):
驱动禁止直接memcpy用户态指针,必须使用专用 API:
copy_from_user(void *to, const void __user *from, unsigned long n)copy_to_user(void __user *to, const void *from, unsigned long n)
中断上下文禁令:
copy_from_user内部可能因缺页异常而睡眠(等待磁盘 IO),绝不允许在中断上下文调用。
五、 零拷贝进阶:mmap 内存映射
核心机制:在用户态虚拟地址和内核态物理内存之间建立直接映射,零拷贝(Zero-Copy),适合传输 >4KB 的大数据。
驱动侧实现模板:
staticintmy_mmap(structfile*filp,structvm_area_struct*vma){// 1. 获取物理页框号(PFN)unsignedlongpfn=virt_to_phys(ds18b20->buffer)>>PAGE_SHIFT;// 2. 设置 Cache 属性(必须明确指定)vma->vm_page_prot=pgprot_noncached(vma->vm_page_prot);// 3. 核心映射 API(修改 MMU 页表)if(remap_pfn_range(vma,vma->vm_start,pfn,vma->vm_end-vma->vm_start,vma->vm_page_prot)){return-EAGAIN;}return0;}Cache 属性抉择(面试高频):
pgprot_noncached:直达物理内存。适合硬件寄存器、DMA 共享内存(保证实时一致性,速度最慢)。pgprot_writecombine:读直达,写合并。适合 Framebuffer 显存。- 默认(缓存):适合纯软件数据传输。
与copy_from_user的对决:
| 维度 | copy_from_user | mmap |
|---|---|---|
| 拷贝次数 | 2 次(硬件→内核→用户) | 0 次 |
| 适用数据量 | < 1KB(控制指令、配置参数) | > 4KB(视频流、大量采样数据) |
| 风险等级 | 低(内核全程校验) | 高(用户直接操作物理内存) |
mmap 两大致命陷阱:
- 越界映射:APP 传入的
len大于驱动实际分配的内存,remap_pfn_range会映射到非法物理内存。解法:检查vma->vm_end - vma->vm_start,超出返回-EINVAL。 - 释放后访问(Use-After-Free):驱动
remove时kfree了内存,但 APP 未调用munmap。APP 再次读写时触发Unable to handle kernel paging request。解法:在驱动的release函数中遍历 VMA 强制解除映射(zap_vma_ptes)。