__raw_writel, writel_relaxed 和 writel的区别

本文探讨了在ARM平台上的几种内存写操作方法,包括__raw_writel、write_relaxed和writel的区别与应用场景。特别关注了这些操作如何确保数据的一致性和同步,尤其是在涉及到DMA操作的情况下。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因为对别的平台不了解,下面仅谈它们在ARM上的区别


__raw_writel: 因为有volatile关键字, 所以编译器不会打乱多个__raw_writel的执行顺序。

对于ARM而言,当多个写以代码的顺序到达相同设备时,执行的顺序也是被保证的,不过

对于不同的设备,执行的顺序就不被保证了。


write_relaxed: 在ARM平台上与__raw_writel一样,因为与__raw_writel相比,它只多做

了一个大端到小端转换。


writel: 当CONFIG_ARM_DMA_MEM_BUFFERABLE被定义时,在写之前,它会多做

一个DSB和L2的sync。 为什么需要这样呢? 因为,DMA buffer都是 bufferable了,

(详见加入CONFIG_ARM_DMA_MEM_BUFFERABLE的commit log)

在DMA进行之前要保证write buffer里的数据都到memory里。

所以说: 只有在有DMA操作的代码里,writel才是必须的,其实它是一个

当DMA buffer变成bufferable之后的一个补充品。因为要保证DMA buffer里的

内容在DMA开始之前都到memory里,所以开始DMA的指令(writel)里加了

sync memory的指令。


其实: writel也不能保证写的东西一定完成,它只能保证写的指令或写的内容

已经到了设备端,但具体有没有写完成是不知道的。如果有一定要设备端

写完成才能做下面的指令的要求,最好再用readl把它读回来。


### Linux IO内存访问函数的替代方案 在Linux内核开发中,除了常用的 `asm/io.h` 中定义的基础 I/O 操作函数(如 `readb`, `writeb`, `readw`, `writel` 等),还存在多种高级或特定用途的函数来实现对设备寄存器或其他硬件资源的操作。以下是一些常见的替代方案及其特点: --- #### 1. **`ioread32` `iowrite32` 的使用** `ioread32` `iowrite32` 是一组专门设计用于访问已映射到虚拟地址空间的 I/O 内存区域的函数[^3]。它们的功能类似于 `readl` `writel`,但在某些平台上可能具有更高的兼容性优化特性。 ```c #include <linux/io.h> u32 data = ioread32(virtual_address); // 从指定地址读取32位数据 iowrite32(data, virtual_address); // 向指定地址写入32位数据 ``` 这些函数适用于已经通过 `ioremap()` 完成物理地址到虚拟地址映射的情况。相比直接使用指针操作,`ioread32` `iowrite32` 更加安全且易于移植。 --- #### 2. **`_rep` 系列函数** 为了高效批量传输数据,Linux 提供了 `_rep` 系列函数,允许一次性读取或写入多个单位的数据。例如: - `ioread8_rep(void __iomem *addr, u8 *buffer, size_t count)`:从 I/O 地址连续读取 `count` 字节并存储到缓冲区。 - `iowrite8_rep(void __iomem *addr, const u8 *buffer, size_t count)`:将缓冲区中的 `count` 字节写入到 I/O 地址。 ```c u8 buffer[10]; ioread8_rep(virtual_address, buffer, sizeof(buffer)); // 批量读取10字节 ``` 此类函数特别适合需要频繁进行大量数据交互的应用场景。 --- #### 3. **`__raw_*` 系列函数** 如果当前系统不需要考虑字节序问题,可以直接使用 `__raw_*` 系列函数。这些函数绕过了默认的字节序转换逻辑,从而提高了性能[^3]。 常见函数包括: - `__raw_readb(addr)` - `__raw_readw(addr)` - `__raw_readl(addr)` - `__raw_writeb(value, addr)` - `__raw_writew(value, addr)` - `__raw_writel(value, addr)` 示例代码如下: ```c u32 raw_data = __raw_readl(virtual_address); // 不带字节序转换的读取 __raw_writel(raw_data | BIT(0), virtual_address); // 修改后重新写回 ``` 需要注意的是,`__raw_*` 函数的行为依赖于具体的架构实现,因此在跨平台项目中需谨慎使用。 --- #### 4. **自定义汇编代码** 对于极端高性能需求或特殊硬件适配情况,可以编写定制化的汇编代码片段以获得最大灵活性效率[^5]。这种方法虽然强大但复杂度高,通常只在必要时才采用。 示例代码(假设为 ARM 架构): ```c static inline void custom_write(u32 val, volatile void __iomem *addr) { asm volatile("str %0, [%1]" : : "r"(val), "r"(addr)); } ``` 此方法完全规避了 C 层面的抽象开销,但也显著增加了调试难度。 --- #### 5. **内存屏障相关函数** 在多处理器或多线程环境下工作时,必须确保正确的内存访问顺序以防竞态条件发生。为此,Linux 提供了一系列带有同步特性的 I/O 访问函数[^4]: - `readl_relaxed(addr)` / `writel_relaxed(value, addr)`:无显式内存屏障的轻量化版本。 - `readl_acqire(addr)` / `writel_release(value, addr)`:结合获取/释放语义的强一致性模型。 典型应用案例: ```c u32 status = readl_acquire(status_reg); // 获取状态值的同时建立加载屏障 if (!(status & STATUS_READY)) { pr_err("Device not ready!\n"); } ``` --- #### 总结对比表 | 方法 | 特点 | |---------------------|------------------------------------------------------------------------------------------| | `ioread32/iowrite32` | 高层封装,易用性强 | | `_rep` 系列 | 支持批量数据传输 | | `__raw_*` | 跳过字节序处理,提高速度 | | 自定义汇编 | 最大自由度与性能,但复杂度极高 | | 内存屏障增强型 | 解决并发环境下的安全性问题 | ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值