内核里面writel(readl)是如何实现的

本文详细解析了操作系统中的writel与readl函数,包括它们如何实现内存映射设备的读写操作,以及底层的具体实现细节。这些函数用于32位数据的写入和读取,对于理解操作系统内存管理至关重要。
writel和readl,这两个个函数实现在操作系统层,有内存保护的情况下,往一个寄存器或者内存地址写一个数据。先说一下writel:
 
在arch/alpha/kernel/io.c中有
188 void writel(u32 b, volatile void __iomem *addr)
189 {
190     __raw_writel(b, addr);
191     mb();
192 }
 
 
这样一个writel函数的作用应该是向一个地址上写一个值,我想知道这个函数底下具体实现的细节,于是往下继续跟踪代码:__raw_writel(b, addr);(发现在同目录下)
 
129 void __raw_writel(u32 b, volatile void __iomem *addr)
130 {
131     IO_CONCAT(__IO_PREFIX,writel)(b, addr);
132 }
 
再往下跟踪 IO_CONCAT,在对应的io.h中的定义如下:
134 #define IO_CONCAT(a,b)  _IO_CONCAT(a,b)
135 #define _IO_CONCAT(a,b) a ## _ ## b
这段代码前几天问过了,是标示将两边的字符串连接起来的意思。
 
跟踪__IO_PREFIX 定义如下
501 #undef __IO_PREFIX
502 #define __IO_PREFIX     apecs
 
继续阅读代码,看看定义__IO_PREFIX之后紧接着包含了哪个头文件。在哪个头文
件里面寻找答案。对于你的apsec,看看以下代码段(linux-2.6.28-rc4)

arch/alpha/include/asm/core_apecs.h
------------------------------------------
#undef __IO_PREFIX
#define __IO_PREFIX             apecs
#define apecs_trivial_io_bw     0
#define apecs_trivial_io_lq     0
#define apecs_trivial_rw_bw     2
#define apecs_trivial_rw_lq     1
#define apecs_trivial_iounmap   1
#include <asm/io_trivial.h>
------------------------------------------

前往arch/alpha/include/asm/io_trivial.h
------------------------------------------
__EXTERN_INLINE void
IO_CONCAT(__IO_PREFIX,writel)(u32 b, volatile void __iomem *a)
{
       *(volatile u32 __force *)a = b;
}
 
就是最终通过*(volatile u32 __force *)a = b;
来写入数据的。
同样的readl读取数据也和writel类似,这里就不重复了。
 
(如果在没有os,没有mmu的情况下,当开发板裸跑的时候,我们只需要一句话就一切ok:
*(unsigned long *)addr = value)


 readX/writeX() are used to access memory mapped devices. On some

 architectures the memory mapped IO stuff needs to be accessed

 differently. On the simple architectures, we just read/write the

 memory location directly.

writel() 往内存映射的 I/O 空间上写数据,wirtel()  I/O 上写入 32 位数据 (4字节)。

 

 原型:

#include  

void writel (unsigned char data unsigned short addr )

 

 

readl() 从内存映射的 I/O 空间读取数据,readl 从 I/O 读取 32 位数据 字节 )

 

原型:

#include  

unsigned char readl (unsigned int addr )

 

变量    addr  是 I/O 地址。

返回值  从 I/O 空间读取的数值。

 

定义

#define readb __raw_readb

#define readw(addr) __le16_to_cpu(__raw_readw(addr))

#define readl(addr) __le32_to_cpu(__raw_readl(addr))

#ifndef __raw_readb

static inline u8 __raw_readb(const volatile void __iomem *addr)

{

    return *(const volatile u8 __force *) addr;

}

#endif

 

#ifndef __raw_readw

static inline u16 __raw_readw(const volatile void __iomem *addr)

{

    return *(const volatile u16 __force *) addr;

}

#endif

 

#ifndef __raw_readl

static inline u32 __raw_readl(const volatile void __iomem *addr)

{

    return *(const volatile u32 __force *) addr;

}

#endif

 

#define writeb __raw_writeb

#define writew(b,addr) __raw_writew(__cpu_to_le16(b),addr)

#define writel(b,addr) __raw_writel(__cpu_to_le32(b),addr)



