Linux应用与驱动开发:mmap和内存映射

学习笔记:Linux 驱动开发之 mmap 与内存映射

1. 核心概念:什么是 mmap

mmap (Memory Map) 是一种内存映射文件的方法。在嵌入式 Linux 驱动开发中,它主要用于将外设的物理地址(如 GPIO 寄存器)映射到用户进程的虚拟地址空间

  • 传统方式 :用户态 read/write <--> 内核态拷贝数据 <--> 驱动操作寄存器。
  • mmap 方式 :用户态指针 <--> MMU 直接转换 <--> 硬件寄存器。(零拷贝,速度极快

2. 核心难点:用户虚拟地址 vs 内核虚拟地址

这是理解 Linux 内存管理的关键分水岭。

2.1 对比图表

特性 用户虚拟地址 (UVA) 内核虚拟地址 (KVA)
定义 应用程序看到的地址。 操作系统内核驱动看到的地址。
地址范围 (32位) 0x00000000 ~ 0xBFFFFFFF (0~3G) 0xC0000000 ~ 0xFFFFFFFF (3G~4G)
可见性 私有 。进程A和进程B的 0x1000 互不相干。 全局共享。所有进程进入内核态后看到的都是同一份。
映射工具 mmap() ioremap()
访问权限 仅当前进程有效,进程结束即销毁。 系统启动即存在,只有内核代码可读写。
物理对应 通常不连续,按需分配(缺页中断)。 逻辑地址线性映射,vmalloc/ioremap 非线性。

2.2 它们与物理地址的关系

假设 i.MX6ULL 的一个 LED 寄存器物理地址是 0x0209C000

  1. CPU/MMU :只认物理地址 0x0209C000
  2. 驱动程序 (KVA) :通过 ioremap 拿到一个地址(如 0xF0001000)。驱动写 0xF0001000 -> MMU 查表 -> 写入物理地址。
  3. 应用程序 (UVA) :通过 mmap 拿到一个地址(如 0xB7001000)。应用写 0xB7001000 -> MMU 查表 -> 写入物理地址。

结论:UVA 和 KVA 就像是通往同一个房间(物理地址)的两扇不同的门。一扇门开在"用户公寓"(0-3G),另一扇门开在"管理员办公室"(3-4G)。


3. mmap 的实现流程(驱动与应用配合)

3.1 应用程序做了什么?

应用程序中的mmap最终会调用驱动中实现的 .mmap 接口(即下面的 my_driver_mmap 函数)

C 复制代码
int fd = open("/dev/my_led", O_RDWR);
// 申请映射:从 offset 0 开始,映射 4096 字节
unsigned char *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

// 直接操作硬件!
*addr = 0x01; // 点灯

3.2 驱动程序做了什么? (fops.mmap)

驱动的核心任务是构建页表

  1. 获取物理地址 :知道你要操作哪个寄存器(例如 0x0209C000)。
  2. 计算页帧号 (PFN) :Linux 内存管理以"页"为单位(通常 4KB)。
    • PFN = 物理地址 >> PAGE_SHIFT (即除以 4096)。
  3. 调用 remap_pfn_range:这是核心函数。它负责修改当前进程的页表,建立 UVA 到 物理地址 的映射。
C 复制代码
// 驱动代码示例
static int my_driver_mmap(struct file *file, struct vm_area_struct *vma)
{
    unsigned long phy_addr = 0x0209C000; // 硬件物理地址
    
    // 关键:设置为不使用缓存 (Nocache)!否则寄存器读写会出错
    vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);

    // 建立映射:把 vma->vm_start (应用层的UVA) 映射到 phy_addr (物理地址)
    if (remap_pfn_range(vma, 
                        vma->vm_start,      // 用户虚拟地址起始
                        phy_addr >> PAGE_SHIFT, // 物理地址页帧号
                        vma->vm_end - vma->vm_start, // 映射大小
                        vma->vm_page_prot)) // 属性:无缓存
    {
        return -EAGAIN;
    }
    return 0;
}

4. 关键细节与注意事项

4.1 为什么要 pgprot_noncached

  • 原因 :对于硬件寄存器,必须禁止 CPU 缓存 (Cache)
  • 后果:如果不加这一行,应用程序写数据可能只写到了 Cache 里,LED 灯根本不会亮,或者读取的状态是旧的。

4.2 为什么映射大小通常是 4KB (4096)?

  • MMU 管理内存的最小单位是一页 (Page),ARM Linux 默认页大小是 4096 字节。
  • 即使你只需要操作 4 个字节的寄存器,mmap 也会映射整整一页(4096 字节)。因此,你在计算偏移量时要小心。

4.3 ioremapmmap 的关系

  • 驱动程序自己要访问寄存器 -> 用 ioremap
  • 驱动程序想让应用程序直接访问寄存器 -> 实现 .mmap 接口(内部调用 remap_pfn_range)。
  • 通常一个驱动里这两个都会用到。

5. 总结

  1. 物理地址是唯一的真理,但被 CPU 藏在了 MMU 后面。
  2. KVA (内核虚拟地址) 是驱动在内核态用的,通过 ioremap 映射。
  3. UVA (用户虚拟地址) 是应用在用户态用的,通过 mmap 系统调用请求,由驱动协助映射。
  4. remap_pfn_range 是连接 UVA 和 物理地址 的桥梁。

下一步实践建议:

在韦东山的开发板上,你可以写一个简单的程序,利用 /dev/mem 这个系统自带的驱动节点。它已经实现了 mmap 功能。你可以尝试用 mmap 映射 GPIO 的物理基地址,然后在应用层直接点亮 LED,这将是你理解这一概念最直观的实验。

相关推荐
小郭团队2 小时前
1_1_七段式SVPWM (传统算法反正切)算法理论与 MATLAB 实现详解
人工智能·stm32·嵌入式硬件·算法·dsp开发
BreezeJuvenile3 小时前
SPI_STM32的SPI外设及常用寄存器介绍
stm32·单片机·嵌入式硬件·spi外设·spi常用寄存器介绍
独处东汉4 小时前
AI辅助Stm32l031项目开发调试板子printf
stm32·单片机·嵌入式硬件
ytttr87314 小时前
基于STM32和W5500芯片的Modbus TCP协议栈实现
stm32·嵌入式硬件
芯思路14 小时前
STM32开发学习笔记之三【按键】
笔记·stm32·学习
一枝小雨17 小时前
【OTA专题】17 打通Bootloader与App逻辑之间的通信
stm32·单片机·嵌入式·流程图·freertos·ota·bootloader
一枝小雨19 小时前
【OTA专题】18 OTA性能优化:优化bootloader存储空间与固件完整性校验(CRC)
stm32·单片机·性能优化·嵌入式·freertos·ota·bootloader
尼喃20 小时前
PW2605Z,专为系统安全护航的高可靠性负载开关
stm32·单片机·嵌入式硬件
阿昊真人20 小时前
stm32 按键中断
stm32·单片机·嵌入式硬件