进程优先级
在了解进程调度之前我们先来看看什么是进程的优先级。
为什么进程会存在优先级?进程的本质就是通过一定的排序来确定享用资源的先后顺序,而当资源不足时,我们就需要通过优先级来确定进程的先后顺序。
我们可以通过ps -la
命令来查看
如上图可以看到有PRI 和 NI两个信息,前者priority代表这个可执行程序的优先级,在Linux中可修改的优先级的范围区间[60,99]共四十个,它的数字越小,代表优先级越高,而Linux中默认的是80;后者nice值表示对优先级的一个修正值,Linux不让我们直接修改PRI,而是通过NI来修改PRI,具体将会使得PRI变为:PRI(new)=PRI(old)+nice,下面让我们来使用看看:
输入top
,然后输入r
,输入对应的PID,然后输入修正的NI值,可以看到最后结果变成了90
那如果再次输入-10,它是会变成80吗?
可以看到结果变成了70,而并不是我们认为的80,因为它并不是简单的覆盖上次的内容,而是重新从默认值开始计算的,NI值的范围是[-20,19]
,也就是计算后的PRI值的范围刚好是[60,99],这个大家可以自行验证。
那为什么PRI有一个范围值呢?因为任何的实时操作系统(控制所有实时任务协调一致运行的操作系统),在调度时,要进行公平的调度!
想一想,如果不进行优先级的限制,那么我们都想自己的进程优先被处理,都想把自己PRI值调低,把别人的PRI值调高,这不就乱套了吗!所以是用来限制进行公平调度的。
谈完了进程优先级,下面我们就来谈谈关键的进程的调度。
进程的调度
我们认为的进程是在CPU上进行的对吧,那么它是把一个进程的所有代码全部跑完之后再跑下一个进程的吗?不对,这样的话相当于我们同时只能运行一个程序,这与我们现实生活中接触的手机啊,计算机啊是相违背的。
其实现代的操作系统是基于事件片来进行轮转的!(也就是每个程序不管执行到了哪一步,超出了指定的时间就会跳出,一般是以毫秒为单位的)
在CPU中我们知道有大量的寄存器:
eax/ebx/ecx/edx:用于存储临时变量
eds/ecs/fg/gs:段寄存器,区分代码与数据
eip:即pc指针,指向当前代码执行的位置
ebp/esp:函数栈帧的建立
等等等等
而每一个进程在执行的过程中基于上面说的事件片轮转的方式运行就会产生大量的临时变量,比如某一步的变量值,执行到了哪一行代码之类的,这些临时变量我们称之为硬件上下文
,当时间片到了的时候,我们就把这些临时数据拷贝到PCB中保存起来,称之为保护上下文
,然后当程序二次调度的时候把这些临时数据在覆盖到对应的寄存器中,来找到上次运行的位置接着往下运行!称之为恢复上下文
,注意是覆盖,也就是说每一个进程并不能看到之前进程的数据,即寄存器只有一套,而我们的这些寄存器中保存的数据有多套。 所以基于这种上下文的保存和恢复,相当于多个进程在并发执行。
接着我们就来看看Linux中的运行队列是怎么进行调度的
这是运行队列的结构,我们关注红蓝方框中的内容,第一个nr_active
表明有多少个进程,
queue[140]
表示运行队列共有140个,本质是一个task_struct类型的指针
,我们这里讨论100-139,那么对应的就是优先级的60-99共四十个,60即对应100,99对应139,每一个指针就指向不同优先级的链表,优先级相同的就都存储在这个链表当中。
所以,运行队列调度进程的时候,就从下标100开始,遍历整个指针数组,即高优先级开始调度,把高优先级的链表执行完,再去执行下一个优先级的链表的进程。那问题又来了,我们需要每次运行都遍历整个数组吗,这样是不是太麻烦了?
于是我们就引出了bitmap[5]
这个整型数组,一个整型32比特位,这个数组也就是160个比特位,我们就用140个比特位来表示指针数组的每一位的状态,1表示有进程,0表示没有进程,
我们就可以一个字节一个字节去遍历,如上图,前两个字节没有,而第三个字节中有,我们在进入到第三个字节中遍历,这样就能大大增加我们遍历的效率!
下面又有一个问题了:如果我们正在执行一个优先级为80的进程,这时候突然增加了很多60优先级的进程,那么怎么办呢,如果只有一个
这样的结构的话,优先级低的进程就一直吃不到资源啊!
所以Linux中提供了两个一模一样的结构,一个用来表示活跃队列,一个来表示过期队列,并且有两个指针
来指向这两个队列。
所以执行的逻辑就是:基于时间片,轮转的进行当前的活跃队列,如果有新加入的进程,把它加入到过期队列中,然后把基于时间片没有执行完的进程加入到过期队列中,然后把这两个指针指向的内容交换,再去执行新指向的队列!执行的时候认的活跃队列的指针!!也就解决了上述的问题。
以上就是关于Linux系统进程调度的理解,如有错误,欢迎指正,谢谢大家!