今天我来学习Linux:进程的切换与调度,学完这个,不仅可以让你知道cpu进程是怎么样运行的,也能让你知道为什么我们单核cpu运行时也好像多核cpu一样处理多个进程
话不多说,现在开始啦>.<
1.进程切换是什么
CPU上下⽂切换:其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运⾏另外的任务时, 它保存正在运⾏任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务⾃⼰的堆栈中, ⼊栈⼯作完成后就把下⼀个将要运⾏的任务的当前状况从该任务的栈中重新装⼊CPU寄存器,并开始下⼀个任务的运⾏, 这⼀过程就是context switch

一般来说,保存的进程上下文(CPU 寄存器、程序计数器等)会存储到该进程的 PCB(task_struct)中
下面是Linux内核源码对于旧数据处理存放的代码,很明显的看到存储在task_struct里面的一个任务状态段数据结构了

2.为什么要进程切换
这个其实很好理解,就比如你写了一个程序,是一个死循环输出打印的程序
如果不进行进程切换,那么cpu就会一直将这个进程持续进行下去,知道这个进程结束,那太可怕了,因为这个进程是个死循环,永远无法结束,不仅后面的进程无法运行,同时内存有可能会直接崩掉
有了进程切换,那么假如运行1ms这个进程,就执行下一个进程,就不会发生上述情况,不仅不会死循环,也能让后面的进程执行
3.怎么进行进程切换
为了解决这个问题,于是定义了一个时间片,系统会给每一个进程设置一个合适的时间片,当这个进程的时间⽚到达,进程就被操作系统从CPU中剥离下来,下次再运行
时间⽚:当代计算机都是分时操作系统,没个进程都有它合适的时间⽚(其实就是⼀个计数
器)
4.深度理解Linux内核的进程切换与进程调度(Linux2.6内核进程O(1)调度队列为例)
下图是内核里面runqueue的具体结构,下面我们将一一剖析它们

1.优先级
普通优先级:100〜139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)
实时优先级:0〜99(不关⼼)
queue[140]里面,0~99存放的是实时优先级(工业上用得到,我们只需要了解),而剩下的100~139(40个)就对应我们的PRI,也就是普通优先级,这也解释了为什么我们NI范围是**-20~19**,那系统怎么知道谁先执行呢,就是通过queue[i]的索引i,i在前面,就先索引,那要是有很多个PRI相同的进程怎么办,想一想链式哈希桶,Linux内核就是这么弄的,将他们头插在queue[i]上,这就是优先级与进程的关系,本质上就是顺序遍历queue[]上的东西
2.活动队列
蓝色方框里面的就是当前的活动队列,上面说了,优先级与进程的关系,本质上就是顺序遍历queue[]上的东西,可是这样每次都要O(n)遍历,太浪费时间了,于是bitmap来帮助我们,为什么是5呢,因为4 * 32(sizeof(size_t)) < 140 < 5 * 32,所以5最合适,这样判断bitmap就知道queue那些索引存在,达到O(1)复杂度
那运行超过时间片的进程放哪儿呢,答案是进入过期队列

总结一下就是:
时间⽚还没有结束的所有进程都按照优先级放在该队列
• nr_active: 总共有多少个运⾏状态的进程
• queue[140]: ⼀个元素就是⼀个进程队列,相同优先级的进程按照FIFO规则进⾏排队调度,所以,
数组下标就是优先级!
• 从该结构中,选择⼀个最合适的进程,过程是怎么的呢?
-
从0下表开始遍历queue[140]
-
找到第⼀个⾮空队列,该队列必定为优先级最⾼的队列
-
拿到选中队列的第⼀个进程,开始运⾏,调度完成!
-
遍历queue[140]时间复杂度是常数!但还是太低效了!
• bitmap[5]:⼀共140个优先级,⼀共140个进程队列,为了提⾼查找⾮空队列的效率,就可以⽤
5*32个⽐特位表⽰队列是否为空,这样,便可以⼤⼤提⾼查找效率!
3.过期队列
也就是红色框部分,过期队列和活动队列结构⼀模⼀样
过期队列上放置的进程,都是时间⽚耗尽的进程
活动队列上的进程都被处理完毕之后,对过期队列的进程进⾏时间⽚重新计算
4.active指针和expired指针
active指针指向活动队列
expired指针指向过期队列
当活动队列全部都执行完后,就swap(active与expired),这样活动队列继续执行,过期队列也再次为空,交替,直到所有进程全部完成
5.总结
在系统当中查找⼀个最合适调度的进程的时间复杂度是⼀个常数,不随着进程增多⽽导致时间成
本增加,我们称之为进程调度O(1)算法!