【Linux】13:进程优先级和进程的切换与调度

目录

一、优先级的基本概念

1.1 什么是优先级

1.2 为什么要有优先级

二、如何查看并且修改进程优先级

2.1 PRI

2.2 NI

2.3 优先级极限测试

2.4 UID

2.4.1 uid概念

2.4.2 完整流程:你怎么操作资源

2.4.3 特殊情况:进程 UID 会变化(提权场景)

2.4.4 总结

三、进程切换

四、进程调度

五、总结


一、优先级的基本概念

1.1 什么是优先级

在日常生活中,我们有很多排队的场景公路上等红灯在医院挂号或者是在食堂吃饭等等,但是我们在公路上救护车可以无视红灯医院里急诊病人可以优先挂号,以及食堂里老师肆意的插队…

这些需要排队的事物,就可以看作进程,而像救护车急诊病人,以及食堂的老师等等,这些都是具有优先权的象征,为什么要有优先权呢?一定是有更重要的事情去做。

所以,此时就可以延申到进程 ---- 的优先级是什么?

进程要访问某种资源,进程通过一定的方式(排队),确认享受资源的先后顺序。

相信细心的你也发现了,优先级不就是我们前面学习的 ---- 权限吗?

其实优先级权限是有区别的:权限决定的是 --- 能不能的问题,而优先级是决定 --- 先后顺序的问题

1.2 为什么要有优先级

如果我们食堂的窗口远大于人数,如果我们自习室里的座位非常多,人却非常少,我们是不需要排队的,也就是说,当资源充足的时候,我们并不需要排队

所以优先级产生的本质是因为 ----- 【资源相对比较少】

所以呢,优先权高的进程有优先执行权利,有了优先级;才能使得一些重要的进程被率先执行

二、如何查看并且修改进程优先级

知道了进程优先级的基本概念后,我们来Linux中看看它到底是个什么东西呢?

我们可以对运行起来的进程使用下列指令去查看其优先级

ps -al

此处我们要关注的是以下几个重要信息:

  • UID: 代表执行者的身份
  • PID :代表这个进程的代号
  • PPID:代表这个进程是由哪个进程发展衍生而来的,亦即父进程的代号
  • PRI:代表这个进程可被执行的优先级,其值越小越早被执行
  • NI:代表这个进程的nice值

那我们重点来讲解一下这两个信息值

对于 PRI 还是比较好理解的,即 进程的优先级,或者通俗点说就是程序被CPU执行的先后顺序,此值越小进程的优先级别越高
那 NI 呢? 就是我们所要说的 nice 值了,其表示进程可被执行的优先级的修通俗简单一点来讲呢,就是进程的 nice 值不是进程的优先级,他们不是一个概念,但是进程nice值会影响到进程的优先级变化。

2.1 PRI

测试代码如下:

#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main() { while(1) { printf("我是一个进程,我的pid为:%d\n",getpid()); sleep(1); } return 0; }

编译运行后,我们使用ps -al来查看进程信息。如下所示:

红色框框里面的部分就是我们的进程和优先级。PRI 就是 priority ,优先权的意思

而我们运行自己写的程序,查询改进程的权限为80,这也就说明Linux下进程的优先级本质就是数字

其实Linux下优先级是可以被修改的,修改范围为[60, 99]这40范围内,且进程的默认权限值是80而优先级 数字越小,表示该进程的 优先级越高!

2.2 NI

我们清楚了进程的优先级是什么,以及为什么,接下来我们看一下到底该怎么做?

1:运行上面的程序,打印出进程的pid,使用top命令进入到Linux任务管理器

top#进入任务管理器

如下所示:

2:接下去按下R/r之后,就会跳出来下面这句话,这里的renice指的就是要重新修改进程,此时shell正在等待我们输入进程的PID值,那我们就可以输入上方的 304248

3:然后呢,我们看到 shell 又在等待我们输入需要更改的nice

4:输入要修改的值即可,比如,这里我输入10:

这个时候再使用ps查看,就会发现进程的PRI这一项变为了90,但是我们发现,不仅仅是PRI这一栏变了,还有一栏叫做NI的值也变了,而且还刚刚好是我们输入的数值!

其实,Linux系统支持用户调整优先级,但并 不是让用户直接修改 pri 值,而是修改 nice 值。而 nice 值不是优先级,而是优先级的 修正数据

  • 所以真正的优先级应该是:pri = pri(old) + nice ;old指老的优先级
  • 所以我们刚刚修改的并不是优先级,而是nice值

2.3 优先级极限测试

进程的优先级真的只能在[60,99]这个范围呢移动吗?如果不相信,我们可以来做一下极值测试:

我们将程序的进程值调到尽可能的大

  • nice值变为19,pri变为99,就是我们给出的最大范围
  • 如果我们把nice值设置为-10

我们发现,进程的pri变为了70,但是我们刚才不是刚把进程的优先级调至为99吗?

