并发和竟态的基本概念

并发产生竟态

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

解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问,中断屏蔽、原子操作、自旋锁、信号量、互斥体等是 Linux 设备驱动中可采用的互斥途径。

编译乱序和执行乱序

编译

程序编译的时候,如果开启了编译优化,编译器就具备对指令进行乱序优化的能力。编译器可以对访存的指令进行乱序,减少逻辑上不必要的访存,以及尽量提高Cache命中率和CPU的Load/Store单元的工作效率。这样汇编码就不会严格按程序逻辑来生成
.....
int a=0;
int b=0;
b = a + 1;
a = 1;
.....
解决编译乱序问题,需要通过 barrier ()编译屏障进行。我们可以在代码中设置 barrier ()屏障,
个屏障可以阻挡编译器的优化
.....
int a=0;
int b=0;
b = a + 1;
barrier();
a = 1;
.....

运行

而执行乱序则是处理器运行时的行为,后发射的指令还是可能先执行完,这是处理器的“乱序执行(Out-of-Order Execution)”策略。高级的CPU可以根据自己缓存的组织特性,将访存指令重新排序执行。连续地址的访问可能会先执行,因为这样缓存命中率高。有的还允许访存的非阻塞,即如果前面一条访存指令因为缓存不命中,造成长延时的存储访问时,后面的访存指令可以先执行,以便从缓存中取数。因此,即使是从汇编上看顺序正确的指令,其执行的顺序也是不可预知的

这一乱序对于单核的程序执行是不可见 的,因为单个CPU在碰到依赖点(后面的指令依赖于前面指令的执行结果)的时候会等待。但是这个依赖点等待的过程,在 SMP 处理器里面对于其他核是不可见的
cpu0
.....
int a = 0;
int b = 0;
if(a == 1){
    printf("b = %d\n",b);
}
.....

cpu1
.....
b = 88;
a = 1;
.....

cpu1即使编译后没乱序,但是执行的时候因为乱序执行策略:a=1,也可能先于b=88执行,那么cpu0得出的b就为0;

对于互斥操作来说,a就相当于一个互斥标记,b就是一个临界区的内存行为;我们如何保证在cpu1中,a=1后,b=88的内存操作一定执行了?处理器为了解决多核间一个核的内存行为对另外一个核可见的问题,引入了一些内存屏障的指令

屏障指令

DMB(数据内存屏障):在DMB之后的显式内存访问执行前,保证所有在DMB指令之前的内存访问完成;内存访问完了就可以继续往下走了

DSB (数据同步屏障):等待所有在 DSB 指令之前的指令完成(位于此指令前的所有显式内存访问均完成,位于此指令前的所有缓存、跳转预测和TLB 维护操作全部完成);也就是要等所有指令执行完了才往下走
ISB (指令同步屏障): Flush 流水线,使得所有 ISB之后执行的指令都是从缓存或内存中获得的,ISB不仅做了DSB所做的事情,还将 流水线清空
A Data Synchronization Barrier (DSB) completes when all instructions before this instruction complete.

A Data Memory Barrier (DMB) ensures that all explicit memory accesses before the DMB instruction complete before any explicit memory accesses after the DMB instruction start.

An Instruction Synchronization Barrier (ISB) flushes the pipeline in the processor, so that all instructions following the ISB are fetched from cache or memory, after the ISB has been completed.

基于内存屏障指令的互斥逻辑

如果两个并发实体同时调用ldrex+strex,可以实现原子地更新内存数据,来保证修改单个整型数据的互斥,可用于互斥标记;也得用DMB保证这个互斥量结构里面的成员被更新
1LOCKED EQU 1
2UNLOCKED EQU 0
3lock_mutex
4 ; 互斥量是否锁定?
5 LDREX r1, [r0] ; 检查是否锁定
6 CMP r1, #LOCKED ; 和"locked"比较
7 WFEEQ ; 互斥量已经锁定,进入休眠
8 BEQ lock_mutex ; 被唤醒,重新检查互斥量是否锁定
9 ; 尝试锁定互斥量
10 MOV r1, #LOCKED
11 STREX r2, r1, [r0] ; 尝试锁定
12 CMP r2, #0x0 ; 检查STR指令是否完成
13 BNE lock_mutex ; 如果失败,重试
14 DMB ; 进入被保护的资源前需要隔离,保证互斥量已经被更新
15 BX lr
16
17unlock_mutex
18 DMB ; 保证资源的访问已经结束
19 MOV r1, #UNLOCKED ; 向锁定域写"unlocked"
20 STR r1, [r0]
21
22 DSB ; 保证在CPU唤醒前完成互斥量状态更新
23 SEV ; 像其他CPU发送事件,唤醒任何等待事件的CPU
24
25 BX lr

内核相关屏障API

每个 CPU 都是乱序执行,但是单个 CPU 在碰到依赖点的时候会等待,所以执行乱序对单核不一定可见。但是,当程序在访问外设的寄存器时,这些寄存器的访问顺序在CPU 的逻辑上构不成依赖关系,但是从外设的逻辑角度来讲,可能需要固定的寄存器读写顺序,这个时候,也需要使用CPU 的内存屏障指令在Linux内核中,定义了读写屏障mb()、读屏障rmb()、写屏障wmb()、以及作用于寄存器读 写的__iormb()、__iowmb()这样的屏障API

详情参考:《Linux设备驱动开发详解:基于最新的Linux4.0内核》

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值