内核的并发和竞态(信号量completion自旋锁)

Posted 正在起飞的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核的并发和竞态(信号量completion自旋锁)相关的知识,希望对你有一定的参考价值。

1、并发和并行

(1)并行:在同一时刻有多个线程一起运行;
(2)并发:在同一时刻只有一个线程在运行,但是在一个时间段内有多个线程运行;
总结:并发是宏观的并行。因为CPU运行特别快,虽然CPU不断在切换运行的线程,但是对于人来说,根本感知不到CPU的切换过程,就好像荧光灯在不停的闪烁,
但是频率只要够快,人们就感觉不到在闪烁。

2、并发产生的原因

(1)正在运行的多个用户进程以无法预知的方式访问驱动程序代码
(2)外部设备的中断异步的发生,导致正在运行的进程或者驱动代码被中断
(3)linux的软件抽象(如timer, tasklet, workqueue)也在异步运行着
(4)现在SMP的处理器架构,导致驱动程序可能会在不同的CPU上运行
(5)可抢占的内核调度算法,可能导致驱动代码随时被抢占

3、内核的竞态

内核的竞态和进程/线程的同步问题是一样的,都是对共享资源的访问不一致导致的。A线程和B线程同时去操作同一共享资源时,由于两个线程互相都感知不到对方在
操作共享资源,当两个线程同时去修改共享资源时,时间上后修改的就会覆盖掉前修改的,这就导致了错误。

4、解决竞态的方法

4.1、乐观锁和悲观锁

悲观锁:认为多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁,在同一时间只允许一个线程访问资源,从源头解决竞争;
下面介绍的信号量、comption、自旋锁都是属于悲观锁;
乐观锁:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

4.2、信号量

//创建一个信号量,初始值为val
void sema_init(struct semaphore *sem, int val)

//创建名字为name的信号量被初始化为1:初始状态是没有锁定的;
DECLARE_MUTEX(name)

//创建名字为name的信号量被初始化为0:初始状态是锁定的
DECLARE_MUTEX_LOCKED(name)

//P操作,会一直阻塞
void down(struct semaphore *sem);

//P操作,在等待过程中可以被打断,打断后函数返回非零值,调用者不会拥有该信号量
void down_interruptible(struct semaphore *sem);

//P操作,不能拥有信号量就马上返回并返回非零值
void down_trylock(struct semaphore *sem);

//V操作
void up(struct semaphore *sem);

(1)信号量本质就是一个整数值,用来管理资源。比如用信号量来管理打印机资源:设备上总共有三台打印机,于是信号量初始值为3,每一次申请打印机资源后,信号量就减一(P操作)。
当信号量减到0(或者负数)时则代表当前没有可用的打印机,这个时候申请打印机资源的线程就会阻塞,等待之前的线程释放打印机资源将信号量加一(V操作),当信号量大于0时则会去唤醒
最先申请打印机资源的线程。
(2)当信号量初始值为1时,代表只有一个共享资源,这个资源在同一时刻只能被一个线程享有,于是达到了互斥访问的效果;

4.3、读取者/写入者信号量:rw_semaphore

4.3.1、为什么要有读取者/写入者信号量?

(1)上面将信号量初始化为1,其实就是一把互斥锁,保证了在同一时刻只有一个线程能访问共享资源,虽然保证了访问共享资源时不会冲突,但是这样共享资源的利用率又很低;
(2)访问共享资源的线程如果都是去读,那么同时访问是没有冲突的,只有当线程去尝试修改共享资源时才可能冲突;
(3)于是读写信号量的功能:在同一时刻,允许多个线程去读,或者允许一个线程去写;读和写操作是互斥的,但是可以有多个线程同时读,提高共享资源的利用率;

4.3.2、读取者/写入者信号量的优缺点

(1)优点:当共享资源被读取的时候占多数时,因为允许多个线程同时读,所以共享资源利用率高;
(2)缺点:当写操作占用时间长,并且不停有线程去上写锁,很可能造成要读取共享资源的线程“饿死”;但是如果提高写锁的优先级,如果不停有线程去上写锁,也可能
造成读线程“饿死”;所以用读写锁,要注意策略,不要造成线程“饿死”。