其实这是因为:pri(old),是指老的优先级,这个优先级永远是80!所以再次调整时,还是从80开始变化。

我们把nice值设置为极小:

进程的pri果然为60,所以nice值的范围就是 [-20, 19]

可能你会有疑问,为什么要给进程优先级加上限制范围呢?

  1. 如果不加限制,把自己的优先级调整的非常高,别人的优先级非常低,又因为OS是根据进程的pri来执行先后顺序的,这样做可能就会打乱操作系统执行顺序。
  2. 这样常规进程就很难分配到资源,其实这就是一种 进程饥饿 的问题。

为了尽量避免进程饥饿的问题,现在的任何分时操作系统,都是较为公平的进行调度。

2.4 UID

2.4.1 uid概念

UID同样是一个重要的概念,他表示的是用户的UID,也就是说明这个进程是谁的。

我们可以使用id来查看UID对应的用户是谁。如下所示:

在linux系统中,我,也就是zhang,或者1004,是怎么访问资源的?是我自己来访问嘛?

其实是我们写的进程来访问。

你本机用户zhang,UID=1004,不是你本人直接访问文件 / 网络 / 硬件,是你启动的进程替你访问资源。 操作系统不认用户名zhang,只认数字 UID=1004,所有权限校验全靠 UID/GID。

2.4.2 完整流程:你怎么操作资源

  1. 你登录系统输入账号密码登录,系统读取/etc/passwd,绑定你的身份:用户名zhang↔ UID=1004、GID = 对应组 ID。 登录后你的 shell(bash/zsh)进程,运行身份就是 UID=1004。

  2. 你敲命令 / 运行程序,生成新进程比如你执行cat test.txtvim code.c、浏览器、服务程序: 子进程会继承父进程 (shell) 的 UID、GID,新进程有效身份依旧是 1004。

  3. 进程发起资源访问,内核校验 UID 权限当进程要读文件、创建文件夹、操作端口、读写磁盘时,Linux 内核做权限判断:

  • 文件自带属性:属主 UID、属组 GID、rwx 权限位
  • 进程运行 UID (1004)和文件属主 UID 对比:
    • 匹配:使用文件「所有者权限」
    • 不匹配,再对比 GID;都不匹配则使用其他用户权限
  • 权限不足直接返回Permission denied

举例子: 文件a.txt属性:-rw------- 1 zhang zhang,UID=1004 你运行cat a.txt,进程 UID=1004 → 匹配所有者,可读可写。

如果是 root 进程(UID=0)访问,root 拥有最高权限,不受普通 rwx 限制。

2.4.3 特殊情况:进程 UID 会变化(提权场景)

(1)su id 程序

程序设置 SUID 权限后,运行时进程 UID 会变成文件属主 UID,不是启动者 UID。 例:passwd命令属主是 root (UID=0),带 SUID,普通用户 zhang 运行它,进程临时变成 UID=0,才能修改密码文件/etc/shadow

(2)sudo / su

执行sudo ls /root: 临时切换进程身份为 root (UID=0),进程以 root 权限操作资源;命令结束后身份恢复为 1004。

2.4.4 总结

人(zhang)只是触发程序运行,真正和系统、文件、硬件交互的载体是进程; 进程身上标记着数字 UID(1004),内核依靠这个 UID 判断该进程能不能访问对应资源。 没有进程,单纯的用户账号无法读写任何系统资源。

三、进程切换

Linux 操作系统中进程调度切换是 操作系统核心功能之一,它涉及到如何有效地利用 CPU资源,保证系统的响应速度和吞吐量。

首先介绍4个关键的概念:

  1. 竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
  2. 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰
  3. 并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行
  4. 并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

我们知道 一个CPU在 同一时间只能运行一个进程,而 -- 并发 -- 实际上就是 -- 利用时间片,让每个进程在CPU上只能运行一个时间片的时间,然后就被切换到另一个进程,所以我们计算机虽然看起来似乎是非常流畅的运行每个进程,而实际上则是一卡一卡的运行的,只不过这个时间非常短,我们感觉不到罢了。

进程首次调度完成 被切换走,当 CPU 二次调度该进程时,是如何记得上次执行到哪里了呢?


CPU中存在有大量的寄存器,进程运行产生的临时数据都被保存在这些寄存器中,这些临时数据被称为进程的硬件上下文,当时间片消耗完的时候,进程会保存这些上下文,现阶段大家可以理解为保存到PCB中,当进程被二次调度时,进程会将曾经保存的硬件上下文进行恢复(将之前保存的硬件上下文覆盖到CPU的寄存器中)。

进程切换包括以下几个关键步骤:

  1. 上下文保存: 当操作系统决定要切换到另一个进程时,首先需要保存当前进程的上下文信息,包括程序计数器、寄存器内容、栈指针等。这些信息存储在进程的控制块(PCB)中。
  2. 选择新进程: 在确定要切换到哪个新进程之前,操作系统会根据调度算法从就绪队列中选择一个合适的进程。这个选择可能基于进程的优先级、先到先服务(FIFO)、轮转法等。
  3. 加载新进程的上下文: 一旦确定了新进程,操作系统就会从其对应的PCB中恢复该进程的上下文信息。这包括将新进程的程序计数器值加载到CPU中,以便执行新进程的代码。

