[笔记]《Linux内核设计与实现》第九、十章内核同步

目前正在通读《Linux内核设计与实现》一书,本文是对第九章<内核同步介绍>、第十章<内核同步方法>的总结。
1.前置概念:
临界区:也叫临界段,就是访问和操作共享数据的代码段。
竞争条件:race conditions,发生了多个执行线程处于同一个临界区中同时执行的情况,我们就称它是竞争条件。
同步:synchronization,避免并发和防止竞争条件,就称为同步。
伪并发:多个任务不真是同时发生的,而是相互交叉进行。
死锁:所有线程都在相互等待,但它们又不会释放已经占有的锁。
自死锁:一个执行线程试图去获得一个自己已经持有的锁,它将不得不等待锁被释放,但是因为在忙等待,所以自己也永远不会有机会释放这个锁。
递归锁:同一个锁可以被一个执行线程多次请求。递归锁可以用来防止自死锁现象。
争用:指锁的争用,lock contention。是指当锁正在被占用时,有其他线程试图获得该锁。

2.并发执行的原因:
1)中断
2)软中断和tasklet
3)内核抢占:内核具有抢占性,内核中的任务可能会被另一任务抢占。
4)睡眠及与用户空间的同步
5)对称多处理:两个或多个处理器可以同时执行代码

3.加锁的难点:
如何辨别出真正需要共享的数据和相应的临界点

4.给谁加锁:
要给数据而不是代码加锁

5.锁的作用:
是使得程序以串行方式对资源进行访问。

6.加锁方案:
加锁粒度的控制,过粗,会导致锁被高度争用,严重降低系统性能;过细,只会增加复杂度加大开销。
原则:设计初期加锁方案应该力求简单,仅当需要时再进一步细化加锁方案,精髓在于力求简单。

7.锁的类型:
各种锁机制之间的区别主要在于:当锁已经被其他线程持有,因而不可用时的行为表现。
锁是用原子操作实现的,而原子操作不存在竞争。
7.1.原子操作
原子操作确保指令执行期间不被打断,要么全部执行完,要么不执行。
真正的原子操作需要的是--所有中间结果都正确无误。
内核提供了两组原子操作接口:原子整数操作和原子位操作。
原子操作通常是内联函数,往往通过内嵌汇编指令来实现。
能用原子操作时,就尽量不要使用复杂的加锁机制。

7.2.自旋锁
spin lock
内核中最普通的锁,最多只能被一个可执行线程持有。争用时忙等待,即一直进行忙循环,特别浪费处理器时间。
其初衷:在短期间内进行轻量级加锁。
在现在的抢占式内核中,锁的持有时间等价于系统的调度等待时间。
自旋锁的特点:
1)在Linux中,自旋锁不可递归;
2)自旋锁可以使用在中断处理程序中。
读写自旋锁:也叫共享/排斥锁,或者并发/排斥锁。多个读操作可以安全地获得同一个读锁,而写操作为了互斥访问只能等待,只有所有的读者都释放了锁,写操作才能获得锁。

7.3.信号量
由Dijkstra在1968年提出,荷兰人。信号量支持两个原子操作P()和V(),这两个名字来自荷兰语Proberen和Vershogen。前者叫做测试操作(字面意思是探查),后者叫做增加操作。
后来的系统把两种操作分别叫做down()和up()。
down()操作通过对信号量计数减1来请求获得一个信号量。
up()操作用来释放信号量。
是一种睡眠锁,当一个从操作试图获得一个不可用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时处理器能重获自由,去执行其他代码。
7.3.1.信号量的特点:
1)适用于锁会被长时间持有的情况;
2)只能在进程上下文中使用,不能在中断上下文中使用;
7.3.2.计数信号量和二值信号量
信号量可以同时允许任意数量的锁的持有者,这个值称为“使用者数量”(usage count)或者简单地叫数量。
二值信号量:也叫互斥信号量,计数为1,在同一时刻仅允许有一个锁持有者。
计数信号量:counting semaphone,count值为大于1的非0值。
读写信号量:是种互斥信号量,只要没有写者,并发持有读锁的读者数不限。相反,在没有读者时,只有唯一的写者可以获得写锁。

7.4.互斥体
mutex,互斥体,一个简单睡眠锁,指的是任何可以睡眠的强制互斥锁。其行为和互斥信号量类似,但是操作接口更简单,实现也更高效,而且使用限制更强。
它不同于信号量,它仅仅是实现了Dijkstra设计初衷中的最基本的行为。
互斥体的特点:
1)任何时刻中只有一个任务可以持有mutex;
2)给mutex上锁者必须负责给其再解锁;最常用的方式是:在同一个上下文中上锁和解锁。
3)当持有一个mutex时,进程不可以退出。
4)mutex不能在中断或者下半部中使用;
5)mutex只能通过官方API管理
mutex和信号量很相似,内核中两者共存会令人混淆。使用规范是:首选mutex,只有碰到特殊场合(一般是很底层代码)才会需要使用信号量。

7.5.完成变量
completion variable,当一个任务完成任务后,就会使用“完成变量”去唤醒在等待的任务。是一种代替信号量的一个简单的解决办法。

7.6.大内核锁(BKL)
是一种全局自旋锁。使用它主要是为了方便实现从Linux最初的SMP过渡到细粒度加锁机制。
BKL的特点:
1)持有BKL的任务仍然可以睡眠,但不是说睡眠是安全的。
2)BKL是一种递归锁;一个进程可以多次请求一个锁,但是必须调用同样次数的解锁操作。在最后一个解锁操作完成后,锁才会被释放。
3)BKL只可以用在进程上下文中。
4)BKL在被持有时同样会禁止内核抢占。
5)新的用户不允许使用BKL。
多数情况下,BKL更像是保护代码而不是保护数据。

7.7.顺序锁
简称seq锁,是在2.6版本内核中才引入的一种新型锁。用于读写共享数据。
实现这种锁主要依靠一个序列计数器。当有疑义的数据被写入时,会得到一个锁,并且序列值会增加。
只要没有其他写者,写锁总是能够被成功获得。读者不会影响写锁,这与读写自旋锁及信号量一样。不同的是不允许读者让写者饥饿,写优先于读。

7.8.禁止抢占
preempt_disable()/preempt_enable()成对调用,禁止/释放内核抢占,支持嵌套调用

7.9.屏障
barriers,用于确保顺序的指令称为屏障。
程序代码的顺序,在处理器执行阶段,会在取指令和分派对时,把表面上看似无关的指令按自认为最好的顺序排列。
大多数情况下,这样的排序是最佳的。尽管有时候程序员知道什么是最好的顺序。
barrier()方法可以防止编译器跨屏障对载入或存储操作进行优化。
内存屏障指令(如rmb()/wmb()/mb())可以完成编译器屏障的功能,但是编译器屏障要比内存屏障轻量得多。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值