4.3.3相关的变量类型和函数

struct rw_semaphore 
	__s32			activity;
	spinlock_t		wait_lock;
	struct list_head	wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
;

//初始化
void init_rwsem(truct rw_semaphore *sem);

//上读锁,会阻塞
void down_read(struct rw_semaphore * sem);	

//上读锁,不会阻塞,马上返回
int down_read_trylock(struct rw_semaphore *sem);

//读锁的解锁
void up_read(struct rw_semaphore * sem);

//上写锁,会阻塞
void down_write(struct rw_semaphore * sem);	

//上写锁,不会阻塞,马上返回
int down_write_trylock(struct rw_semaphore *sem);

//写锁的解锁
void up_write(struct rw_semaphore * sem);

4.4、completion

4.4.1、completion介绍

(1)在内核中存在A线程的执行依赖B线程的某些操作,也就是线程同步,A线程的某些操作必须等待B线程的某些操作完成才能执行;
(2)comption是一种轻量级的机制,它允许一个线程告诉另一个线程某个工作已经完成;
补充:普通的信号量也可以完成completion的功能,但是completion的开销更小;

4.4.2、comption相关函数

//初始化一个变量叫name的struct completion变量
#define DECLARE_COMPLETION(name) struct completion work = COMPLETION_INITIALIZER(work)

//初始化struct completion变量
void init_completion(struct completion *c)

//等待completion完成
void wait_for_completion(struct completion *c);

//唤醒一个等待completion的线程
extern void complete(struct completion *c);

//唤醒所有等待completion的线程
extern void complete_all(struct completion *c);

4.5、自旋锁

4.5.1、信号量和自旋锁的比较

(1)共同点:自旋锁和非自旋锁(比如:信号量)在功能上是一样的,都是互斥锁,都是解决内核的竞态,确保共享资源的访问不冲突;
(2)不同点:非自旋锁上锁失败线程则会阻塞,放弃CPU的使用,CPU切换去执行其他的线程;自旋锁上锁失败不会让渡出CPU,会不断的循环尝试获取锁,知道成功获取到锁;
参考博客:什么是自旋锁?自旋的好处和后果是什么呢?;

4.5.2、自旋锁的好处

(1)CPU阻塞和唤醒线程是需要开销的;
(2)自旋锁在获取不到锁时不释放CPU,不断尝试获取锁,也是有开销的,自旋锁的开销和锁住的时间是正相关的;
总结:当共享资源被锁住的时间很短时,自旋锁的开销小,适合用自旋锁;当共享资源锁住的时间长时,适合用非自旋锁;

5、除了锁之外的同步办法

5.1、免锁算法

免锁算法就是从源头解决,内核竞态不少情况是多个线程要去访问同一资源,如果我们增加资源的数量,让线程不必去争抢资源,也就
不会发生竞态,也没有必要使用上述的锁机制;比如:增加循环缓冲区;

5.2、原子变量atomic_t

typedef struct 
	int counter;
 atomic_t;

有时候共享资源就是一个整数值,完整的锁机制对一个简单的整数来讲显得有些浪费,于是内核提供了一种原子的整数类型atomic_t。
一个atomic_t变量在所有内核支持的架构上保存一个int值,但是不能使用完整的整数范围,在atomic_t变量中不能记录大于24位的整数。
补充:操作atomic_t类型变量要使用专门的函数。

5.3、其他方法

(1)位操作:以原子的形式操作单个位。上面的atomic_t是针对整数值的;
(2)seqlock:适合会被频繁读,但是很少写的共享资源;
(3)读取-复制-更新:针对频繁读,但是很少写的共享资源,做了优化处理,驱动很少用;

以上是关于内核的并发和竞态(信号量completion自旋锁)的主要内容,如果未能解决你的问题,请参考以下文章

Linux 设备驱动的并发控

Linux 设备驱动的并发控

漫画|Linux 并发竞态互斥锁自旋锁信号量都是什么鬼?

Linux 设备驱动的并发控制 学习笔记

Linux 内核同步之自旋锁与信号量的异同

15 同步于互斥 并发竞态和编译乱序执行乱序