Linux 提供以下宏来解决所有可能的排序问题:

#include linux/kernel.h>

void barrier(void) /*告知编译器插入一个内存屏障但是对硬件没有影响。编译后的代码会将当前CPU 寄存器中所有修改过的数值保存到内存中, 并当需要时重新读取它们。可阻止在屏障前后的编译器优化,但硬件能完成自己的重新排序。其实linux/kernel.h> 中并没有这个函数,因为它是在kernel.h包含的头文件compiler.h中定义的*/

#include linux/compiler.h>

# define barrier() __memory_barrier()

#include asm/system.h>

void rmb(void); /*保证任何出现于屏障前的读在执行任何后续的读之前完成*/

void wmb(void); /*保证任何出现于屏障前的写在执行任何后续的写之前完成*/

void mb(void); /*保证任何出现于屏障前的读写操作在执行任何后续的读写操作之前完成*/

void read_barrier_depends(void); /*

一种特殊的、弱些的读屏障形式。rmb 阻止屏障前后的所有读指令的重新排序,read_barrier_depends

只阻止依赖于其他读指令返回的数据的读指令的重新排序。区别微小, 且不在所有体系中存在。除非你确切地理解它们的差别,

并确信完整的读屏障会增加系统开销,否则应当始终使用 rmb。*/

/*以上指令是barrier的超集*/

void smp_rmb(void);

void smp_read_barrier_depends(void);

void smp_wmb(void);

void smp_mb(void);

/*仅当内核为 SMP 系统编译时插入硬件屏障; 否则, 它们都扩展为一个简单的屏障调用。*/

典型的应用:

writel(dev->registers.addr, io_destination_address);

writel(dev->registers.size, io_size);

writel(dev->registers.operation, DEV_READ);

wmb();/*类似一条分界线,上面的写操作必然会在下面的写操作前完成,但是上面的三个写操作的排序无法保证*/

writel(dev->registers.control, DEV_GO);

内存屏障影响性能,所以应当只在确实需要它们的地方使用。不同的类型对性能的影响也不同,因此要尽可能地使用需要的特定类型。值得注意的是大部分处理同步的内核原语,例如自旋锁和atomic_t,也可作为内存屏障使用。

某些体系允许赋值和内存屏障组合,以提高效率。它们定义如下:

#define set_mb(var, value) do {var = value; mb();} while 0

/*以下宏定义在arm体系中不存在*/

#define set_wmb(var, value) do {var = value; wmb();} while 0

#define set_rmb(var, value) do {var = value; rmb();} while 0

使用do...while 结构来构造宏是标准 C 的惯用方法,它保证了扩展后的宏可在所有上下文环境中被作为一个正常的 C 语句执行。

使用 I/O 端口

I/O 端口是驱动用来和许多设备之间的通讯方式。

I/O 端口分配

在尚未取得端口的独占访问前,不应对端口进行操作。内核提供了一个注册用的接口,允许驱动程序声明它需要的端口:

#include linux/ioport.h>

struct resource *request_region(unsigned long first, unsigned long n, const char *name);/*告诉内核:要使用从 first 开始的 n 个端口,name 参数为设备名。若分配成功返回非 NULL,否则将无法使用需要的端口。*/

/*所有的的端口分配显示在 /proc/ioports 中。若不能分配到需要的端口,则可以到这里看看谁先用了。*/

/*当用完 I/O 端口集(可能在模块卸载时), 应当将它们返回给系统*/

void release_region(unsigned long start, unsigned long n);

int check_region(unsigned long first, unsigned long n);

/*检查一个给定的 I/O 端口集是否可用,若不可用, 返回值是一个负错误码。不推荐使用*/

操作 I/O 端口

在驱动程序注册I/O 端口后,就可以读/写这些端口。大部分硬件会把8、16和32位端口区分开,不能像访问系统内存那样混淆使用。驱动必须调用不同的函数来存取不同大小的端口。

只支持内存映射的 I/O 寄存器的计算机体系通过重新映射I/O端口到内存地址来伪装端口I/O。为了提高移植性,内核向驱动隐藏了这些细节。Linux 内核头文件(体系依赖的头文件 ) 定义了下列内联函数(有的体系是宏,有的不存在)来访问 I/O 端口:

