linux竞争

404 阅读5分钟

一、并发与竞争

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. 简介

原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性。

  • 原理

1.当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。
2.对于自旋锁而言,如果自旋锁正在被线程A持有,线程B想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态,线程B不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待锁可用。
3.从这里我们可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。

2.API

/* 定义 */  
spinlock_t lock;
/* API */
DEFINE_SPINLOCK(spinlock_t lock)     /* 定义并初始化一个自选变量 */
int spin_lock_init(spinlock_t *lock) /* 初始化自旋锁*/
void spin_lock(spinlock_t *lock)     /* 获取指定的自旋锁,也叫做加锁 */
void spin_unlock(spinlock_t *lock)   /* 释放指定的自旋 */
int spin_trylock(spinlock_t *lock)   /* 尝试获取指定的自旋锁,如果没有获取到就返回0 */
int spin_is_locked(spinlock_t *lock) /*检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0 */


/* 注意点-死锁现象 */
/* 1. 被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API 函数 */
/* 2.关闭本地中断 */
/* 关闭中断自旋锁API 推荐后两个*/
void spin_lock_irq(spinlock_t *lock) /* 禁止本地中断,并获取自旋锁。*/
void spin_unlock_irq(spinlock_t *lock) /* 激活本地中断,并释放自旋锁。*/
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags) /* 保存中断状态,禁止本地中断,并获取自旋锁。*/
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) /* 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。*/


/* 示例代码 */
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) /* 释放锁 */
}

四、信号量