Linux memremap接口用法及示例

memremap 是 Linux 内核提供的一个函数,用于灵活地将物理地址范围映射到内核虚拟地址空间。它是对传统 ioremap 函数的增强,提供了对内存类型(如设备内存、写合并内存)的更好控制。它主要在驱动程序中用于访问设备内存(如寄存器、帧缓冲区)或特殊类型的系统内存。

核心功能:

  1. 物理到虚拟地址映射: 将给定的物理地址区域 (phys_addr, size) 映射到内核的虚拟地址空间。
  2. 内存类型控制: 通过 flags 参数指定映射的内存类型,这对访问设备内存、写合并内存或普通内存至关重要,影响 CPU 缓存行为。
  3. 替代部分 ioremap 对于设备内存映射,memremap 通常比 ioremap 更灵活和推荐。

头文件:

1
#include <linux/io.h>

函数原型:

1
void *memremap(phys_addr_t phys_addr, size_t size, unsigned long flags);

参数:

  • phys_addr:要映射的物理起始地址。
  • size:要映射的区域大小(字节数)。
  • flags:控制映射行为的标志位(位掩码)。关键标志包括:
    • MEMREMAP_WB:映射为写回缓存类型。行为类似普通 RAM。适用于映射系统 RAM。*(使用最少,通常用其他方法映射 RAM)*
    • MEMREMAP_WT:映射为写通缓存类型。写操作直接穿透到内存,但读操作可能缓存。适用于设备内存(如寄存器),确保写操作及时到达设备。
    • MEMREMAP_WC:映射为写合并缓存类型。写操作可能被合并并延迟写入,读操作通常不缓存(且可能慢)。适用于帧缓冲区等对写性能要求高、对读要求低或顺序访问的大块内存。使用需谨慎。
    • MEMREMAP_ENC:与 MEMREMAP_WCMEMREMAP_WT 结合使用,表示映射加密内存(如 Intel SME/TDX)。*(较新内核,特定场景)*
    • MEMREMAP_DEC:与 MEMREMAP_WB 结合使用,表示映射解密内存。*(较新内核,特定场景)*
    • MEMREMAP_DEV:显式请求映射为设备内存/dev/mem 风格)。通常隐含在 MEMREMAP_WT 中。更推荐使用 MEMREMAP_WT
    • MEMREMAP_KIND:内部使用,用于指定 flags 中哪几位表示内存类型。
    • MEMREMAP_EXCLUSIVE:尝试独占此映射,防止其他映射覆盖该区域。*(可选)*

返回值:

  • 成功:映射区域的起始内核虚拟地址 (void *)。
  • 失败:NULL。必须检查返回值!

卸载映射:

使用 memunmap 释放映射:

1
void memunmap(void *addr);
  • addrmemremap 返回的虚拟地址。

重要注意事项:

  1. 内存类型选择: 最关键的是选择正确的 flags
    • 设备寄存器/小内存: 几乎总是使用 MEMREMAP_WT (或隐含它的 MEMREMAP_DEV)。确保写入立即生效,避免缓存导致的意外。
    • 大型帧缓冲区 (FB): 考虑使用 MEMREMAP_WC 以提高写入性能(尤其是连续写入)。但要清楚读取可能慢且不一致。
    • 系统 RAM (特殊场景): 极少使用 MEMREMAP_WB,通常有更好的方法(如 vmallockmap 等)。
  2. 错误检查: **必须检查 memremap 的返回值是否为 NULL**。映射失败是常见情况(如物理地址无效、资源不足)。
  3. 资源释放: 映射成功后,必须在驱动卸载或不再需要时调用 memunmap 释放映射,防止资源泄漏。
  4. 对齐: 虽然 memremap 本身可能不严格要求对齐,但映射的物理区域本身可能有对齐要求(由硬件决定)。确保 phys_addrsize 符合设备规范。
  5. 并发访问: memremap/memunmap 本身不是线程安全的。确保在安全上下文中调用(如模块初始化/退出)。对映射内存的访问需要驱动程序自己通过锁或其他机制保证并发安全。
  6. ioremap 的关系:
    • ioremap 主要用于映射设备 IO 空间(可能存在于独立地址空间),其行为类似 MEMREMAP_WT
    • memremap 更通用,明确支持不同的缓存类型,并能映射系统 RAM (尽管不常用)。对于设备内存,memremap(..., MEMREMAP_WT)ioremap 的现代替代品。
  7. /dev/mem 访问: 如果物理地址范围可以通过 /dev/mem 访问,MEMREMAP_DEVMEMREMAP_WT 通常适用。

示例:

  1. 映射设备寄存器 (推荐方式 - MEMREMAP_WT):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <linux/io.h>
#include <linux/module.h>

#define MY_DEVICE_REGS_PHYS_BASE 0xFE000000
#define MY_DEVICE_REGS_SIZE 0x1000

static void __iomem *regs_vaddr;

static int __init my_driver_init(void)
{
// 映射设备寄存器区域 (使用 MEMREMAP_WT 确保设备可见性)
regs_vaddr = memremap(MY_DEVICE_REGS_PHYS_BASE, MY_DEVICE_REGS_SIZE, MEMREMAP_WT);
if (!regs_vaddr) {
pr_err("Failed to remap device registers\n");
return -ENOMEM;
}

// 现在可以通过 regs_vaddr 访问寄存器
// 例如:u32 control_reg = readl(regs_vaddr + CONTROL_REG_OFFSET);
// writel(0x55AA, regs_vaddr + COMMAND_REG_OFFSET);

// ... 其他初始化代码 ...
return 0;
}

static void __exit my_driver_exit(void)
{
if (regs_vaddr) {
// 卸载时解除映射
memunmap(regs_vaddr);
regs_vaddr = NULL;
}
// ... 其他清理代码 ...
}

module_init(my_driver_init);
module_exit(my_driver_exit);
  1. 映射帧缓冲区 (谨慎使用 MEMREMAP_WC):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <linux/io.h>
#include <linux/fb.h> // 可能用到 fb_info

void *map_framebuffer(phys_addr_t fb_phys, size_t fb_size)
{
void *fb_vaddr;

// 映射帧缓冲区为写合并 (WC) 以提高写入性能
fb_vaddr = memremap(fb_phys, fb_size, MEMREMAP_WC);
if (!fb_vaddr) {
pr_err("Failed to remap framebuffer\n");
return NULL;
}

return fb_vaddr;
}

void unmap_framebuffer(void *fb_vaddr)
{
if (fb_vaddr) {
memunmap(fb_vaddr);
}
}

// 在 fb_probe 中调用 map_framebuffer,在 fb_remove 中调用 unmap_framebuffer

总结:

memremap 是 Linux 内核中映射物理内存(尤其是设备内存)到内核虚拟空间的强大且灵活的工具。正确选择 flags(特别是 MEMREMAP_WT 用于设备寄存器,谨慎使用 MEMREMAP_WC 用于帧缓冲区)、严格的错误检查(NULL 返回值)以及及时使用 memunmap 释放映射是正确和安全使用该接口的关键。 它逐渐成为替代传统 ioremap 的首选方法,尤其是在需要明确控制缓存行为时。