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,这将是你理解这一概念最直观的实验。

相关推荐
kebidaixu28 分钟前
FreeRTOS 移植到 STM32F407VETX 记录(四)
stm32
结城明日奈是我老婆1 小时前
基于stm32f103c8t6最小系统板俩块版通讯
stm32·单片机·嵌入式硬件
fengfuyao9852 小时前
STM32F030 SD卡文件系统读取实例
stm32·单片机·嵌入式硬件
kebidaixu2 小时前
FreeRTOS 移植到 STM32F407VETX 记录(三)
stm32·单片机·嵌入式硬件
普中科技12 小时前
【普中STM32F1xx开发攻略--标准库版】-- 第 45 章 FSMC-外扩 SRAM 实验
stm32·单片机·嵌入式硬件·fsmc·普中科技·外扩sram·is62wv51216
嵌入式ZYXC16 小时前
第3篇:《面试题:I2C为什么要加上拉电阻?阻值怎么选?》
stm32·单片机·嵌入式硬件·面试·职场和发展
你疯了抱抱我16 小时前
【STM32】使用 STM32CubeMX 生成项目,LED测试;上位机:STM32F411CEU6
stm32·单片机·嵌入式硬件
嵌入式小站19 小时前
STM32 零基础可移植教程 24:SPI Flash 读数据,先从指定地址读几个字节
chrome·stm32·嵌入式硬件
guygg8820 小时前
基于C# + Halcon的通用ROI绘制工具
stm32·单片机·c#