unsigned inb(unsigned port);

void outb(unsigned char byte, unsigned port);

/*读/写字节端口( 8 位宽 )。port 参数某些平台定义为 unsigned long ,有些为 unsigned short 。 inb 的返回类型也体系而不同。*/

unsigned inw(unsigned port);

void outw(unsigned short word, unsigned port);

/*访问 16位 端口( 一个字宽 )*/

unsigned inl(unsigned port);

void outl(unsigned longword, unsigned port);

/*访问 32位 端口。 longword 声明有的平台为 unsigned long ,有的为 unsigned int。*/

在用户空间访问 I/O 端口

以上函数主要提供给设备驱动使用,但它们也可在用户空间使用,至少在 PC上可以。 GNU C 库在  中定义了它们。如果在用户空间代码中使用必须满足以下条件:

1)程序必须使用 -O 选项编译来强制扩展内联函数。

2)必须用ioperm 和 iopl 系统调用(#include ) 来获得对端口 I/O 操作的权限。ioperm 为获取单独端口操作权限,而 iopl 为整个 I/O 空间的操作权限。 x86 特有的)

3)程序以 root 来调用 ioperm 和 iopl,或是其父进程必须以 root 获得端口操作权限。x86 特有的)

若平台没有 ioperm 和 iopl 系统调用,用户空间可以仍然通过使用 /dev/prot 设备文件访问 I/O 端口。注意:这个文件的定义是体系相关的,并且I/O 端口必须先被注册。

串操作

除了一次传输一个数据的I/O操作,一些处理器实现了一次传输一个数据序列的特殊指令,序列中的数据单位可以是字节、字或双字,这是所谓的串操作指

令。它们完成任务比一个 C 语言循环更快。下列宏定义实现了串I/O,它们有的通过单个机器指令实现;但如果目标处理器没有进行串 I/O

的指令,则通过执行一个紧凑的循环实现。 有的体系的原型如下:

void insb(unsigned port, void *addr, unsigned long count);

void outsb(unsigned port, void *addr, unsigned long count);

void insw(unsigned port, void *addr, unsigned long count);

void outsw(unsigned port, void *addr, unsigned long count);

void insl(unsigned port, void *addr, unsigned long count);

void outsl(unsigned port, void *addr, unsigned long count);

使用时注意: 它们直接将字节流从端口中读取或写入。当端口和主机系统有不同的字节序时,会导致不可预期的结果。 使用 inw 读取端口应在必要时自行转换字节序,以匹配主机字节序。

暂停式 I/O

为了匹配低速外设的速度,有时若 I/O 指令后面还紧跟着另一个类似的I/O指令,就必须在 I/O 指令后面插入一个小延时。在

这种情况下,可以使用暂停式的I/O函数代替通常的I/O函数,它们的名字以 _p 结尾,如 inb_p、outb_p等等。

这些函数定义被大部分体系支持,尽管它们常常被扩展为与非暂停式I/O

同样的代码。因为如果体系使用一个合理的现代外设总线,就没有必要额外暂停。细节可参考平台的 asm 子目录的 io.h

文件。以下是include\asm-arm\io.h中的宏定义:

#define outb_p(val,port)    outb((val),(port))

#define outw_p(val,port)    outw((val),(port))

#define outl_p(val,port)    outl((val),(port))

#define inb_p(port)        inb((port))

#define inw_p(port)        inw((port))

#define inl_p(port)        inl((port))

#define outsb_p(port,from,len)    outsb(port,from,len)

#define outsw_p(port,from,len)    outsw(port,from,len)

#define outsl_p(port,from,len)    outsl(port,from,len)

#define insb_p(port,to,len)    insb(port,to,len)

#define insw_p(port,to,len)    insw(port,to,len)

#define insl_p(port,to,len)    insl(port,to,len)

由此可见,由于arm使用内部总线,就没有必要额外暂停,所以暂停式的I/O函数被扩展为与非暂停式I/O 同样的代码。


相关内容