<think>嗯,让我梳理一下用户的问题:用户在Linux驱动开发场景下询问writel函数是否会立即写入目标寄存器。这是个涉及硬件交互细节的专业问题,需要结合内核机制和硬件特性来分析。 用户提供了四篇站内引用,其中[2][3][4]明确提到writel是用于寄存器读写的内核函数,[3]特别指出其作用是在有内存保护的环境下操作寄存器。不过这些引用都没直接回答"立即写入"这个时间性问题。 从计算机体系结构角度,writel的写入行为其实包含三个层面: 首先在软件层面,writel通过MMIO(内存映射I/O)将数据写入CPU指定的总线地址。此时数据可能还停留在CPU缓存或写缓冲中,尤其是当启用Cache时。这里需要解释CPU缓存机制对写入延迟的影响。 接着是硬件层面,外设寄存器的性质起决定性作用。普通内存寄存器可能允许延迟写入,但像中断控制寄存器这类关键寄存器,内核会通过内存屏障强制同步。这点需要强调不同寄存器类型的差异。 最后考虑总线协议,PCIe等总线本身就支持异步传输,此时写入操作会被放入队列。这里可以举例说明总线事务的排队机制。 用户作为嵌入式开发者,可能真正关心的是寄存器操作的可靠性。比如在配置GPIO后立即读取状态是否有效,这时候需要建议使用读-修改-写模式配合内存屏障函数mb()。 突然想到个易错点:有些开发者误以为writel返回即代表硬件完成操作,这个误解可能导致驱动BUG。应该在回复中明确警示这一点,并给出标准做法——当需要确保写入完成时,应在writel后追加readl回读作为同步。 对了,X86架构的IO空间操作(in/out指令)是即时生效的,但ARM/PowerPC等架构的MMIO机制差异较大,这个细节也要提及以避免架构混淆。 </think>在Linux系统中,**`writel`函数不保证立即将数据写入目标硬件寄存器**,其行为受以下关键因素影响: ### 核心影响因素 1. **CPU缓存与写缓冲** - `writel`操作的是**内存映射I/O(MMIO)空间**,数据可能暂存于CPU写缓冲或缓存层级中。 - 现代CPU为提高性能会延迟实际写入,直至缓存刷新或屏障指令触发同步。 2. **内存屏障(Memory Barrier)** - 若代码未显式添加内存屏障(如`mb()`, `wmb()`),写入操作可能被处理器重排序。 - 示例:仅用`writel`而无屏障时,后续指令可能先于寄存器写入执行。 3. **总线事务延迟** - 外设总线(如PCIe)可能对写入操作排队,硬件响应时间不可控。 - 例如:低速设备可能需数微秒完成实际寄存器更新。 4. **寄存器类型差异** - **关键寄存器**(如中断控制):通常需立即同步,需主动调用屏障函数。 - **普通配置寄存器**:可容忍延迟,依赖后续屏障统一提交。 ### 确保立即写入的方法 ```c writel(value, reg_addr); // 写入寄存器 mmiowb(); // 内存屏障:强制刷新所有挂起的MMIO写入 ``` - 使用`mmiowb()`或`wmb()`等屏障函数**强制刷新写缓冲**(ARM/X86架构均适用)。 - 部分驱动会通过**读取回显**确认写入完成(如`readl(reg_addr) == value`)。 ### 典型场景分析 | 场景 | 是否需立即写入 | 处理方法 | |---------------------|----------------------|------------------------------| | 中断使能/清除 | ✅ 必须 | `writel() + mmiowb()` | | DMA启动寄存器 | ✅ 必须 | 屏障后检查状态标志 | | 外设配置寄存器 | ️ 通常可延迟 | 依赖后续屏障统一提交 | | 调试寄存器输出 | ❌ 无需 | 直接使用`writel` | ### 总结 `writel`**本身是异步操作**,是否立即生效取决于: 1. **CPU微架构**(缓存/缓冲策略) 2. **显式内存屏障**的使用 3. **外设总线延迟** 4. **寄存器关键性** **开发建议**:对时序敏感的寄存器操作,必须配合内存屏障或回读验证机制[^2][^3][^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值