Linux内核读写寄存器函数用法
Linux内核readl和writel函数的用法
在 Linux 内核中,readl()
和 writel()
是用于访问 内存映射 I/O(Memory-Mapped I/O, MMIO) 的函数,专门处理 32 位宽度的寄存器或内存操作。它们提供了一种安全和可移植的方式与硬件设备寄存器进行交互。
一、基本概念
readl(addr)
: 从虚拟地址addr
读取 32 位数据。writel(val, addr)
: 将 32 位值val
写入虚拟地址addr
。
这两个函数的主要目的是:
- 保证原子性:确保 32 位操作在目标平台上是一次性完成的。
- 处理字节序:根据 CPU 和设备的不同,自动处理字节序问题。
- 内存屏障:隐式添加内存屏障(Memory Barrier),防止编译器或 CPU 乱序执行导致的问题。
二、函数原型
1 |
|
volatile
关键字告诉编译器不要优化此操作(确保每次访问都是真实的硬件访问)。__iomem
表示该地址指向 I/O 内存空间,而非普通内存。
三、使用步骤
映射物理地址到虚拟地址
硬件寄存器的物理地址需要先通过ioremap()
映射到内核虚拟地址空间:1
void __iomem *reg_base = ioremap(phy_addr, size);
计算寄存器偏移量
假设寄存器偏移为reg_offset
,则实际访问地址为:1
void __iomem *reg_addr = reg_base + reg_offset;
读写寄存器
1
2u32 value = readl(reg_addr); // 读取寄存器
writel(new_value, reg_addr); // 写入寄存器解除映射
在驱动卸载时,使用iounmap()
释放映射:1
iounmap(reg_base);
四、完整示例
1 |
|
五、注意事项
地址对齐
确保访问的地址是 32 位对齐的(即地址是 4 的倍数),否则某些架构会导致异常。内存屏障
- 若需要显式控制内存屏障,可使用
mmiowb()
或在读写后调用rmb()
/wmb()
。 writel()
自身包含写屏障,但某些场景可能需要额外处理。
- 若需要显式控制内存屏障,可使用
错误处理
始终检查ioremap()
的返回值,映射失败可能导致内核崩溃。并发访问
如果多个 CPU 或线程可能同时访问同一寄存器,需使用自旋锁(spin_lock
)保护。设备特性
某些设备的寄存器可能需要特定的访问顺序(例如先写命令寄存器再写数据寄存器)。
六、其他相关函数
- 8 位访问:
readb()
/writeb()
- 16 位访问:
readw()
/writew()
- 64 位访问:
readq()
/writeq()
通过合理使用 readl()
和 writel()
,可以确保在内核中安全、高效地操作硬件寄存器。
Linux内核中写完数据将其写入到flash中,确保不再缓存中
Linux内核中写完数据将其写入到flash中,确保不再缓存中的接口
在 Linux 内核中,若要将数据写入 Flash 并确保数据彻底落盘(即不在缓存中残留),需要根据具体的存储架构和访问方式选择合适的方法。以下是几种典型场景的解决方案:
一、块设备层(如通过 MTD 转换为块设备)
若 Flash 被抽象为块设备(如 /dev/mtdblockX
或通过 FTL 转换的块设备),需确保数据绕过缓存或主动刷写缓存:
1. 直接 I/O(绕过页缓存)
在打开设备时使用 O_DIRECT
标志,强制绕过内核页缓存,直接操作物理存储:
1 | int fd = open("/dev/mtdblock0", O_WRONLY | O_DIRECT); |
- 注意:需要对齐的缓冲区(通常按 512 字节对齐)。
2. **主动刷写缓存
Linux内核中写数据将其写入到DDR中而不经过缓存的接口
在 Linux 内核中,若要将数据直接写入 DDR 内存 并确保不经过 CPU 缓存(即绕过缓存,直接操作物理内存),需要根据具体场景选择合适的方法。以下是几种典型场景的实现方案:
一、直接操作非缓存内存(Non-Cached Memory)
如果目标内存区域需要被硬件(如 DMA 控制器或外设)直接访问,或需要避免缓存一致性问题,可以通过以下接口分配或映射 非缓存内存:
1. 使用 dma_alloc_coherent
(一致性 DMA 内存)
此函数分配的内存对 CPU 和设备是 一致性(Coherent) 的,CPU 写入的数据会直接反映到物理内存,无需手动刷新缓存。
适用场景:需要与 DMA 控制器或其他硬件共享内存时。
1 |
|
关键特性:
- 内存物理连续,适合 DMA 传输。
- 自动保证 CPU 和设备的内存视图一致。
2. 使用 ioremap
映射非缓存物理内存
如果已知某段物理地址需要直接访问(如预留的硬件缓冲区),可通过 ioremap
将其映射为内核虚拟地址,并指定非缓存属性。
1 |
|
关键特性:
- 需手动指定物理地址(通常配合设备树预留内存使用)。
ioremap
默认可能映射为设备内存(非缓存),但具体行为依赖架构和 flags。
3. 使用 memremap
显式指定缓存策略
在较新内核中,memremap
允许更灵活地控制内存映射属性:
1 |
|
二、强制绕过 CPU 缓存的写入操作
若已有一段普通内存(如通过 kmalloc
分配),但需要确保某次写入直接到达 DDR,可以手动禁用缓存或刷新缓存:
1. 使用 non-temporal
写入指令(如 movnti
)
通过 SSE/AVX 指令直接写入内存而不污染缓存(需在用户态或内核模块中内联汇编):
1 | // 示例:使用 movnti 写入 32 位数据(x86 架构) |
注意:
- 需处理内存对齐(通常 16 字节对齐)。
- 平台依赖性强,通常仅用于性能关键代码。
2. 手动刷新缓存
写入普通缓存内存后,强制刷新缓存使数据落盘:
1 |
|
适用场景:
- 临时性强制刷新,但频繁操作会严重影响性能。
三、保留内存(Reserved Memory)
通过设备树预留一段物理内存,确保内核不会将其分配给普通内存池,然后映射为直接访问的非缓存内存:
1. 设备树中预留内存
1 | /reserved-memory { |
2. 驱动中映射预留内存
1 | // 获取预留内存信息 |
四、注意事项
缓存一致性
- 若内存被多个 CPU 核心或设备共享,需确保使用正确的同步机制(如内存屏障
wmb()
/rmb()
)。 - 避免混合使用缓存和非缓存方式访问同一内存区域。
- 若内存被多个 CPU 核心或设备共享,需确保使用正确的同步机制(如内存屏障
性能影响
- 绕过缓存会降低 CPU 写入速度(无缓存加速)。
- 频繁的非缓存操作可能增加总线带宽压力。
平台依赖性
- 非缓存内存的实现细节(如
ioremap
属性)因架构而异(ARM、x86、RISC-V 等需区别处理)。
- 非缓存内存的实现细节(如
五、总结
方法 | 适用场景 | 特点 |
---|---|---|
dma_alloc_coherent |
DMA 设备共享内存 | 自动维护一致性,物理连续,需释放 |
ioremap /memremap |
访问预留的物理地址 | 灵活指定缓存策略,需手动管理映射 |
保留内存 + 设备树 | 固定物理地址的硬件缓冲区 | 避免内核占用,需设备树配合 |
手动刷新缓存 | 临时强制数据落盘 | 性能开销大,仅适合低频操作 |
根据具体需求选择合适方法,确保数据直接写入 DDR 且避免缓存干扰。