linux同步论

 

1           同步概要

在计算机中或生活中,经常遇到这样一种情况:多个访问者对同一个资源进行访问、操作,这里面涉及到的一个关键内容就是如何同步,即怎么保证该次访问的内容是所期待的。简单的例子,甲希望访问一个资源,该资源更新日期为昨天,但乙却在甲访问前的插了一脚,对资源做了修改,那么甲访问到的就不是所期待的内容了,后续的操作可能就会出现问题。这个例子可能不太合适,你可能会说,那甲应该每次访问的都期待最新的不就好了吗?所以说这个例子不太合适,有没有更好的?我可以理解的?

在这个简单的例子中,同步涉及到的几个关键点是:

-           共享资源:大家所争夺的资源;

-           并发访问:大家同时对该资源进行访问操作,这是多CPU同时对这一个资源进行操作;

-           抢占访问:我期待没人在我之前对资源进行了修改,但我却无法保证,多任务的情况下就是这样。

本文主要总结一些同步方法,其中包括linux内核同步机制和一些思考。

2           linux内核中的同步方法

2.1       介绍

理论上,共享资源称之为临界资源,该资源位于临界区,必须保证对临界区的操作是原子的,即一个时刻只能有一个用户在临界区,对临界资源进行访问操作。当无奈的发生了这种不可能出现的情况时,我们称之为竞争条件。内核同步就是为了解决并发和竞争条件。

2.2       同步机制

这是我能想到的第一个方法就是锁:我把资源锁起来,你们谁都别想用,除非我解锁。请记住,锁的不是代码,是我们宝贵的临界区资源。锁的实现采用了原子操作,这点保证了加锁和开锁的安全,也就是说,在加锁的瞬间,你是没机会先一步溜进去的。因为世界是多变的,造成内核中的锁也有很多种,每种锁都有自己的特点,虽然是句废话,但不得不说,不弄清楚这些个特点,很有可能造成无法挽回后果死锁。

2.2.1    自旋

锁如其名,当请求这把锁的时候,发现已经有人抢先一步,那后来者就自旋在那,相当于忙等,不干活,这太浪费了,正因为这,需要自旋锁:

-           持有时间短;

-           不能睡眠;

特别注意:linux内核的自旋锁是非递归的,也就是持有者不能再次请求该锁,其他系统实现我没看过,也就不说了。还有一点,在单CPU机器上,自旋锁的功能就是抢占的开关而已,如果你连抢占都没有的话,那么自旋锁调用就真正为空了。自旋锁不会导致睡眠,因而中断里面也可以调用,不过调用之前,需要关闭中断,以防死锁。

2.2.2    睡眠锁(信号量)

前面的自旋锁有个问题,在请求失败的时候,会偷懒,不干活,CPU时间的浪费是真的浪费。为了解决这,内核设计者把请求信号量失败的任务放到了一个等待队列中,让该任务睡眠,让CPU该干什么干什么去,总比闲着好。待到信号量占用者释放了该锁,内核再唤醒在队列中的任务,使其获得信号量。虽然是避免了时间的浪费,可是这里却增大了内核开销,毕竟需要为他们准备队列,还要唤醒他们。因而,该锁具有如下特点:

-           锁可能被长时间持有;

-           只能在进程上下文中使用,中断上下文可不能睡眠;

-           持有者可以睡眠,因为请求者都在睡眠,不关心你是醒还睡;

-           持有自旋锁就不要再去请求睡眠锁;

上面的特点可以和自旋锁对比,信号量还有另外一个特点,就是:

-           信号量可以被多于一人持有,而不像自旋锁只能一个人持有;

当信号量个数为1时,称之为互斥信号量(mutex)。

2.2.3    读写锁

锁如其名,考虑到我们对数据的访问,无外乎读和写,当大家都是读的时候,仅允许一个人读,那太死板了。内核因此动了动脑筋,让请求读的人都能读,当:

-           没有写者;

对于写请求,需要满足:

-           没有读者和其他写者;

这读写锁针对应自旋锁和信号量,分别称为:

-           读写自旋锁;

-           读写信号量,都是互斥信号量;

特别注意:读写自旋锁情况下,读者升级为写者是会出事的,很容易推理。当今内核已经不鼓励使用该锁了,大家可以用RCU来代替,也就说到此了。

疑问:读写信号量时,能升级吗?

2.2.4    完成变量

思想如同信号量,实现上有所区别。

2.2.5    大内核锁(BKL

BKL是有害的,我们还需要继续吗?

2.2.6    顺序锁(seq

轻量级读写锁,对一个数据加入计数器,写时加1,读者可以通过在所读数据前后,比较该计数值,确定是否有写者,来确定数据的使用情况。

2.2.7    禁止抢占

在单CPU机器上,可抢占的代码,可以模拟多CPU的并发情形,也就是几乎在同时(伪并发),多个任务抢占一个资源,那么我们可以选择关闭抢占,来阻止这种事情的发生。前面提到的,自旋锁所在区域也是关抢占的。但单独的禁止抢占更适用于资源是每CPU变量的情形。

2.2.8    屏障

防止编译器的不适当优化,通过使用屏障保证指令的顺序。

2.2.9    原子变量和原子操作

假设资源为原子变量,对其的任何操作都是原子的,也就是说他是天然就能保证同步的资源。原子操作保证了操作的原子性。

2.2.10CPU变量

外加合适的原子操作就能保证同步。

2.2.11读写-复制-更新(RCU

这是一种内核提倡的机制,很特别:              

-           RCU只保护动态分配并通过指针引用的数据结构;

-           RCU区域,不能睡眠;

-           写者,复制资源,修改副本,更新资源;更新前确保资源无人访问,即没有读者;

-           读者,任意时刻都能获得资源,且是当前时候的最新版本,读者会增加资源的引用计数,资源引用计数为0时,说明没有读者,资源可以被更新;

写者通过call_rcu将更新操作安装,待到资源没有读者时,安装函数被调用。

疑问:写者应该唯一吗?

3           参考

[1]. Linux内核设计与实现

[2]. ULK

[3]. LDD

 

PS:哪位觉得不对的,我胡说的,请告诉我,以免我再去误人,谢谢~。~

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值