Linux驱动程序之I/O端口操作和I/O内存操作__raw_readl和__raw_writel函数

本文详细介绍了Linux系统中I/O操作的基本实现原理,重点讲解了__raw_readl和__raw_writel函数的作用及使用方法,并通过s3c2440驱动程序中的实例展示了这些函数的应用。

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

1、__raw_readl和__raw_writel函数

     Linux对I/O的操作都定义在asm/io.h中,相应的在arm平台下,就在asm-arm/io.h中。


#define __raw_readl(a)   (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a))
#define __raw_writel(v,a) (__chk_io_ptr(a), *(volatile unsigned int __force   *)(a) = (v))

     在include\linux\compiler.h中:

#ifdef __CHECKER__
……
extern void __chk_io_ptr(void __iomem *);
#else
……
# define __chk_io_ptr(x) (void)0
……
#endif

     __raw_readl(a)展开是:((void)0, *(volatile unsigned int _force *)(a))。在定义了__CHECKER__的时候先调用__chk_io_ptr检查该地址,否则__chk_io_ptr什么也不做,*(volatile unsigned int _force *)(a)就是返回地址为a处的值。(void)xx的做法有时候是有用的,例如编译器打开了检查未使用的参数的时候需要将没有用到的参数这么弄一下才能编译通过。__chk_io_ptr()是编译器为了更细致地检查参数的属性,用于调试,正常编译时没有作用。volatile为了防止Compiler优化。

     CPU对I/O的物理地址的编程方式有两种:一种是I/O映射,一种是内存映射。__raw_readl和__raw_writel等是原始的操作I/O的方法,由此派生出来的操作方法有:inb、outb、_memcpy_fromio、readb、writeb、ioread8、iowrite8等。


举例:s3c2440驱动程序里面的s3c2410_gpio_setpin()函数:

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)
{
    void __iomem *base = S3C24XX_GPIO_BASE(pin);
    unsigned long offs = S3C2410_GPIO_OFFSET(pin);
    unsigned long flags;
    unsigned long dat;

    local_irq_save(flags); //保存当前处理器上的中断状态,并禁用当前处理器上的中断发送

    dat = __raw_readl(base + 0x04);
    dat &= ~(1 << offs);
    dat |= to << offs;
    __raw_writel(dat, base + 0x04);

    local_irq_restore(flags);  //与local_irq_save()相反
}

<think>好的,用户现在问的是__raw_readlreadl的区别。之前他们问过关于scatter walk结构体的问题,看来他们对Linux内核或底层编程比较感兴趣。这次的问题涉及到内存映射I/O操作,可能需要解释这两个函数的底层机制适用场景。 首先,我需要回忆这两个函数的定义作用。记得在Linux内核中,readl__raw_readl都是用来读取内存映射的I/O寄存器的,但它们在实现使用上有区别。用户可能是在开发驱动程序或者进行内核模块编程时遇到了问题,需要了解何时使用哪个函数。 接下来,要考虑用户可能的背景。他们可能是内核开发者,或者在学习设备驱动开发,需要处理硬件寄存器。这时候,理解这两个函数内存屏障、字节序处理以及可移植性差异就很重要了。如果选错了函数,可能会导致硬件操作不正确,或者代码在不同架构上出现问题。 然后,我需要详细比较这两个函数。首先,内存屏障方面,readl通常包含内存屏障,确保执行顺序,防止编译器或CPU重排序指令,而__raw_readl没有这个保证。这对于多核系统或需要严格操作顺序的设备寄存器非常重要。 字节序处理方面,readl会自动处理字节序转换,确保在不同端模式的处理器上得到正确的值,而__raw_readl直接读取原始数据,不进行转换。这意味着在使用__raw_readl时,开发者需要自己处理字节序,尤其是在跨平台代码中。 可移植性也是一个关键点。readl是内核推荐的接口,更安全,适合大多数情况,而__raw_readl通常在特定架构或需要直接操作的情况下使用,比如寄存器本身没有端序问题,或者在内核底层代码中需要避免额外开销。 用户可能没有明确说出他们的具体场景,但需要根据这些信息判断哪种情况适用。例如,在编写可移植的驱动时应该用readl,而在性能敏感或明确知道硬件情况的代码中可能用__raw_readl。 还要注意潜在的问题,比如错误使用__raw_readl导致的数据不一致或端序错误,以及缺少内存屏障引发的竞态条件。这些都可能成为用户实际开发中的隐患,所以需要强调使用场景注意事项。 最后,要确保回答结构清晰,分点说明,让用户能逐步理解两者的区别,并通过示例代码加深印象。同时提醒用户一般情况下优先使用readl,只有在特定需求下才考虑__raw_readl,避免滥用带来的问题。</think>在 Linux 内核中,`__raw_readl` `readl` 都是用于访问**内存映射 I/O 寄存器**的接口,但它们在实现细节使用场景上有重要区别: --- ### 1. **内存屏障(Memory Barrier)** | 函数 | 内存屏障行为 | |---------------|----------------------------------------------------------------------------| | `readl` | **自动插入内存屏障**(`rmb()`或`dma_rmb()`),保证读取顺序性,防止 CPU/编译器乱序优化 | | `__raw_readl` | **无内存屏障**,直接读取寄存器,不保证执行顺序(需开发者手动控制) | - **典型场景**: - 当需要确保在读取寄存器前,所有之前的操作已完成(如 DMA 传输)时,必须用 `readl`。 - 若寄存器读取顺序无关紧要(如调试或单核独占访问),可用 `__raw_readl` 减少性能损耗。 --- ### 2. **字节序处理(Endianness)** | 函数 | 字节序转换 | |---------------|--------------------------------------------------------------------------| | `readl` | **自动处理字节序**(根据 CPU 端序调整) | | `__raw_readl` | **直接返回原始值**(不转换字节序) | - **典型场景**: - 若寄存器值本身是处理器原生字节序(如某些硬件状态寄存器),使用 `__raw_readl`。 - 若寄存器值需要统一为小端/大端格式(如网络协议栈),优先用 `readl`。 --- ### 3. **可移植性与安全性** | 函数 | 适用场景 | |---------------|----------------------------------------------------------------------| | `readl` | **内核推荐接口**,安全且跨架构兼容(如 ARM、x86、PowerPC) | | `__raw_readl` | **低层专用接口**,需开发者明确硬件特性(常见于特定架构代码,如 `arch/arm/`) | --- ### 4. **底层实现对比** 以 ARM 架构为例(代码简化): ```c // readl 的实现(包含屏障字节序转换) #define readl(c) ({ \ u32 __v = __raw_readl(c); \ rmb(); // 插入读内存屏障 __v = le32_to_cpu(__v); // 转换为 CPU 端序 __v; }) // __raw_readl 的直接操作 #define __raw_readl(addr) (*(volatile u32 *)(addr)) ``` --- ### 5. **实际使用建议** - **优先使用 `readl`**: 适用于 99% 的驱动开发场景,保证安全性可移植性。 - **谨慎使用 `__raw_readl`**: 仅在以下情况使用: 1. 需要极高性能(如中断处理) 2. 明确知道硬件寄存器无端序问题 3. 在内核底层代码中手动控制内存屏障(如 DMA 映射代码) --- ### 6. **常见错误示例** ```c // 错误:未处理端序,导致读取值错误 u32 val = __raw_readl(addr); // 假设硬件寄存器是小端格式,而 CPU 是大端 // 正确:自动转换字节序 u32 val = readl(addr); ``` --- 若需进一步分析具体代码场景,可以提供上下文,我会给出针对性建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值