目录
前言:
进程优先级存在的合理性:
进程优先级在Linux操作系统里的是什么:
从指令层面认识Linux下的进程优先级:
见一见PRI和NI:
修改进程优先级的操作:
nice值存在的必要性:
Linux2.6系统下的调度队列和O(1)调度算法:
调度队列里的优先级队列:
直达O(1)之间复杂的的梯子---位图
编辑 补充两点:
进程切换:
简单的描述切换过程:
举一个例子:
进程切换的速度:
为啥要进程进程切换:
啥是进程上下文数据:
前言:
之前我们已经大致了解了进程的一个属性 --- 进程状态 , 这是针对一个进程而言 .
那操作系统如何合理的管理各个进程以此保证大多数进程都能正常运行 , 就是这一章要探讨的内容了.
进程优先级存在的合理性:
想象一下这个情况 : 学校食堂通常只在饭店才需要排长长的队 , 如果你天生反骨,下午三点去吃午饭,我相信排队的可能性直逼0% . 可为什么在饭店要排队?因为资源是有限的 , 打菜窗口相对于学生数量来说实在是太少了 , 为了保证公平 , 必须通过排队来确定优先级
生活中如此 , 内存资源有限的操作系统也是如此 , 必须通过
进程优先级在Linux操作系统里的是什么:
就和进程的状态一样 , 进程的优先级也是进程控制块PCB的一个属性 , 用整形来表示的
从指令层面认识Linux下的进程优先级:
见一见PRI和NI:
使用ps指令的 -l选项 , 可以显示出进程的优先级PRI和优先级修正值NI , 和之前学过的指令像结合就可以达到下图的效果
修改进程优先级的操作:
nice值存在的必要性:
PRI(priority)已经是进程的优先级了 , 可修改进程的优先级还是要通过另外的NI修正值,原因如下:
- 从使用层面来说 : 程序员可能不止一次修改过进程的优先级 , 倘若没有nice值的存在 , 后续修改优先级时就需要知道前上次的修改信息 , 这样很不利于操作 , 而nice值的存在使得程序员很容易的知道上次的修改情况 , 同时 , 只能通过修改nice值使进程的优先级值在一个基准值上下浮动,避免了多次修改后造成的混乱
- 从操作系统层面来说 : 由于同时存在多个进程 , PRI值相同的值在同一个优先级的调度队列里,如果直接更改进程的优先级数值 , 那么就需要多开发一套算法 , 来动态的将进程插入到其他优先级队列里 . 而使用nice值 , 可以让进程在被调度一次之后再根据nice值自然地插入到对应的优先级队列中.
- 当然了 , 即便有了nice值 , 在一定程度上可以规避调整进程优先级给调度队列带来的混乱 , 可终究还是有影响 . 因此 , nice值的修改操作没法执行很多次 , 会被操作系统拒绝.
Linux2.6系统下的调度队列和O(1)调度算法:
调度队列里的优先级队列:
一个CPU就存在一个运行队列runqueue , 用于存放进程调度的各种信息 , 既然刚刚谈论了进程的优先级 , 那就先来看优先级队列 :
他是runqueue内的一个元素---下图里queue[140] , 他是一个队列 , 按照进程的优先级分为了140个元素 , 每个元素都各自存放了一个管理进程的双向链表的头指针 , 用于管理同一优先级的进程 ,因此称为runqueue优先级队列
直达O(1)之间复杂的的梯子---位图
到此为止 , CPU想要一次调度优先级从高到低的进程 , 似乎最多也只用遍历优先级队列的140个元素,对每一个链表判空 , 调度非空的链表即可 , 可并非优先级队列的每一个元素里都存在元素(链表元素---特定优先级的进程) , 所以又设计出了位图来优化查找需要调度进程的效率 ,如下图所示:
补充两点:
- 之所以称作O(1)调度算法,并非是只用访问一次 , 而是这样的设计使得无论是有很多空链表还是很少的空链表 , 判断的效率都是固定且高效的.
- 想象一下 : 就好像一条街上有140个房子 , 如果是无脑的遍历 , 就好像一个一个找 ,每次还得进去看看有没有人 , 如果没人的话这个查看的过程就浪费了时间 , 而且有多少空房间不固定,增加了不确定性 ; 如果是使用位图来辅助定位 , 就好像你提前拿到了每个房子的信息后用一次性就排除了空房子 , 对于没人的房子,可以省去进去查看的时间
进程切换:
既然我们知道了操作系统通过时间片和一些内核的数据结构来有条不紊的来回调度每一个进程 , 那一个时间片耗尽的进程是如何把资源暂时让位给其他进程以及如何之后又重新运行的呢? 这就是进程切换要谈论的问题.
简单的描述切换过程:
当一个进程时间片耗尽 , 被操作系统从调度队列上剥离 , 并保存他的硬件上下文数据(比如运行到哪段代码了) , 等到下一次调度时 , 再根据保存的硬件上下文数据来恢复此进程的运行 , 继续完成他的使命.
举一个例子:
加入小明是一个在校大学生 , 本来上课上的好好的, 某一天心血来潮想要离开学校去参军(一个进程从调度队列剥离),倘若他没和辅导员(操作系统)上报 , 自个就去了 , 一年后再回来 , 可能就发现被勒令退学了!!!
正确的做法是告诉辅导员你要干啥去 , 于是辅导员保存了一份你的相关材料(硬件上下文数据)这样你才可以安安心心的暂时离开学校 .
在回学校之后 , 你也不能直接偷偷摸摸的去上课 , 依旧是找到辅导员 , 修改你的材料为正常状态(根据保存的硬件上下文数据重新调度) , 这才能够继续你在学校的正常学习.
进程切换的速度:
虽然上面的例子里提到小明从出去参军到回来间隔了一年 , 但进程切换的间隔可快多了.一个进程从被剥离到再次被调度的时间间隔极短---人类无法察觉的微秒级别!!!
为啥要进程进程切换:
由于CPU的局限性 , 在同一时间只能执行一个进程 , 可用户的期望必定是同时运行多个程序 , 但好在是CPU快得惊人 , 因此来回的进程间切换看起来就好像是同时进行!!!
啥是进程上下文数据:
再进程的切换里我们对于进程上下文数据一笔带过 , 但深入一点说 , 就是一个进程运行的情况 , 比如进程本身的信息和运行到哪段代码了(有程序计数器保存下一条待执行的指令地址) , 这些都可以通过CPU和寄存器将他们记录并保存并到其他地方 .
存到哪里? 早些年的操作系统直接将进程上下文数据存在进程自己的PCB里 , 但随着计算机的发展 , 进程上下文的内容太大 , 于是又存了的一个叫做任务状态段的数据结构对象里 ,不过任然让进程自己的的PCB保存一个指针指向他.