Linux内核的并发与竞态、信号量、互斥锁、自旋锁

/************************************************************************************html

*本文为我的学习记录,若有错误,欢迎指正。并发

*本文参考资料: 
学习

*        https://blog.csdn.net/liu_sheng_1991/article/details/52291427测试

*        https://blog.csdn.net/smallfish_love/article/details/50753932
spa

*        http://www.javashuo.com/article/p-mqrlziqc-bm.html.net

*        https://www.cnblogs.com/lonelyxmas/p/4287338.html?utm_source=debugrun&utm_medium=referraldebug

************************************************************************************/code

1. 并发与竞态

(1)并发:并发是指多个执行单元同时、并行被执行。htm

(2)竞态:并发的执行单元对共享资源(硬件资源和软件上的全局变量,静态变量等)的访问容易发生竞态。blog

(3)竞态发生的状况:

1)对称多处理器(SMP)的多个CPU:SMP是一种紧耦合、共享存储的系统模型,它的特色是多个CPU使用共同的系统总线,所以能够访问共同的外设和存储器。

2)单CPU内进程与抢占它的进程:Linux内核支持内核抢占,一个进程在内核执行的时候可能被另外一个优先级高的进程打断,进程与抢占它的进程访问共享资源的状况相似于SMP的多个CPU。

3)中断(硬中断、软中断)与进程之间:中断能够打断正在执行的进程,若是中断处理程序访问进程正在访问的资源,竞态也会发生。中断也可能被新的更高优先级的中断打断,所以,多个中断之间也可能引发并发而致使竞态发生。

2. 竞态的解决方法

解决竞态的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其余的执行单元是被禁止访问的。

访问共享资源的代码区域被称为临界区,临界区须要被以某种互斥机制加以保护。中断屏蔽、原子操做、自旋锁和信号量等是Linux设备驱动中可采用的互斥途径。

2.1 中断屏蔽

(1)基本概念

在单个CPU范围内避免竞态的一种简单省事的方法是在进入临界区以前屏蔽系统的中断。CPU通常具有屏蔽和打开中断的能力,这样能够保证正在执行的内核路径不被中断处理程序抢占,防止竞态条件的发生。具体而言,中断屏蔽使得中断与进程之间的并发再也不发生,并且,因为Linux内核的进程调度等操做依赖中断来实现,内核抢占进程之间的并发得以免。可是不能长时间屏蔽中断,由于在中断屏蔽期间,全部的中断得不处处理,有可能形成数据丢失和系统崩溃的可能。这就要求在中断屏蔽以后,当前的内核执行路径应当尽快的执行完临界区的代码。

(2)操做方法

Linux内核中提供如下API来实现中断屏蔽。

local_irq_disable();  //屏蔽中断
local_irq_enable();   //打开中断
local_irq_save(flags);//禁止中断并保存当前cpu的中断位信息

2.2 原子操做

原子操做是指在执行过程当中不会被别的代码途径所中断的操做,分为整形原子操做和位原子操做。

2.3 信号量(进程级)

(1)基本概念

本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取情况。它是对临界区保护的一种经常使用方法,他的使用方法和自旋锁差很少。与自旋锁相同只有获得信号量的进程才能执行临界区的代码。可是与自旋锁不一样的是,当获取不到信号量时,进程不会原地打转而是进入休眠等待状态。

(2)操做方法

Linux内核中提供如下API来操做信号量。

//1)定义信号量
Struct semaphore sem; 

//2)初始化信号量
void sema_init(struct semaphore *sem, int val); //初始化sem为val,固然还有系统定义的其余宏初始化,这里不列举 

//3)得到信号量
void down(struct semaphore *sem);               //得到信号量sem,其会致使睡眠,并不能被信号打断 
int down_interruptible(struct semaphore *sem);  //进入睡眠能够被信号打断 
int down_trylock(struct semaphore *sem);        //不会睡眠 

//4)释放信号量
void up(struct semaphore *sem);                 //释放信号量,唤醒等待进程 

2.4 互斥锁(进程级)

(1)基本概念

互斥锁是一种特殊的信号量。

(2)操做方法

Linux内核中提供如下API来操做互斥锁。

//1)定义互斥锁lock
mutex_init(struct mutex* lock);

//2)获取互斥锁
 mutex_lock(struct mutex *lock);

//3)释放互斥锁
 mutex_unlock(struct mutex *lock);

2.5 自旋锁

(1)基本概念

自旋锁是一种典型的对临界资源进行互斥访问的手段,其名称来源于他的工做方式。为了得到一个自旋锁,在某CPU上运行的代码须要先执行一个原子操做,该操做测试并设置某个内存变量,因为是原子操做,因此在该操做完成以前其余执行单元不可能访问这个内存变量。若是测试代表锁已经空闲,则程序得到这个自旋锁并继续执行;若是测试代表自旋锁被占用,程序将在一个小的循环中重复这个“测试并设置”的操做,即所谓的“自旋”。当自旋锁的持有者经过重置该变量释放这个自旋锁以后,某个“等待的测试并设置”操做向其调用者报告锁已释放。

(2)操做方法

Linux内核中提供如下API来操做自旋锁。

//1)定义自旋锁
spinlock_t lock;
//2)初始化自旋锁 spin_lock_init(lock);
//3)获取自旋锁 spin_lock(lock); //得到自旋锁lock spin_trylock(lock);//尝试获取lock若是不能得到锁,返回假值,不在原地打转
//4)释放自旋锁 spin_unlock(lock); //释放自旋锁

P.S.:信号量与自旋锁的区别:

自旋锁不会引发调用者睡眠,若是自旋锁已经被别的执行单元保持,调用者就一直循环查看是否该自旋锁的保持者已经释放了锁,"自旋"就是"在原地打转"。而信号量则引发调用者睡眠,它把进程从运行队列上拖出去,除非得到锁。

鉴于自旋锁与信号量的上述特色,通常而言,自旋锁适合于保持时间很是短的状况,它能够在任何上下文使用;信号量适合于保持时间较长的状况,会只能在进程上下文使用。若是被保护的共享资源只在进程上下文访问,则能够以信号量来保护该共享资源,若是对共享资源的访问时间很是短,自旋锁也是好的选择。可是,若是被保护的共享资源须要在中断上下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

相关文章
相关标签/搜索