I/O 寄存器和 RAM 一个主要的不同是:I/O 操作会带来副作用, 而内存操作没有。一个内存写操作的唯一效果是存储一个值到某个地址, 并且一个内存读操作返回上次写到该地址的值. 由于内存存取速度对CPU 性能是至关重要的, 这种无副作用的操作已被多种方式优化: 值被缓存, 并且读/写指令被重新编排.
编译器能够缓存数据值到CPU 寄存器而不写到内存, 并且即使数据值已经存储到内存, 读和写操作都能够在缓冲内存中进行而不是直接接触物理RAM. 此外,指令重编排可能在编译器级别或在硬件级别发生: 很多情况下,如果一个指令以不同于在程序文本中出现的顺序来执行(例如, 为避免在 RISC 流水线中的互锁),它能够执行得更快,
对于传统内存(至少在单处理器系统)来说,这些优化是透明和有益的。驱动直接存取I/O寄存器的主要目的是能提高CPU性能。然而,这些优化对正确的 I/O 操作可能是致命的. 处理器无法预见这种情形, 一些其他的操作(在一个独立处理器上运行, 或者发生在一个 I/O 控制器的事情)依赖内存存取的顺序. 编译器或者 CPU 可能只尽力胜过你并且重编排你请求的操作; 结果可能是奇怪的错误而非常难于调试. 因此, 一个驱动必须确保没有进行缓冲并且在存取寄存器时没有发生读或写的重编排.
--------------------------------------------------------------------------------------------------------------------------
1. I/O端口:当一个寄存器或内存位于I/O空间时,称其为I/O端口。
2. I/O内存:当一个寄存器或内存位于内存空间时,称其为I/O内存。
再来看一下I/O寄存器和常规内存的区别:I/O寄存器具有边际效应(side effect),而内存操作则没有,内存写操作的唯一结果就是在指定位置存贮一个数值;内存读操作则仅仅是返回指定位置最后一次写入的数值。何为边际效应呢?就是读取某个地址时可能导致该地址内容发生变化。比如很多设备的中断状态寄存器只要一读取,便自动清零。
现在来看一看如何在Linux驱动程序中使用I/O端口和I/O内存。
使用I/O端口的步骤:1. 申请 2. 访问 3. 释放
访问I/O端口:
访问I/O端口时,多数硬件都会把8位,16位和32位的端口区分开。因此,C语言程序中必须调用不同的函数来访问大小不同的端口。
使用I/O内存的步骤:1. 申请 2. 映射 3. 访问 4. 释放
根据计算机平台和所使用总线的不同,I/O内存可能是,也可能不是通过页表访问的。如果访问是经由页表进行的,内核必须首先安排物理地址使其对设备驱动程序可见(这通常意味着在进行任何I/O之前必须先调用ioremap)。如果访问无需页表,那么I/O内存区域就非常类似于I/O端口,可以使用适当形式的函数读写它们。