总结:

  • 虽然CPU中的寄存器只有一套,但是寄存器内部保存的 数据可以有多套。
  • CPU是被所有进程共享的,但内部的数据却是进程私有的。

大家可以理解为:在任意一个时刻,CPU中 的数据只属于一个进程。所以看起来,我们用的是同一个设备,但实际上进程之间是具有独立性的。

四、进程调度

进程调度是 操作系统根据一定的调度策略从就绪队列中选择下一个要执行的进程的过程。调度策略的选择会影响系统的性能、响应速度和资源利用率。

这是Linux系统下对运行队列的设计。

不考虑其他成员,我们只看圈出来的两个部分:

红色部分为活动队列蓝色部分为过期队列,他们两个你可以认为是完全相同的两个结构

  1. 进程队列数组 queue[140]:这个数组用于存储不同优先级的进程队列。每个队列按照先进先出(FIFO)规则进行排队调度。数组的下标表示进程的优先级,因此可以直接根据优先级来访问对应的进程队列,提高了访问效率。
  2. 进程队列状态位图 bitmap[5]:为了快速判断哪些队列是非空的,使用了一个位图来表示每个队列的状态。每个比特位对应一个队列,如果该队列非空,则对应的比特位为1;否则为0。这样,查找非空队列的操作变得高效,时间复杂度为常数级别。
  3. active指针和expired指针:这两个指针用于指示当前活跃队列和过期队列。随着调度的进行,它们的内容可以交换,从而实现活跃队列和过期队列的动态切换。
  4. 活跃队列 和 过期队列:活跃队列中包含当前活跃的进程,而过期队列包含一段时间内未被调度的进程。Linux 内核根据需要从活跃队列和过期队列中选择进程进行调度,以平衡优先级和资源利用效率。
  5. O(1) 调度算法: Linux 内核的调度器通常采用 O(1) 调度算法(使用了位图(bitmap)来实现),该算法在常数时间内选择下一个要执行的进程,而不受进程数量的影响。这确保了调度器的高效性,使得系统在任何负载情况下都能快速响应。

什么是--- 活动队列 ?

  1. 时间片还没有结束的所有进程都按照优先级放在该队列
  2. nr_active: 总共有多少个运行状态的进程
  3. queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!
  4. bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率
  5. 总结:活跃队列 ---- 表示当前 CPU正在执行的运行队列,而 正在执行的运行队列(也就是活跃队列)是不可以增加新的进程的。

什么是 ---过期队列

  1. 过期队列 和 活动队列 --- 结构一模一样
  2. 过期队列上放置的进程,都是时间片耗尽的进程、
  3. 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

总结:

  • 某个处在活跃队列中的进程的时间片消耗完,该进程就会从活跃队列中剥离,然后被添加到过期队列。
  • 当活跃队列正在执行时如果有进程需要添加进运行队列,那么就会添加至过期队列当中也就是说 活跃队列的进程一直在减少,而过期队列中的进程一直在增多!

activeexpired结构体指针 有什么用?

  • 它们分别指向活跃队列过期队列,而活跃队列与过期队列由于属性完全相同,于是被放在了一个叫做prio_arry_t[2]的数组里,prio_arry_t[0]指向活跃队列,prio_arry_t[1]指向过期队列
  • 当活跃队列被CPU执行完毕后,我们只需要交换两个指针的内容即可,这样仅仅是指向的内容变了,活跃队列变为过期队列,过期队列变活跃队列,并且时间复杂度为 O(1)

五、总结

  • 进程优先级资源有限催生优先级,区分执行先后;权限管 “能不能访问”,优先级管 “谁先运行”。 PRI 真实优先级 60~99,默认 80,数值越小越优先;nice 是修正值,范围 - 20~19,PRI=80+nice,只能改 nice,限制区间防止进程饥饿。top 命令 r 可修改 nice。

  • UID 用户标识UID 是用户数字身份,内核凭它校验文件权限;root (UID=0) 权限不受普通 rwx 约束。SUID 程序、sudo 能临时提升进程 UID。

  • 进程基础特性竞争:抢 CPU;独立:资源隔离;并行:多 CPU 同时跑;并发:单 CPU 分时切换。

  • 进程切换(上下文切换)CPU 寄存器存进程上下文,切走时存入 PCB,重新调度时恢复;一套寄存器,多套进程私有上下文。

  • Linux O (1) 调度active 活跃队列:时间片未用完进程;expired 过期队列:时间片耗尽、新建进程。 内部 queue [140] 按优先级排队,bitmap 快速找非空队列。active 耗尽直接交换双队列指针,调度耗时恒定 O (1)。