Linux 并发控制

灵魂拷问:

什么叫并发?
并发会导致什么不良后果?
并发的场景都有哪些?
怎么解决并发带来的问题?

并发和竟态

并发:

多个执行单元同时并行被执行,访问同一块共享资源(硬件资源,软件资源(静态变量,全局变量等)),产生竞态。

后果:

结果与预期不一致

场景:

  1. 单核
    进程时间片耗完,轮到其他进程执行;
    进程被高优先级进程抢占;
    进程被中断抢占;
    中断被高优先级中断抢占;

  2. SMP多核CPU
    每个CPU内部情况如单核,另外多核CPU核间如下情景也会发生并发导致的竟态:
    核间进程之间;
    核间进程与中断之间;
    核间中断与中断之间。

思考:
竟态的产生的根本原因是计算机中CPU、存储、外设的拓扑架构引起的。不同时刻CPU上运行的执行单元(进程或中断)通过总线访问外设或者内存(这两个都是共享资源)带来了竟态。

解决办法:

核心:保证访问共享资源的临界区(代码区域)在同一时刻的互斥访问
方式:中断屏蔽,原子操作,自旋锁,信号量,互斥体。

Tips 总结:

  1. (多核架构)驱动中使用local_irq_disable()/local_irq_enalbe()通常意味着一个bug

1. 中断屏蔽

1). 用途是什么?
禁止本地CPU的中断,从而防止中断导致的并发竟态;另外,由于Linux的进程调度也是基于中断实现,所以屏蔽中断也可以防止进程抢占导致的并发竟态产生。

2). 实现原理是什么?
屏蔽 ARM CPSR的I位。

3). 怎么用?

local_irq_disable()  //禁止中断
---临界区---
local_irq_enable()  //开中断

另外,还有 local_irq_save(flags)/local_irq_restore(flags),除了有禁止中断的功能外,还能保存和恢复CPSR;
禁止中断底半部 local_bh_disable()/local_bh_enable()。

4). 有什么要注意的

  • 中断屏蔽只能禁止本地CPU的中断,适用于单核架构;如果是SMP多核架构,核间中断导致的并发竟态同样无法避免。鉴于现在的产品几乎都是多核架构,所以单独使用中断屏蔽避免竟态并不合适,需要配合spinlock使用;
  • 中断屏蔽会导致本地CPU无法响应中断,所以要求临界区代码必须尽快执行完毕,否则中断长时间得不到响应,会导致数据丢失甚至系统崩溃等严重问题;

2. 原子操作

1). 用途是什么?
整型变量进行排他性操作。

2). 实现原理是什么?
基于底层CPU的汇编指令使用嵌入式C代码实现。对于ARM架构,指令是 LDREX和STREX。

#if __LINUX_ARM_ARCH__ >= 6
static inline void atomic_add(int i, atomic_t *v)
{
    unsigned long tmp;
    int result;

    prefetchw(&v->counter);
    __asm__ __volatile__("@ atomic_add\n"
"1:    ldrex    %0, [%3]\n"
"    add    %0, %0, %4\n" 
"    strex    %1, %0, [%3]\n"
"    teq    %1, #0\n"
"    bne    1b"
    : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)
    : "r" (&v->counter), "Ir" (i)
    : "cc");
}
...
#endif

3). 怎么使用?
首先明确一点,原子操作的作用是保证对变量的 read-modify-store操作一气呵成,不被打断。那么基于此技术原理,结合我们的代码业务和运用场景进行思考和运用。

编写设备驱动程序,对共享资源(变量)设置一个共享变量。该共享变量可以是引用计数,比如事件发生计数;也可以是设备状态记录标志等。
如果该共享变量可能会被被多个线程访问(增加/减少计数,改变标志位状态),那么就可以将它设置为atomic_t类型,用原子操作的API来操作它。

举例:

  • 内核kobject的引用计数kref_get()就是对atomic_inc_return()的封装引用。
  • 使用原子变量,使得设备或资源同一时刻只能被一个进程打开。

具体API

  • static inline void atomic_add(int i, atomic_t *v) 给一个原子变量v增加i
  • static inline int atomic_add_return(int i, atomic_t *v) 同上,只不过将变量v的最新值返回
  • static inline void atomic_sub(int i, atomic_t *v) 给一个原子变量v减去i
  • static inline int atomic_sub_return(int i, atomic_t *v) 同上,只不过将变量v的最新值返回
  • static inline int atomic_cmpxchg(atomic_t *ptr, int old, int new) 比较old和原子变量ptr中的值,如果相等,那么就把new值赋给原子变量。返回旧的原子变量ptr中的值
  • atomic_read 获取原子变量的值
  • atomic_set 设定原子变量的值
  • atomic_inc(v) 原子变量的值加一
  • atomic_inc_return(v) 同上,只不过将变量v的最新值返回
  • atomic_dec(v) 原子变量的值减去一
  • atomic_dec_return(v) 同上,只不过将变量v的最新值返回
  • atomic_sub_and_test(i, v) 给一个原子变量v减去i,并判断变量v的最新值是否等于0
  • atomic_add_negative(i,v) 给一个原子变量v增加i,并判断变量v的最新值是否是负数
  • static inline int atomic_add_unless(atomic_t *v, int a, int u) 只要原子变量v不等于u,那么就执行原子变量v加a的操作。如果v不等于u,返回非0值,否则返回0值

参考:
《Linux设备驱动开发详解》 第7章
http://www.wowotech.net/sort/kernel_synchronization

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值