Linux内核设计与实现(9)第九章:内核同步介绍
1. 临界区,竞争条件,同步
临界区:就是访问和操作共享数据的代码段。
竞争条件: 多个执行线程处于同一个临界区。
同步: 安排进程/线程执行的先后顺序就是同步
同步目的:避免并发和防止竞争条件
1.1 内核中造成并发的原因:
中断:任何时刻都可能发生
软中断和tasklet:内核能在任何时刻唤醒或者调度软中断和tasklet
内核抢占:内核具有抢占性
睡眠及用户空间的同步:会唤醒调度程序,从而导致另一个新的用户进程执行
对称多处理:多个cpu同时执行代码
2. 锁
锁:实现了对临界资源的互斥访问
锁也是采用原子操作实现的,因此锁操作不存在竞争
2.1 锁的种类:互斥锁,读写锁,自旋锁,信号量
锁的种类:互斥锁,读写锁,自旋锁,递归锁
互斥锁:
首选互斥锁,互斥锁能够满足各类功能性要求。
优点: 1. 简单效率高;
2. 线程会睡眠,所以等待的过程不会占用CPU时间。
所以信号量适用于等待时间较长的临界区。
缺点:开销大/会膨胀
自旋锁:
执行时间短/临界区小,选择自旋锁
特点:临界区要小;不允许睡眠;可以再中断上下文中执行
优缺点:不睡眠,线程会忙等待,直到它拿到锁
读写锁:
如果能区分出读写操作,读写锁就是第一选
特点:写独占,读共享;写锁优先级高
信号量
如果是流程上,则选择信号量。
信号量不一定是锁定某一个资源,而是流程上的概念
互斥量则纯粹是“锁住某一资源”的概念;独占情况下使用互斥量
特点:会有上下文切换/睡眠
线程会睡眠,所以等待的过程不会占用CPU时间。
所以信号量适用于等待时间较长的临界区。
之前锁的相关文章
多线程(4)什么是锁?锁机制,死锁等说明
https://blog.csdn.net/lqy971966/article/details/104527787
Linux 锁机制(1)之互斥量 mutex
https://blog.csdn.net/lqy971966/article/details/119108670
Linux 锁机制(2)之读写锁 rwlock_t
https://blog.csdn.net/lqy971966/article/details/103541567
Linux 锁机制(3)之自旋锁
https://blog.csdn.net/lqy971966/article/details/103541614
Linux 锁机制(4)之信号量
https://blog.csdn.net/lqy971966/article/details/119326689
Linux 锁机制(5)之互斥,读写,信号量和自旋锁总结比较
https://blog.csdn.net/lqy971966/article/details/119113679
2.2 死锁
2.3.1 死锁定义
死锁就是所有线程都在相互等待释放资源,导致谁也无法继续执行下去。
详细定义:
两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
2.3.2 常见死锁情形
1.两个线程同时加锁
线程1 lock A lock B …… unlock B unlock A
线程2 lock B lock A …… unlock A unlock B
导致死锁……
2.两个线程互相调用
线程1 func1 lock A lock B …… unlock B unlock A
线程2 lock A func1 lock B …… unlock B unlock A
导致死锁……
2.3.3 避免死锁:
1. 按顺序加锁:
如果有多个锁的话,尽量确保每个线程都是按相同的顺序加锁,
按加锁相反的顺序解锁。(即加锁a->b->c,解锁c->b->a)
2. 防止发生饥饿:
即设置一个超时时间,防止一直等待下去。
如:这个代码的执行是否一定会结束?如果A不发生,B一定要等待下去吗?
3. 不要重复请求同一个锁。
设计应力求简单。加锁的方案越复杂就越容易出现死锁。
2.4 什么数据需要加锁
内核全局变量
共享数据
3. 编写内核代码时,时时记着下面这些问题:
这个数据是不是全局的?除了当前线程以外,其他线程能不能访问它?
这个数据会不会在进程上下文或者中断上下文中共享?它是不是要在两个不同的中断处理程序中共享?
进程在访问数据时可不可能被抢占?被调度的新程序会不会访问同一数据?
当前进程会不会睡眠(或者阻塞)在某些资源上,如果是,它会让共享数据处于何种状态?
怎样防止数据失控?
如果这个函数又在另一个处理器上被调度将会发生什么?