Linux设备驱动程序中的并发控制

来源:互联网 发布:c语言 输入英文名 编辑:程序博客网 时间:2024/05/19 23:01

并发(Concurrency):多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件资源(全局变量、静态变量等))的访问则很容易导致竞态(Race Conditions)。

在Linux内核中,主要竞态发生于如下几种情况:
1、对称多处理器(SMP)的多个CPU
这里写图片描述
图 SMP体系结构
在SMP情况下,两个核(CPU0和CPU1)的竞态可能发生于:

  • CPU0进程与CPU1的进程之间

  • CPU0的进程与CPU1的中断之间

  • CPU0的中断与CPU1的中断之间

2、单CPU内进程与抢占它的进程
Linux 2.6以后内核支持抢占调度,一个进程在内核执行的时候可能耗完了自己的时间片(timeslice),也可能被另一个更高优先级的进程打断,进程和抢占它的进程访问共享资源的情况类似于SMP的多个CPU。
3、中断(硬中断、软中断、Tasklet、底半部)与进程之间
中断可以打断正在执行的进程,如果中断服务程序访问进程正在访问的资源,则竞态也会发生。
中断也有可能被新的更高优先级的中断打断,因此,多个中断之间本身也可能引起并发而导致竞态。但是Linux 2.6.35之后,就取消中断的嵌套。
解决办法:
保证对共享资源的互斥访问,即一个执行单元在访问共享资源时,其他执行单元被禁止访问。具体Linux内核提供了许多互斥机制如中断屏蔽、原子操作、自旋锁、信号量、互斥体等。

编译乱序和执行乱序

现在的编译器在目标代码优化上都具备对指令进行乱序优化的能力。编译器可以对访存的指令进行乱序,减少逻辑上不必要的访存,以及尽量提高Cache命中率和CPU的Load/Store单元的工作效率。因此在打开编译优化后看到汇编码未按照实际代码排布,这是正常的。
为解决编译乱序在内核里提供了barrier()调用。设置编译屏障后可以保证屏障前的语句和屏障后的语句不乱”串门”。

#define barrier() __asm__ __volatile__("": : :"memory")

解决编译乱序问题不能使用volatile,关键字volatile,含义是”易变的”。
暗示除了当前执行线索以外,其他的执行线索也可能改变某内存。
例如:
线程A读取var这个变量两次而没有修过var,编译器可能觉得读一次就行了,第2次直接取第1次的结果。但是加了volatile修饰var后,则告诉编译器var变量可能会随时在其他地方修改,线程A中第2次内存读取操作就不会被优化掉了。volatile也不具备保护临界资源的作用。内核中也不太喜欢volatile,可以查看内核文档:Document/volatile-considered-harmful.txt
执行乱序(Out of Order Execution):
高级的CPU可根据自己缓存的组织特性,将访存指令重新排序执行。连续地址的访问可能会先执行,因为这样缓存命中率高。有的还允许访存的非阻塞,即如果前面一条访存指令因为缓存不命中,造成长延时的存储访问时,后面的访存指令可以先执行,以便从缓存中取数。因此,即使从汇编上看顺序正确的指令,其执行的顺序也是不可预知的。
CPU0上面执行:

while (f == 0);print x;
CPU1上面执行:
x = 42;f = 1;

以上程序在CPU0上打印不一定是42
处理器未解决多核间一个核的内存行为对另外一个核可见的问题,引入内存屏障指令:
DMB(数据内存屏障):在DMB之后的显式内存访问执行前,保证所有在DMB指令之前的内存访问完成;
DSB(数据同步屏障):等待所有在DSB指令之前的指令完成(位于此指令前的所有显式内存访问均完成,位于此指令前的所有缓存、跳转预测和TLB维护操作全部完成);
ISB(指令同步屏障):Flush流水线,使得所有ISB之后执行的指令都是从缓存或内存中获得的
Linux内核的自旋锁、互斥体等互斥逻辑,需要用到上述指令。
基于内存屏障的互斥逻辑实现:

LOCKED      EQU 1UNLOCKED    EQU 0lock_mutex    ;互斥量是否锁定?    LDREX r1, [r0]      ;检查是否锁定    CMP r1, #LOCKED     ;和"locked"比较    WFEEQ               ;互斥量已经锁定, 进入休眠    BEQ lock_mutex      ;被唤醒, 重新检查互斥量是否锁定    ;尝试锁定互斥量    MOV r1, #LOCKED    STREX r2, r1, [r0]  ;尝试锁定    CMP r2, #0x0        ;检查STR指令是否完成    BNE lock_mutex      ;如果失败重试    DMB                 ;进入被保护的资源前需要隔离, 保证互斥量已经被更新    BX lrunlock_mutex    DMB                 ;保证资源的访问已经结束    MOV r1, #UNLOCKED   ;向锁定域写"unlocked"    STR r1, [r0]    DSB                 ;保证在CPU唤醒前完成互斥量状态更新    SEV                 ;像其他CPU发送事件, 唤醒任何等待事件的CPU    BX lr

当程序访问外设寄存器时,这些寄存器访问顺序在CPU的逻辑上构不成依赖关系,但从外设的逻辑角度来讲,可能需要固定的寄存器读写顺序,这时需要用CPU的内存屏障指令。内核文档详见:Document/memory-barriers.txt和Document/io_ordering.txt

1 0
原创粉丝点击