Linux的并发与竞争

619 阅读4分钟

一、并发与竞争

1.并发

Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可 能会相互覆盖这段内存中的数据,造成内存数据混乱。

  1. 多线程并发访问, Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
  2. 抢占式并发访问,从 2.6 版本内核开始, Linux 内核支持抢占,也就是说调度程序可以 在任意时刻抢占正在运行的线程,从而运行其他的线程。
  3. 中断程序并发访问。
  4. SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并 发访问。

2.竞争

  1. 并发访问带来的问题就是竞争,对于共享数据段必须保证一次只有一个线程访问。
  2. 如果多个线程同时操作临界区就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。很多 Linux驱动初学者往往不注意这一点,在驱动程序中埋下了隐患,这类问题往往又很不容易查找,导致驱动调试难度加大、费时费力。

二、原子操作

1.简介

原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。

  • 原理
/* 假如现在要对无符号整形变量a赋值,值为3,对于C语言来讲很简单: */
    a = 3
/* 但是 C 语言要先编译为成汇编指令:*/
    ldr r0, =0X30000000 /* 变量 a 地址 */
    ldr r1, = 3         /* 要写入的值 */
    str r1, [r0]        /* 将 3 写入到 a 变量中 */
/* 当多个线程同时操作a则会产生数据混乱 */
/* 要解决这个问题就要保证示例代码中的三行汇编指令作为一个整体运行 */

2. API

/* Linux 内核定义了叫做 atomic_t 的结构体来完成整形数据的原子操作 */
typedef struct {
    int counter;
} atomic_t;
/* 定义 */
atomic_t a;
/* 整形操作 */
ATOMIC_INIT(int i) //定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v) //读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) //向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) //给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) //从 v 减去 i 值。
void atomic_inc(atomic_t *v) //给v加1也就是自增。
void atomic_dec(atomic_t *v) //从v减1也就是自减
int atomic_dec_return(atomic_t *v) //从v减1,并且返回v的值。
int atomic_inc_return(atomic_t *v) //给v加1,并且返回v的值。
int atomic_sub_and_test(int i, atomic_t *v) //从v减i,如果结果为0就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) //从v减1,如果结果为0就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) //给v加1,如果结果为0就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) //给v加i,如果结果为负就返回真,否则返回假
/* 64位的改变 */
typedef struct {
    long long counter;
} atomic64_t;
/* 函数 */
atomic64_xxx
/* 原子位操作 */

三、自旋锁

1. 简介

  • 与原子操作的区别

原子操作只能对整形变量或者位进行保护,自旋锁则可以对结构体变量进行保护。

  • 机制

当一个线程要访问某个共享资源的时候首先要获取相应的锁,但该锁只能被一个线程锁持有,若其他线程无法获取到该锁就会原地打转等待该锁可以被获取。(类似于阻塞时间为无穷的信号量)

  • 缺陷

自旋锁一直处于自旋状态,这样会浪费处理器时间,所有自旋锁的持有时间不能太长,要及时释放掉。

  • 注意事项

临界区内不要有阻塞(sleep)和中断

2.API函数

1. 定义锁
spinlock_t lock;
2. 初始化锁
int spin_lock_init(spinlock_t *lock)
3. 获取锁
void spin_lock(spinlock_t *lock) 
void spin_lock_irq(spinlock_t *lock) //禁止本地中断
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) //保存中断状态再禁止本地中断
4. 释放锁
void spin_unlock(spinlock_t *lock) 
void spin_unlock_irq(spinlock_t *lock) //激活本地中断
void spin_unlock_irqrestore(spinlock_t *lock,unsigned long flags) //恢复中断状态再激活本地中断
5. 下半部(BH)
void spin_lock_bh(spinlock_t *lock) //关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock) //打开下半部,并释放自旋锁。
6. 其他
int spin_trylock(spinlock_t *lock) //无阻塞取锁
int spin_is_locked(spinlock_t *lock) //检查自旋锁是否已经被获取,没有返回非0,否则返回0
7.API使用模板
例
DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */

/* 线程 A */
void functionA (){
    unsigned long flags; /* 中断状态 */
    spin_lock_irqsave(&lock, flags) /* 获取锁 */
    /* 临界区 */
    spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}

/* 中断服务函数 */
void irq() {
    spin_lock(&lock) /* 获取锁 */
    /* 临界区 */
    spin_unlock(&lock) /* 释放锁 */
}