一.进程优先级
1.是什么?
进程优先级是得到CPU资源的先后顺序。
那么优先级和权限有何区别呢?
优先级是能够得到CPU资源,只是先后问题而已,而权限考虑的是能否得到资源的问题。
2.为什么要设计优先级?
原因在于CPU资源有限,要通过优先级去确定谁先谁后。
3.怎么办?
进程优先级是task_struct里的一个属性,是一个int数字。值越低,优先级越高。
①.知识点补充------uid
uid:即user id,OS识别用户是谁的东西,每个用户都有一个唯一的uid,这就是进程为什么访问文件的时候,OS准确识别出访问者是owner还是other还是所属组。
来说说是怎么识别的:
当查询某个进程的相关信息时,也会显示当前用户的uid,即显示派自己来做事的老大是谁:

创建文件时,文件也会储存三个角色的uid:

而访问文件的指令本身就是进程,那么指令这个进程也会存有uid,通过比对文件的uid和指令的uid,如果和文件哪一个角色的uid相等,那么就判定访问者是什么角色。
用户->bash(OS给每个用户一个bash)->将访问任务交给多个进程(指令)->每个进程完成自己的访问任务。
因而,进程就代表用户,就像杨金水代表朝廷管理江南织造局一样。
②.回归正题

在进程详细信息这个列表中,PRI就代表进程的优先级 ,它的默认值是80 ,这个值可以改变,但不建议改动。而NI是nice值,代表进程优先级的**修正数据,**通过更改NI,就可以更改优先级。
得出一个结论:

这里有个坑,每次调整优先级,都是从默认值开始,去加NI值,所以千万不要拿PRI那列的数字去加NI值。

同时,NI值是有范围的:-20,19
LinuxPRI默认值为80,所以实际优先级的范围是**60,99**
为什么要去设置一个范围呢?原因是为了进程得到CPU资源的公平性,得让每个进程不能等的太久。长期得不到资源的进程会有进程饥饿的现象出现。就像排队在食堂打饭,等太久人都饿麻了。
二.切换进程
1.知识点补充------竞争,独立,并行,并发
①.竞争
OS内的进程数量始终是要多余CPU的资源的,所以进程间必然发生竞争。为了高效完成任务,合理竞争资源,就有了进程优先级。
②.独立
就像任务管理器里有很多进程在使用CPU资源,将其中一个进程结束,并不会影响其他进行的运行。每个进程在使用资源期间,这些资源供其独享,多个进程之间互不干扰。至于如何做到独立性,我们后面的文章的谈到。
③.并行与并发

并发:一个CPU先服务一个进程,然后迅速断开,再服务另一个。
并行:多个CPU同时服务多个进程。
2.进程切换
①.死循环如何运行
每个进程都会被分配到一个叫时间片 的东西,这是个时间段,当CPU服务这个进程的时间超过时间片,就会停止服务,换句话说进程拥有CPU资源不是永久拥有的,而是在时间片这段时间内暂时被拥有,时间一到,CPU就会切换另一个进程,进行服务。所以进程拥有太多的数据或代码的话,是不一定能在一次服务里被执行完的。
而死循环进程并不会一直执行下去,直到内存被消耗完,卡死系统;当其运行时间超过时间片,CPU就会切换服务的进程。
②.浅谈CPU的寄存器
1.CPU内部存在多个寄存器,多个寄存器临时储存一个正在运行的进程的上下文数据。
2.寄存器!=寄存器里的内容。
寄存器就是运输机本身,只有一份,寄存器里存的临时数据就是装在运输机里的东西,要么是坦克,士兵,大炮,要么混着来,反正不唯一,也不重样,运载的武器换了,但运输机还是它自己。
③.如何切换进程
Ⅰ.与大学生参军入伍的例子,类比出切换的过程:

进行一次保留,剥离,再恢复的过程,就是进行一次切换进程。
Ⅱ.具体切换的过程:


数据经拷贝以后存储在何处?

如何区分全新进程和已经调度过的进程呢?


三.Linux进程调度
切换和调度共同构成了调度器。
我们在上一篇文章梳理了调度队列如何处理阻塞的原理,但那是关于操作系统学科范畴,属于纸面的东西,这一部分将要从Linux底层的角度去了解调度。
1.前备知识
在了解整个底层之前:得明白我们正在使用的操作系统是分时操作系统 ,是基于时间片进行调度的,而不是当下谁重要,谁优先级就最高的实时操作系统。但Linux操作系统是具有一定的实时操作系统功能的,只是平时使用时被裁掉或被关闭了而已。
2.映射算法
queue140数组里有140个位置,每个位置都存储一个task_struct指针 ,前100个属于实时优先级,不考虑,剩余40个就刚好对应了分时优先级-20.19里的40个优先级的范围。
那么如何将越界的分时优先级映射到剩余40个位置里储存起来呢?
这40个位置的首个位置下标是100,60是默认值80 + 优先级最小值-20,用60作为被减数的原因是求一个偏移量:

3.调度规则
既然能玩映射,那么这个queue140的本质是一个哈希表。
假如几个进程的优先级相同 ,那么就可以像哈希桶那样,将这几个进程全部链接在那个位置上。
那么就可以总结一下:在整个宏观的queue上,看优先级, 按照进程优先级(也就是顺着queue往下)来进行调度**,但需要逐个节点的去遍历,可能会出现遍历到的节点没有进程的情况,找到第⼀个⾮空队列,该队列必定为优先级最⾼的队列。在局部的同一个位置,即优先级相同时,按照先进先出的原则对链表上的进程进程调度。所以就要分两步,先挑队列,再挑进程。**
4.调度器如何快速调度?
按照以上的调度方式,调度器去寻找某个进程,在链表上的开销是O(1),在整个队列上的开销是O(N),那么总开销就是O(N),是比较低的,那么调度器可否快速挑选进程呢?
造成低效率的原因是,挑队列的时候我无法知道哪个位置是有task_strucrt的,因此需要在队列上从上往下按着优先级来逐个遍历队列元素。
这就不得不提runqueue里位于queue140上方的bitmap5了,这是个无符号整型的map,一个无符号整型的数是32位,那么5个就是160位,每位与runqueue上的元素一一对应,每个比特位无非两种结果------0或1,1就代表queue对应的元素存在task_struct,反之就不存在。所以挑队列时就可以根据map筛出有task_struct的位置,然后在这些位置中再挑进程。就能在O(1)的开销下挑队列。
那为啥是160个比特位,也就是5个无符号整型呢?原因是4个数128位没法覆盖包揽runqueue每个节点,6个数192位又过于浪费了。那么闲置的20个就不用了。
