抢占式内核和非抢占式内核
Linux 内核有两个空间,一个是内核空间一个是用户空间,如果一个进程正在内核态执行的时候,允许内核打断他的执行,让另一个进程执行,那么这个内核就是可抢占式内核。
还有一种情况就是,进程执行到内核态的时候,不允许进程切换,就是说,一定要等到进程自己释放CPU使用权,这个内核就是非抢占式内核。
抢占式内核的优点非常明显,就是可以优先让那些优先级大的任务先执行,可以打断低优先级的进程,但是缺点也比较明显,就是内核抢占导致的问题就是要保护全局变量,静态变量,因为这些变量存在全局区,很有可能被打断后导致全局变量被修改。
非抢占式内核的缺点也很明显,不能让高优先级进程优先执行,我都拉肚子了,你还不让我上厕所,不得憋死我啊,优点也是很明显,非抢占式内核也不担心函数的可重入问题,因为一个时间片只有一个进程在内核空间执行,全局变量也不会被未知的纂改。
非抢占式内核的任务调度逻辑
抢占式内核的任务调度逻辑
内核抢占的设计逻辑
内核做什么事情呢?
内核的主要工作就是分配CPU时间给各个进程使用,并执行处于就绪态的最高优先级的进程,我们知道进程有三种状态,一个是就绪态,一个时执行态,一个时阻塞态,阻塞也就是睡觉了,他自己没事情干了,就睡觉了,就绪就是早上起床去排队准备上地铁,成功上了地铁其实就是进入了执行态了。
Linux 的调度是基于分时技术,因为CPU的时间被分成时间片,给每个就绪态的进程分一个时间片,调度算法不会在意那些阻塞的进程的,毕竟你们都睡觉了,我就不给你们准备午饭了,就调度这些就绪的进程好了。
那什么时候可以被调度呢?
中断和系统调用,中断执行结束后,内核调度器,如果只是说内核的话,有点笼统,内核里面有一个调度器专门用来调度进程的,这个内核调度器就去判断,当前是不是需要重新调度,系统调度也是一样,Linux 内核有无数个系统调度函数,而且系统调度会阻塞,阻塞的时候,CPU如果没事干会非常闲,那就需要调度到其他进程上去执行。
关于调度的知识后面再继续说明,我们先通过CPU的调度说明一个问题,因为多进程调度的原因,肯定会存在一个问题,就是资源竞态。
什么是资源竞态呢?
就是有多个进程去操作一个资源,可以是一个文件,一个变量等待,造成的结果就是不符合预期结果。
什么是内核同步?
我们在上一章说了原子操作,如果多进程对同一个资源操作,就有可能造成这个变量的不确定性,内核同步就是为了解决这种不确定的问题而出现的。
举个例子
有一个房间A里面放了一个篮球,房间没有锁,小明喜欢打篮球,这一天,他没有上课,来到了A房间拿走了篮球,小龙也喜欢打篮球,这一天,小龙也没有上课,小龙也想去A房间拿篮球,发现篮球没有了,所以小龙就没有篮球打了,小龙很生气,就跟老师说,为什么谁拿走了篮球也不知道,被偷了都不懂,为什么不给房间上个锁?
然后学校的老师听了小龙的建议,在A房间上了个锁,锁的钥匙就老师自己拿着,然后,又来了,小明又想去A房间拿篮球,这时候,发现房间上锁了,就只好去找老师拿钥匙,小明拿了钥匙过来开锁拿走了篮球,然后把A房间锁上了。
这时候,小龙也想去打篮球了,小龙就去找老师,老师说,钥匙已经被小明拿走了,要等小明把钥匙还回来,你才可以拿到钥匙,小龙就在旁边看书等着,这时候,小明把钥匙还回来了,老师把钥匙给了小龙,小龙就拿着钥匙开心的去A房间开房拿篮球了。
内核同步的方法
内核信号量
老师突然发现,我应该给那些公共场所的教室都上锁,这样才能安全一些,嗯,就应该这么办?
但是电脑室有十几台电脑,只配一把钥匙感觉不够用,所以老师就配了和电脑数量一样的钥匙,拿到钥匙的同学才可以开门进去使用电脑,没有钥匙的同学要先去老师那里申请钥匙。
这个就是Linux 内核里面的信号量,内核信号量是一种睡眠的锁,如果有一个任务试图获得一个不可用的信号量时,信号量就会把这个任务放到一个等待队列里面,让这个任务睡眠,等这个信号量可以用后,再把这个任务唤醒。
信号量这个代码往上就很多了,我建议大家看代码最好是直接用内核源码来看,分开看很多东西都不是很明白,作为一个Linux 开发者,自己下载一份Linux源码应该是标配了吧,毕竟是开源的。
互斥锁
信号量可以用很多把钥匙,钥匙资源只有一个,那么就只能配一把钥匙,这时候就是二值信号量,也叫做互斥锁。
自旋锁
自旋锁和上面的最大区别就是,自旋锁等待的时候不会睡眠,会继续使用CPU时间,会不断的查询锁的状态,直到获取锁,所以我们如果使用自旋锁的时候,应该使用在那些临界区时间片非常短的资源上,要不然长时间占用CPU,这是对CPU性能的一大挑战。
至于这几个的代码实现方式,想放到下个章节再说明,我认为理解其中的原理,比如何实现更加重要,除了这些,还有有读写锁等等
技术 | 说明 | 适用范围 |
每CPU变量 | 在CPU之间复制数据结构 | 所有CPU |
原子操作 | 对一个计数器原子地“读-修改-写”的指令 | 所有CPU |
内存屏障 | 避免指令重新排序 | 本地CPU或所有CPU |
自旋锁 | 加锁时忙等 | 所有CPU |
信号量 | 加锁时阻塞等待 | 所有CPU |
顺序锁 | 基于访问计数器的锁 | 所有CPU |
本地中断的禁止 | 禁止单个CPU上的中断处理 | 本地CPU |
本地软中断的禁止 | 禁止单个CPU上的可延迟函数处理 | 本地CPU |
读-复制-更新(RCU) | 通过指针而不是锁来访问共享数据结构 | 所有CPU |
插播一个招聘