目录
一、进程优先级
二、优先级与权限的区别
三、优先级的查看
四、进程优先级修改
五、进程切换
六、linux2.6内核调度队列与调度原理
一、进程优先级
首先我们得知道一个进程总是需要排队的,他一会在运行队列中排队等待运行,一会在设备的等待队列中排队,排队的本质就是在确认优先级。
cpu资源分配的先后顺序,就是指进程的优先权(priority)。
二、优先级与权限的区别
我们很容易把优先级和权限搞混淆。
优先级指得到申请资源的先后。
权限为是否能得到资源。
他们之间是先有权限,再谈优先级。
三、优先级的查看
进程的优先级其实就是PCB中的一个int字段,数值越小,优先级越大。这跟我们生活中买奶茶排号、医院挂号是一个道理,先叫的都是小号,先享受服务,后来的都是大号,晚一点享受服务。
Linux进程的优先级数据范围:60-99
默认进程优先级:80
我们可以在命令行输入以下指令查看进程的优先级
ps -al
其中我们这里只需要关心 PRI 与 NI 这两项
- PRI(priority):进程优先级
- NI(nice):进程优先级的修正数据
这里的NI即nice值,是Linux进程pcb中存放的一个值,他与PRI的公式如下
pri(新) = pri(老) + nice
这里的老pri不是指上次,而是值默认的pri,一般都是80
四、进程优先级修改
首先在命令行先输入 top 启动任务管理器,再在任务管理器中输入 r 打开重新调整renice值的任务框,此时需要先输入需要调整的进程的pid值。
将我们运行的死循环进程renice值修改为10
此时nice值变为了10,同时PRI值变成了90。
这样我们就完成了优先级的修改,数字变大了,也就是将优先级变低了。
这里我们尝试将优先级调低试一试
发现告诉我们修改失败,不能将优先级调低。此时需要使用 sudo 提权,如下
后面重复之前的操作,对优先级-10 ,此时优先级就变成了70。这里优先级不是从之前的90去-10,而是使用的默认的80 -10。
我们再次修改,让进程的优先级的nice值改为-100。发现进程优先级为60,nice值为-20,这是因为优先级的范围在60-99.系统不让你修改超过这个范围,如果超过了,就取最小值。
小总结:nice值调整的最小为-20,超过部分统一处理为-20
nice值调整的最大为19,超过部分统一处理为19
这样处理是为了让操作系统进行调度的时候,可以让每一个进程都得到调度。否则如果用户随意更改优先级,会导致进程优先级过高,并且还可以一直调,刚运行完如果又调高,会让这个程序一直在运行,致使其他进程无法运行,进而优先级较低的进程长时间得不到CPU的调度,这也叫做进程饥饿。
五、进程切换
在学习进程切换前,我们得先了解以下概念
- 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
- 独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
- 并行: 多个进程在多个CPU下分别,同时进行运行,这称之为并行
- 并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发
小总结一下,进程之间具有竞争性(因为成本控制,导致资源少,需要竞争)和独立性 (该进程在运行时,该进程需要的资源只为他服务)。
并行是多个CPU分别运行
并发是一个CPU通过进程切换,轮流让进程运行。
一般的用户都是用的单CPU,因此这里我们着重学习并发。
首先,我们需知道每一个进程不是占有CPU一直运行的,每个一段时间(时间片),需要从CPU上剥离下来。不然你写一个死循环,一直运行该进程,其他进程还活不活了。Linux内核支持进程之间进行cpu资源抢占的。
同时cpu的速度是非常快的,比如说我们有10个进程,现在的时间片是1微秒,那么在一秒的时间,他可以将这是个进程轮流运行十万次,因此我们人的感知,是几乎感知不到的,就感觉这么多的进程都在同时运行,这样基于时间片的轮转式抢占内核就可以称作并发。
而并发,必须要考虑进程间的切换。
cpu中有很多寄存器,比如eax,ebx,ss,ds,cs等等。通过这些寄存器,可以让数据进行暂时存储,同时寄存器的速度也是非常快的,在cpu内部快速的传递数据。
比如我们写了一个函数,返回类型为int,这时就会使用eax寄存器来保存该临时变量的值,你临时变量销毁就销毁了,无所谓,我寄存器在帮你擦屁股勒,最后给调用该函数的地方。如下代码,我们查看汇编就可以看到使用了eax寄存器。
再一个就是我们的进程内的代码需要不停的在函数之间跳转,此时也会用到pc或者eip来保存下一条指令的地址。
当我们进程在运行的时候,会使用这些寄存器的,我们的进程会产生各种数据,在寄存器中临时保存,如果有多个进程各个进程在cpu寄存器中形成的临时数据(进程硬件上下文),都是不一样的。
而cpu寄存器硬件只有一套,而继承上下文数据有很多,是 1 对 n 的关系,因此当时间片到达,该切换下一个进程的时候,该进程的硬件上下文需要被保存到内存中,当我们下次再次运行该进程时,能够接着上次运行到的地方继续运行。
这个操作应该不难理解,这就相当于存档,我们玩幻兽帕鲁的时候,右上角会经常告诉我们存档中,这就是怕我们突然断网或掉线,如果不存档,就无法恢复到之前玩到的地方,我抓到的腾炎龙和阿努比斯没了,游戏体验就会大幅降低。
小总结:进程切换前首先工作要先将进程的硬件上下文保存到内存,进程重新运行前,需要将存放在内存中的进程硬件上下文重新写入到寄存器中,另外需要注意上下文保存的是寄存器的内容,而不是寄存器本身。
六、linux2.6内核调度队列与调度原理
如下是linux的运行队列具体内容
其中我们主要看蓝色框和红色框里的内容,其中queue具体为task struct *queue[140],是优先级指针数组,前面0-99是系统用的我们不关心,后面的100-139,对应了之前我们学习的60-99的优先级,操作系统将优先级相同的进程地址链入了对应的地方。如下所示
- 上图中有红色和蓝色方框,为什么这里有两个方框呢?
他们一个是active指针指向的,另一个是expired指针指向的。红色和蓝色方框内容相同。比如现在Linux启动了,目前有了10个进程,这10个进程都在active指向的内容中,他们在轮流运行,如果我现在添加了一些进程进去,被添加的进程不会被active指向,而是被expired指针指向,他要将active这些内容全部运行完了,才会再运行expired的内容。
- 为什么会这样呢?
还是老样子,因为操作系统需要保证让每一个进程都能够被调度,如果我现在运行到80优先级的进程了,现在添加了一些60优先级的进程,如果只有一个数组,那么我就得返回运行这些优先级更高的进程,如果这些进程有很多,那么后面90优先级或者更低优先级进程会一直无法被调度。
但是这样也不合理啊,如果运行expired指向的进程时,有进程被添加进来,怎么办呢?此时需要往active添加进程呀!由于两个指针指向的内容都是一样的,我们运行完active里的所有进程后,可以通过swap(active,expired)来进行交换,这样就解决问题了。
方框内不止有数组,还有nr_active与bitmap[5]。
nr_active是一个整形,是否为0来判断指针数组里是否有进程。
bitmap[5]是位图(C++实现位图 ),是int整形数组,一个int整形有32位,5个就有160位,足够140位的优先级访问。比如queue数组中下标120有进程,那么在bitmap中的121位会设置为1,借此来告诉操作系统这里有进程。虽然这样也需要循环遍历,但是我们可以以8位或者16位的速度进行是否为0判断,大大提高效率(毕竟进程有默认优先级,需要调优先级的进程较少)。