前言
这篇文章主要介绍一下 linux 下关于进程优先级的概念,还有如何去修改进程的优先级,以及进程的优先级背后所涉及到的调度原理。
1. 什么是优先级
在介绍优先级的时候,我们有必要将其与权限放在一起进行区分。权限讲的是有没有资格,而优先级则是在有资格的前提下,谁先谁后的问题。换言之,权限用于判断是否能过访问某一个资源,而优先级是对于要访问的资源,谁先访问,谁后访问。
2. 为什么要有优先级
你知道的,在系统中存在着几十上百个进程,如果没有优先级的限制,那么这些进程就会毫无秩序。更详细的说法就是,在讲运行态的时候,我们就说过,cpu 只有一个,所以众多进程就势必要竞争资源,进程之间的关系一定是竞争关系的!
带入到自然社会中,存在竞争,那就一定可能存在恶性竞争。
比如食堂吃饭的时候,如果不排队,每个人都可以没有秩序的抢夺食物,那么势必会导致食堂的混乱,甚至发生事故。但是,如果食堂采取排队取餐的策略,就可以避免这种情况,保证了食堂的就餐秩序。而排队,就是优先级的一种体现,体系的就是谁先吃饭,谁后吃饭。排在前面的人自然就是优先级高的,因为这些人先吃到饭。但是优先级是在有资格访问资源的前提下,换言之,这些人吃上饭只是时间上的问题(假设不存在供应不足的情况)。
因此操作系统也是如此,为了保证进程之间的良性竞争,所以有了优先级这个概念。如果我们进程长时间 cpu 资源,该进程的代码长时间就无法得到推进,这种情况我们成为 ---- 进程的饥饿问题。而谁先得到 cpu 资源,是取决于这款操作系统的调度器的调度原理。就好比我们日常中的 windows,可能时不时会出现应用无法响应等问题,其实这一类卡死问题,就是因为进程调度不到位,该进程的代码一直无法被推进,一直无法得到 cpu 资源,所以才会出现 ”卡死“ 的现象。
3. 优先级的调整
在做优先级调整之前,我们得先来见一见 linux 中的优先级长啥样吧。
PRI 就代表这个进程可被执行的优先级,其值越小越早被执
NI 代表这个进程的 nice 值
PPID 即该进程的父进程,代表是由哪个进程发展衍生出来的
PID 进程代号
UID 代表执行者的身份,即这是哪个用户的进程(每个用户也都有一个用户代号)
所以现在我理解 PRI,即进程的优先级,或者通俗点说就是程序被 CPU 执行的先后顺序,此值越小进程的优先级别越高,就越早被 CPU 执行。那这个 nice 是个什么鬼?
NI 表示进程可被执行的优先级的修正数值,PRI 值越小越快被执行,而 PRI 最后的确定是配合 nice 一起计算的,即PRI(new) = PRI(old) + nice,其中的 PRI(old) 不是指在修改前的进程 PRI,它就是80,一直都是80!而新的 PRI 一直都是用 80 +/- 设定的 nice 值计算出来的。
nice 的取值范围是-20至19,一共40个级别,因此只要将 nice 设置为负数,那么就代表优先级被调高了。反之变低。
而 nice 有取值范围,就意味着进程的 PRI 可调整的范围就注定在 [60, 99]。
而调整进程的优先级,这里提供三种方法。
3.1 nice 调整
nice -n -10 test // 调整后进程的 PRI 就是 80-10=70
nice -n 10 test // 调整后进程的 PRI 就是 80+10=90
3.2 renice 调整
renice -n 10 -p 12345 // -p 后面跟着的参数即进程的 PID
renice -n -5 -u username // 将指定的这个用户的全部进程的优先级进行调整(特别不推荐)
3.3 top 调整
命令行输入 top 命令之后,再输入 R,然后输入要调整的进程的 pid,然后会提示修改的数值,再次输入要修改的 nice 值
如果你输入的 nice 值超过了系统限定的范围,系统也是会做出一定的反馈的。
虽然我们看到的是权限不足的提示,但即便你切换到 root 不会遇到权限问题,你输入的 nice 过小 / 过大,最终进程的优先级还是处于 [60, 99] 这个范围。可以理解为这是 linux 系统在优先级上的一种硬控吧。
实验表面,尽管身为 root,在 nice 调整的时候可以超过给定范围,但是这并没有效果。PRI 依旧只能是60 或者 99,无法超过这个范围。并且我们要清楚的是,可以对 nice 进行调整,提高进程的优先级,但这并不意味着我的进程可以几乎一直被调度!因为nice 的调整是有范围的,作为 linux,它并不想过多的让用户参与优先级的调整。
4. 优先级涉及到的调度问题
所以凭什么说,优先级高了,你的进程就会优先被系统调度,进而被 cpu 执行。所以操作系统是如何根据优先级的高低,开展的调度呢??
结合草图理解,讲运行态的时候,我们提过:每个 cpu 都有一个运行队列,而需要 cpu 资源的进程,会在运行队列中进行排队等待调度(前提是其它外设等资源已经就绪,进程可以被执行的前提)。又假设运行队列中有一个 running 的 task_struct* 这样的指针数组,并且大小为140,而其中的 0-99 都是其中种类的进程使用的(可以理解为是系统运行所需的内部进程),而剩下的 100-139 这40个位置刚好对应 nice 的调整范围(40个级别可调整),所以我们不妨大胆猜测,把 running 数组抽象成一块内存空间,在这块空间的最底层就是用来存储 那些我们能够调整优先级的进程。
整体看待的话,就是 running 中存储的就是正在被 cpu 执行调度的进程队列,而 waiting 就是等待调度的队列,当 running 中的进程都调度完了,就会换成 waiting 中的进程去调度(**run,**wait 这两个是二级指针,分别指向 running 数组和 waiting 数组,而当 running 中的进程调度完了之后,需要换成 waiting 队列中的进程去调度,只需要将这两个二级指针所指向的空间的地址 swap 一下,那么不就换过来了吗?!)。
接着面临的就是为什么优先级能够直接决定执行的先后顺序,因为系统总得判断什么时候执行到 running 队列的尾部吧?换言之,总得把 running 队列中的进程全部调度完毕之后才能换下一批进程吧?所以问题就转换成:如何判断是否已经调度完毕了,可以换队列了。没有办法,只能遍历这个数组,一个一个找!(不可能执行到 PRI 为63的进程的时候,发现这里没进程了,然后就直接换队了,说不定 PRI 为80的位置有一大堆进程等着被执行呢)
你说遍历个一两遍,十几二十遍数组,那也还好。但是!调度是一直存在的一个行为啊,换言之,为 running 数组判空这件事,是一直需要被执行的,也就是遍历数组这件事一直需要重复做的(running队列执行完了,把 waiting 队列中的换上来, 原本 running 的换下去给 waiting,以及类推,反复置换调度)。所以在遍历数组这件事,就采用了 “位图” 的思想!
我们就不以 140 个数组大小为例子了。我们就假设只有 20 个位置需要检查。
采用位图,我们只需要将 20 个数组大小,抽象成 20 位二进制,0 代表这个位置的进程都被调度完毕了,1 代表这个位置的进程还有没被调度的。
设 x = 0000 1110 0110 1101 1010
当 x = 0 的时候,不就是队列中所有的进程都被调度完毕了吗!而如果 x ≠ \not= = 0 ,那就计算找到距离最近的二进制 1,映射到它当前所对应的 PRI 的存储位置,然后以此类推,直到全部的二进制位都位 0,即该队列的进程已经全部执行完毕,可以置换队列了。
所以优先级怎么决定进程被执行的先后顺序的? ---- 当遍历运行队列的时候,不管是采用位图的思想,还是直接遍历,都是从上到下依次遍历,上面的注定就是存储着优先级高的进程,所以从上到下扫描队列,那么一定是优先级高的进程先被执行,执行完才会继续往下扫描下一级的进程。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!