欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪
文章目录
- 进程优先级
- 进程的切换
- linux的调度算法
进程优先级
进程的优先级决定了进程获得CPU资源分配的顺序,在进程(0)这篇文章中博主就讲过并发和并行两个概念。即对于人来说,计算机运行的进程时同时运行的,而对于cpu来说,在一个时间片内,只能处理一个进程。因此,为了能让cpu释放出最大的资源(让更重要的进程被运行,比如操作系统),操作系统会根据进程的优先级,来决定优先级高的进程优先被调度。
进程优先级的本质,是对于cpu资源的竞争。
使用ps -al
指令,可以查看当前用户启动的进程的属性,其中pri决定了该进程的优先级。
比如在上例中,bash进程的pri为80,ps的pri也是80,pri值一致的进程,优先级一致,而pri值越低,该进程则拥有更高的优先级。我们可以看看系统进程的优先级,输入top
指令,可以打开linux系统的任务管理器。
可以看到,系统进程的pri值远低于一般的进程,因此在cpu分配资源时,会倾向于系统进程,这是因为系统进程需要完成管理环境的任务,没有好的任务环境,那么其他进程运行肯定也会收到影响,因此系统进程的优先级需要高于一般的用户进程。
我们可以修改进程的优先级,首先我们随便写一个常驻进程的代码。
接着,我们编译该代码,生成二进制文件test。尝试运行。接着打开另外一个窗口,输入top进入任务管理器,接着输入r,此时top会进入renice模式,我们输入test进程对应的pid,可以修改test的优先级。
要注意一下pri的修改规则,首先我们要知道
pri = pri(默认)+ni
这个ni值是pri的修正值,而pri(默认)的值,是ni值为0的时候,pri的值。ni的全称是nice,即pri值的修正值的意思,ni的值只会在[-20,19]的区间里面取,超过则按照最大可允许的值计算。
比如当前的进程的pri值为80,ni值为0,那么该进程的pri(默认)的值为80,而renice操作,是让我们修改ni的值,比如我们让ni的值变成30,由于ni值只能取[-20,19],因此该ni的值只会是19,此时该进程的pri=80+19=99.
由于pri值越高,优先级越低,因此将ni值设入正值的操作,实际上是降低了该进程的优先级。
此时,我们再回到top当中,此时将ni值设为-30,由于ni值的区间是[-20,19],因此实际ni值为-20,此时pri=80-20=60。要注意pri值的计算方式是pri(默认)+ni。而不是pri(当前)+ni。因此虽然test进程的pri(当前)是99,但是将ni值设为-20之后,test的pri值会变成60。
要注意,普通用户多次修改用户优先级可能会被权限限制,此时可以用sudo指令打开top(sudo top),或者用root账号来执行该操作。
进程的切换
这里给大家补充一下进程的一些概念:
- 竞争性:进程多于cpu个数,一个cpu要处理多个进程,因此,进程是需要竞争cpu资源的,在这个情况下,进程的优先级就是决定了哪个进程会优先使用cpu的资源。
- 独立性:进程之间是独立的,数据并不共享。
- 并发:cpu在一个时间片内只能处理一个进程,时间片到了,就要切换下一个进程
- 并行:cpu在一段时间内会切换多个进程处理,给用户造成多个进程一起运行的现象
进程的切换操作则是基于上述的概念。
现在我们都知道,在一个cpu处理一个进程的时间片到了之后,就会切换下一个进程来处理,但是一个时间片未必就能将进程执行完,比如我们打开qq,这个qq可能会在后台挂上一整天,因此一个短短的时间片肯定是不能将qq的进程执行完的,此时cpu会切换到下一个进程来处理。
那么问题来了,此时qq这个进程可能被处理到了某行代码当中,然后切换到了下一个进程。那么当qq被切换回来时,cpu又怎么知道上一次处理qq这个进程时,执行到了哪行代码中呢?
cpu中存在多个寄存器,这些寄存器负责存储程序产生的临时数据,以供指令进行数据方面的运算。这里博主不多介绍寄存器以及寄存器的相关作用,而是只讲重点的两个寄存器,分别是pc寄存器(指令计数器)和ir寄存器(指令寄存器)
- pc寄存器:用来存放下一条指令的地址。(由于cpu要处理连续的指令,因此需要pc寄存器去记录下一条指令的地址)
- ir寄存器:用来存放当前正在执行的指令。(是指令,而非地址)
这两寄存器与进程的关系如下图:
假如此时进程1的时间片到了,执行进程2,那么ir寄存器和pc寄存器的数据就会被进程2的相关数据覆盖。
那么当进程2结束后,此时切换回进程1.
此时,尴尬的事情发生了,pc寄存器不知道刚刚进程1的指令执行到哪了,那么cpu该怎么处理进程1呢?难道要让进程1从头开始吗?那么当进程2切换回来时,也要将进程2从头开始吗?我们可以设想这么一个计算机,如果使用该计算机时,听音乐只能听前奏,看电影只能看片头,打游戏一直看加载,那么它肯定没法让用户有个好的体验。
所以linux采取将寄存器内的数据,保存在内存当中的策略。当一个进程切出时,将该进程的寄存器数据保存下来,然后当该进程被切回时,将该进程的寄存器数据读取。这样下来,可以确保每个进程,都能按照上一次被处理时的进度,继续处理下去。这些寄存器中的数据,就是该进程的上下文数据
。
那么进程的上下文数据保存在哪呢?我们先来想想,内存当中谁与进程的关系最密切呢?那当然是负责记录进程信息的task_struct了。我们在早期的linux源码中可以看到。
从上可以看到,一个进程的上下文数据,是保存在该进程对应的task_struct对象中的tss对象当中!当一个进程被切出时,tss会记录下当前寄存器中的数据(如上图),然后当这个程序被切回时,读取task_struct中的tss数据,就能恢复之前运行时候的状态了,也就是能按照上次运行的结果,继续运行。
所以task_struct不仅要记录进程的状态,也要记录进程的上下文数据才行啊。
linux的调度算法
其实这个调度算法对于我们使用linux的用户来说,指导意义并不大,但是博主既然谈到了优先级,如果不讲调度算法的话,大家可能体会不到优先级到底有什么用。
如果对操作系统这门课学的比较好的人,可能就会说,调度算法不就是FIFO算法嘛,简单。但是这个FIFO算法其实是老一点的系统才会使用的调度算法了,因为linux系统允许我们更改进程的pri值,对应的也就是说如果采用fifo的调度算法,如果一个进程的优先级被改变,就要花O(N)的时间复杂度,将该进程挪到对应的位置,这显然很慢。
linux的调度算法用了一个哈希桶的结构。这个哈希桶有140个映射位置,前100禁止用户使用,因为这些位置的进程,只能由系统决定怎么用(当然如果你改了linux源码也不是不行)。而后40个映射位置,即[100,140)。
前面将优先级的时候,博主不是提到了嘛,一个用户的进程,其默认pri值等于80,ni的修正区间为[-20,19],因此,一个由用户启动的进程,其修正后的pri值的区间也就是[60,99]。
linux的调度算法为:进程的位置=修正后的pri值-60+100。
比如我们现在有两个pri值为61的进程,一个80的进程,和一个99的进程,那么根据调度算法,它们的位置分别为:101,101,120,139.那么这四个进程的运行队列如下:
此时,每次都调用桶中的顺序最靠前的进程,不就是pri值越低,越先调用,优先级越高;pri值越高,越后调用,优先级越低。
但是此时一个问题就诞生了,pri值61的进程被优先处理,但是一个时间片内未必就能将这个进程处理完毕,特别是该进程是常驻进程的情况下,此时时间片到了,系统就会调度下一个进程,即另外一个pri值也是61的进程,而未处理完毕的进程又会被挂进桶中。
有没有发现这种调度算法的问题?即优先级高的两个进程会被轮流调度,而此时进程c和进程d相当于一直没有运行。
这里博主说一个概念,我们平时的家用pc,其操作系统都是分时操作系统,即所有处于运行状态的进程,都应该被公平的调度。而所谓的优先级,也只是决定了优先级高的进程被优先调度,而非一直调度,也就是说,优先级只是决定了一轮调度的顺序,不能决定一轮调度可以比别的进程多调度几次。
这就好比,一个老师在课后让学校来到他的办公室问问题,由于有问题的同学太多,所以老师决定,按照座号的顺序,让学生进入办公室提问题,当学生的问题解决后,那么就要让下一个学生进入办公室,即使该学生还有其他问题没解决,也要排队等到其他学生的问题解决,进入下一轮排列时,才能继续提出问题,而不是让座号靠前的同学一直提问。
所以linux的运行队列不是一个,而是两个,一个称之为active queue,即活跃队列,另一个称为expired queue,即过期队列,其中,active queue中的进程都是这一轮中没有调度过的进程,expired queue中的进程,都是这一轮被调度过的进程。cpu只会调用active queue中的进程,当该进程调度结束后,就会被挂在expired queue中。
调度的优先级,以及进程在队列中位置,与前面所述的规则一致。
以此类推,这样无论进程的优先级高低,都能在一轮调度中,被公平的调度,如果进程在调度过程中运行结束,此时该进程处于结束状态,而非运行状态,所以不会被挂入expired queue当中,而是被操作系统进行进程结束时的处理。
当active queue中的所有进程都被调度结束时,说明该轮调度中的所有进程都被公平的调度了一次,下一轮要被调度的进程则全都处于expired queue中,而且优先级不变(即在active queue中的进程如果是pri 61,在expired queue中的位置也是pri 61,进程的优先级不变)。此时操作系统会将expired queue变成active queue,而active queue则会变成expired queue。cpu进行下一轮的调度