基本概念
- cpu资源分配的先后顺序,就是指进程的优先权(priority)。
- 优先权高的进程有优先执行权利。配置进程优先权对多任务环境的linux很有用,可以改善系统性能。
- 还可以把进程运行到指定的CPU上,这样一来,把不重要的进程安排到某个CPU,可以大大该善系统整体性能。
简单点说
进程优先级是什么?进程得到CPU资源的先后顺序;
为什么要有优先级?目标资源稀缺,导致要通过优先级确认谁先谁后的问题;
如何做呢?其实优先也是一种数字,int,task_struct
值越低,优先级越高,反之,优先级越低
基于时间片的分时操作系统,考虑公平性,优先级可能变化,但是变化幅度不能太大(在如何调度的地方会有更详细的)
这里可以区分一下 优先级 与 权限
- 优先级就是能得到资源,先后的问题;
权限就是能否等得到某种资源
查看系统进程的基本信息
UID : 代表执行者的身份,也就是 user id
PID : 代表这个进程的代号
PPID :代表这个进程是由哪个进程发展衍生而来的,即父进程的代号
PRI :代表这个进程可被执行的优先级,其值越小越早被执行
NI :代表这个进程的nice值
(接下来说的都是基于时间片的分时操作系统,来研究学习 进程的优先级)
- PRI:表示进程的优先级,默认值为 80 。
- NI:是进程优先级的修正数据,也叫 nice 值。
- 进程真实优先级的计算:进程真实的优先级等于 PRI(默认值)+ NI。
- 需要强调⼀点的是,进程的nice值不是进程的优先级,他们不是⼀个概念,但是进程nice值会影
- 可以理解nice值是进程优先级的修正修正数据
响到进程的优先级变化。
这里主要了解什么是进程的优先级
改变优先级PRI一般情况下不用会被调整(至于为什么,调度算法就能清楚为什么不能直接修改 PRI +++)
这里有一个小知识
系统怎么知道我访问文件的时候,是拥有者,所属组,还是 other??
Linux 系统中,访问任何资源,都是进程访问,进程就代表用户;因此Linux中的进程是通过 uid 识别文件的,每一个文件都有对应的uid,这个uid可以让OS知道是 拥有者 还是 所属组,other;
可以理解nice值是进程优先级的修正修正数据
⽤top命令更改已存在进程的nice:
- top
- 进⼊top后按“r”‒>输⼊进程PID‒>输⼊nice值
其他方法:
如nice renice命令都可以调节优先级,有兴趣可以自己查看一下;
优先级的极值问题
以上面的操作可以知道
nice : [-20, 19] PRI默认是: 80
Linux 进程的优先级范围 [60, 99]
为什么呢?其实基本概念处已经提到了,
- 基于时间片的分时操作系统,考虑公平性,优先级可能变化,但是变化幅度不能太大
- 优先级设立不合理,会导致优先级低的进程,长时间得不到 CPU 资源,进而导致:进程饥饿优先级低的进程长时间都不到 CPU 处理
补充概念-竞争、独立、并行、并发
- 竞争性:系统进程数目众多,而 CPU 资源只有少量,甚至 1 个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级。
- 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰。
- 并行:多个进程在多个 CPU 下分别,同时进行运行,这称之为并行。
- 并发:多个进程在一个 CPU 下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发。
以我们的电脑为例子,一般都是一个 cpu;但为什么我们看起来可以同时运行多个软件呢?
其实这个就是进程的 并发
几毫秒 切换一次进程,我们几乎感觉不到
如何查看cpu状态:
进程切换
在了解切换之前,我们先了解一下死循环与寄存器
死循环进程是如何运行的?
- 一旦一个进程占有 CPU,会把自己的代码跑完吗?不会!!时间片的东西。
这样就不会出现一个进程死占 CPU 的状态,粗略讲,就是一个进程运行的时间;
- 死循环进程,不会打死系统,不会一直都占有 CPU!
寄存器
对寄存器的大致理解就是:寄存CPU的一些,需要保存临时内容的空间;
当然寄存器的种类有很多
下面只是演示的是其中一种 pc
CPU上下文切换
CPU上下文切换:其实际含义是任务切换, 或者CPU寄存器切换。当多任务内核决定运行另外的任务时, 它保存正在运行任务的当前状态, 也就是CPU寄存器中的全部内容。这些内容被保存在任务自己堆栈中, 入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器并开始下一个任务的运行, 这一过程就是context switch。
具体的分析:
进程切换,最核心的,就是保存和恢复当前进程的硬件上下文的数据,即 CPU 内寄存器的内容!!!
所以:当前进程要把自己的进程硬件上下文数据,保存起来,保存到哪里了????
保存到进程的 task_struct 里面的TSS 这个结构中!!
全新的进程 vs 已经调度过的进程
Linux真实的调度算法(Linux2.6内核进程O(1)调度队列)
一个CPU拥有一个runqueue
- 如果有多个CPU就要考虑进程个数的负载均衡问题
runqueue 是Linux中某些版本的运行队列
优先级(queue[140])
为什么优先级队列是140?
其中100是实时操作系统,40是分时操作系统;
为什么?
要知道Linux不只是在后端领域使用,它服务于很多领域,只是我们不常用而已;
- 分时操作系统(基于时间片的那种)
- 实时操作系统 ---- 应用领域(cpu 处理完了一个进程,在处理另一个进程)(如汽车的避险刹车)
如何查找调度队列中的进程呢?(bitmap[5])
我们发现,找对应的队列,优先级需要变量,虽然只有 40 个 但是时间复杂度还是 O(N)
这里就利用了位图的思想,来解决这个问题。
bitmap[5] (5 * 32 = 160 > 120 )正好可以利用位图思想查找 效率为O(1)
队列arry[1],arrry[0]
活动队列
- 时间片还没有结束的所有进程都按照优先级放在该队列。
- nr_active: 总共有多少个运行状态的进程
- queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,
- 数组下标就是优先级!
- 从该结构中,选择一个最合适的进程,过程是怎么的呢?
- 从0下表开始遍历queue[140]
- 找到第一个非空队列,该队列必定为优先级最高的队列
- 拿到选中队列的第一个进程,开始运行,调度完成!
- 遍历queue[140]时间复杂度是常数!但还是太低效了!
- bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!
过期队列
- 过期队列和活动队列结构一模一样
- 过期队列上放置的进程,都是时间片耗尽的进程
- 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算
完整的Linux O(1)调度算法
struct rqueue_elem
{
int nr_active;
bitmap[5]; --------- struct rqueue_elem prio_array[2];
queue[140];
}|
struct rqueue_elem *active = &prio_array[0]
struct rqueue_elem *expired = &prio_array[1]
swap(&active, &expired);
大致过程
nr 是存储的 有多少进程
- 查 nr 有没有进程通
- 过 bitmap 查找位置
- 找到 队列的头进程 q.top pop链接到 task struct current 中 。
一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度;
这个过程就类似于哈希桶一样
正如 活动队列 过期队列里面说的那样;
当活动队列上的进程都被处理完毕之后,会被链接到过期队列的相应位子中,待活动队列中没有进程时,对过期队列的进程进行时间片重新计算;若还没有完成,再次链接到活动队列中,以此循环;
最后小总结:
理解好上面的过程,就能更好的理解 调度算法 抢占 等等。
抢占:最简单的理解,也不能说特别对的理解:
当加入一个新进程时 是进入 a 队列 还是 e 队列呢?
可以理解为 直接 加入到 a 队列; 这也相对插队了,只要优先级提前就能提前调度 。
同时解答了前面,为什么优先级的改变只能还不NI值,而不能改变 PRI值