Linux:进程调度
- 进程优先级
- 查看优先级
- 调整优先级
- Linux 2.6 内核进程调度队列
进程优先级
查看优先级
在Linux中,进程是有优先级的,我们可以通过指令ps -la
来查看:
其中PRI
表示priority
优先级,在Linux中,优先级的范围是[60, 99]
共40个级别。
其中80
是优先级的默认值,PRI
的值越小,进程的优先级就越高。
Linux之所以把优先级控制在40个,是为了防止有的进程把自己的优先级调的很高,导致一直占用CPU
,其他进程得不到资源。这个问题叫做进程饥饿
问题。
调整优先级
那么我们要如何调整一个进程的优先级呢?
在刚刚的图示中,在PRI
后面还有一个项目,NI
,其代表nice
值。进程优先级PRI
与NI
满足以下公式:
P R I = 80 + N I PRI = 80 + NI PRI=80+NI
所以我们想要调整进程的优先级PRI
,就是要调整nice
值。
现在在test.exe
可执行程序中执行以下代码:
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h> int main()
{ printf("I am a process, pid = %d\n", getpid()); while(true) {} return 0;
}
该进程输出自己的PID
后,就处于一个死循环状态。
输出结果:
此时进程一直死循环,我们通过ps -la
观察一下:
可以看到,现在多了一个PID=23432
的进程,也就是我们执行的test.exe
,其PRI
值为默认值80
,现在我们开始修改该进程的nice
:
- 执行
top
指令,进入资源管理器:
你会进入如下页面:
- 按下
r
键:
r
代表你想要改变某个进程的nice
,此时白色行上面会出现一个光标,提示你输入想要改变进程的PID
:
3. 输入PID
:
输入PID
后,现在你就可以改变nice
值了:
- 输入修改后的
NI
值:
此处我把nice
值设为10
,按下回车。
- 最后按下
q
退出管理器
现在我们通过ps -la
观察一下:
test.exe
的NI
变成了10
,而PRI
变成了80 + NI = 90
了。
Linux 2.6 内核进程调度队列
现在我们再来看看,进程是如何被调度的,以及优先级是如何起作用的。
此处我以Linux 2.6
内核的进程调度队列为例。
在CPU
中,存在大量的寄存器,比如以下常见的寄存器:
eax/ebx/ecx/edx
:用于存储临时数据,比如函数返回值
eds/ecs/fg/gs
:段寄存器,区分代码与数据
eip
:也就是pc指针,指明当前代码执行到那个位置
浮点数寄存器
:浮点数运算
ebp/esp
:构建栈区
程序在运行的时候,会存储大量的数据,比如当前执行到哪一行代码,上一个语句运算的结果是什么,函数调用到第几层了,等等。这些数据都存储在CPU
的寄存器中。CPU
中存储的所有临时数据,叫做硬件上下文
。
当一个进程从CPU
中离开,会把所有的硬件上下文都拷贝走,存储在PCB
中,这个过程叫做保护上下文
。
而当一个进程被再次调度的时候,又会把自己的数据写入CPU
中,覆盖原始的寄存器中的数据,这个过程叫做恢复上下文
。
那么我们再来看看运行队列是如何调度进程的
Linux的运行队列run_queue
视图如下:
我们先看到蓝色区域:
其中nr_active
表示当前run_queue
总共含有几个进程
queue[140]
就是运行队列的主体,我们将其划分为两个区间[0, 99]
这段区间暂不讲解,[100, 139]
这个区间用于放正在排队的进程的PCB
.
这个区间刚好有40个元素,而我们的优先级也刚好有40个,也就是说一个优先级的进程对应一个下标。
其实这个数组本质是一个指针数组,类型是task_struct*
,即指向PCB
类型的指针,每个指针指向一个链表,链表内存储着所有该优先级的进程的PCB
比如优先级为80的进程,都存储在queue[120]
指向的链表中
当运行队列调度进程的时候,从下标100开始,也就是从高优先级开始调度,然后遍历这个链表,把高优先级的链表执行完,再去执行下一个优先级的链表的进程。
bitmap
是一个五个元素的int
数组,其是一个位图,一个int有32字节,5×32=160,比140大一些。
其用一个位图来表示某个下标对应链表有没有进程,有就是1,没有就是0。此时就可以通过遍历位图,来快速判断一个queue
的某个下标位置有没有进程,进而得出要不要去遍历该下标的链表。
那么现在有一个问题,那就是刚刚提到的进程饥饿问题:
假设现在CPU
正在执行下标为110的链表的进程,也就是优先级为70的进程,此时刚好又有很多优先级为70的进程进来了,结果CPU一直在执行这个优先级的进程。最后70优先级以后的进程,一直拿不到CPU资源,导致进程饥饿问题。
为了解决该问题,其实run_queue
中有两个运行队列,在上图中你会发现,红色区域和蓝色区域的是一模一样的:
这就是两个相同的队列,一个叫做活跃进程队列
,一个叫做过期进程队列
。
active
指针指向的队列就是活跃进程队列,expired
的指针指向的队列就是过期进程队列
CPU
只执行活跃进程队列中的进程,而新来的进程进入过期队列,此时新来的进程就不会影响正在执行的进程,解决了进程饥饿的问题。
当CPU把活跃进程队列的进程执行完后,此时expired
与active
指针进行交换,那么活跃进程与过期进程就交换了,此时CPU
就去执行新的活跃进程队列。
这样的两个队列轮流执行,一个负责执行,一个负责接受新进程的结构,就解决了进程的饥饿问题