Linux:进程(四)

目录

一、进程优先级

二、Linux调度与切换

1.背景

2.进程切换

3.Linux调度


一、进程优先级

背景:在计算机中,软硬件资源是有限的,而进程想要访问某一种资源,就得通过排队来保证访问资源的过程是有条不紊的。


Linux下对优先级的定义。执行命令ps -la得到以下打印结果。

cpp 复制代码
[euto@VM-4-13-centos 24921]$ ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001 29067 28781  0  80   0 -  1054 hrtime pts/1    00:00:00 myprocess
1 S  1001 29068 29067  0  80   0 -  1054 hrtime pts/1    00:00:00 myprocess
0 R  1001 29078 28979  0  80   0 - 38332 -      pts/2    00:00:00 ps
  • UID:代表执行者的身份
  • PRI:priority的缩写,代表这个进程的优先级,Linux下优先级就是一个整型变量,默认值为80,取值范围为,值越小,优先级越高。
  • NI:nice的缩写,用来代表相对默认值的增量

Linux下进程的优先级可以人为手动修改,但是需要我们手动修改优先级的场景几乎没有。


下面演示如何修改优先级,首先可执行程序myprocess已经被运行了起来,执行ps -la查看。

cpp 复制代码
[euto@VM-4-13-centos 24921]$ ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001  2310 28781  0  80   0 -  1054 hrtime pts/1    00:00:00 myprocess
0 R  1001  2330 28979  0  80   0 - 38332 -      pts/2    00:00:00 ps

执行top命令进入任务管理器。

进入任务管理器后,输入r

得到一行提示,大致意思是"输入要重新设置nice值的PID"。

cpp 复制代码
PID to renice [default pid = 5375]

当前要修改优先级的程序myprocess的PID为2310,于是输入2310。

弹出一行提示,大致意思是"输入新的nice值",我们暂时输入10。

cpp 复制代码
Renice PID 2310 to value 

退出任务管理器后,再执行ps -la命令查看。

cpp 复制代码
[euto@VM-4-13-centos 24921]$ ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001  2310 28781  0  90  10 -  1054 hrtime pts/1    00:00:00 myprocess
0 R  1001  7772 28979  0  80   0 - 38332 -      pts/2    00:00:00 ps

我们发现myprocess的PRI由80变成了90,而NI由0变成了10

因此,可以总结出来,Linux下修改优先级不能直接修改,而是通过加减某一个量来修正

NI值是用来表示当前PRI值相对默认PRI值的增量,由于PRI的范围是,默认PRI值是80,故NI值的取值范围是

PRI = 默认PRI(80)+ NI。


  • NI可以取范围外的值吗,如果可以,PRI会被修改为多少?

在top中修改NI值的时候,输入100。

执行ps -la查看结果。

cpp 复制代码
[euto@VM-4-13-centos 24921]$ ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001  2310 28781  0  99  19 -  1054 hrtime pts/1    00:00:00 myprocess
0 R  1001 13253 28979  0  80   0 - 38332 -      pts/2    00:00:00 ps

可以总结出来,修改的NI值会被操作系统做检测,如果超出范围则修正到范围之内

在上面结果的基础上,将NI值设置为-20,预期结果应该是99-20=79。

执行结果如下。

cpp 复制代码
[euto@VM-4-13-centos ~]$ ps -la
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1001 16994 16006  0  60 -20 -  1054 hrtime pts/1    00:00:00 myprocess
0 R  1001 18523 16091  0  80   0 - 38332 -      pts/2    00:00:00 ps

这里我中断进程后,重新启动了程序,所以PID发生了变化,但是前提条件是一样的,不难看出来,NI值变成了-20,说明NI值是覆盖原来的值,但是PRI并不是我们预期的79,而是60。原因是每次调整优先级的NI值都是在默认的PRI值80上做加减


  • 为什么要让优先级的修改受限?

操作系统中,存在很多必要的常规进程,如果进程的优先级可以无限大,必然会造成多数用户进程的优先级常规进程的优先级高,导致常规进程获取资源的优先度下降,造成系统卡顿。

把一个进程获取资源优先度不高的情况称为进程饥饿

二、Linux调度与切换

1.背景

  • CPU在执行一个进程的时候,是直接一次性把代码跑完吗?

不是,现代操作系统设计的CPU执行代码,都是基于时间片轮转执行的。假设时间片大小是1ms,那么一个进程在CPU上执行1ms后,CPU会马上开始执行另外一个进程。

  1. 竞争性:系统进程数目众多,而CPU资源少量,甚至只有一个,所以进程之间是具有竞争性的,为了高效完成任务,合理竞争相关资源,便有了优先级。而为了保证优先级,便设计了调度器基于时间片轮转执行每一个进程
  2. 独立性:进程之间具备独立性,多个进程运行期间互不影响,这种不影响指的是一个进程不影响另一个进程的执行。
  3. 并行:多个进程在多个CPU下,同时运行,称为并行。
  4. 并发:多个进程在一个CPU下,通过高频进程切换的方式,让多个进程都得以推进运行,称为并发。

如今多数人的个人电脑都只有一个CPU,因此多个进程之间是并发运行,如果把时间精确到CPU的一个时间单元上,那么CPU在这个时间单元上执行的指令是确定的只有一条,而用户感知到的是我们的电脑可以打游戏的同时听音乐,原因就在于我们的CPU非常快!!!(多核其实就是CPU内部有一个控制器,多个运算器)

2.进程切换

CPU有一个运行队列,CPU要轮转切换多个进程。

  • CPU内部有大量寄存器,这些寄存器的种类不一。
  • 进程A正在CPU上被执行时,该时间片内,寄存器上会产生许多临时数据,这些临时数据是和当前的进程A相关的,这些大量临时数据称为进程的硬件上下文
  • 时间片结束,CPU要执行下一个进程B,在此之前,要把寄存器的数据拷贝到进程A的PCB内部(也有部分数据拷贝到了其他地方),这个过程称为保护上下文
  • CPU开始执行进程B,那么就有两种情况,如果进程B是首次被调度,那么进程B直接开始执行,在执行期间所产生大量临时数据直接覆盖寄存器原来的数据。如果进程B是非首次被调度,那么进程B会先把PCB保存的数据恢复到寄存器中,然后开始执行。这个过程称为恢复上下文。

CPU的寄存器只有一套,但是寄存器要处理的数据有多套,这些大量临时数据不属于寄存器,而是属于进程!!!

3.Linux调度

在操作系统理论部分,大部分人了解到操作系统对进程的调度都是类似FIFO的处理方法。下面以Linux系统的调度作详细说明。

概括性的总结一句话就是,Linux实现的调度算法,考虑了进程的优先级,考虑了进程饥饿问题,考虑了效率。

上面这张图是Linux系统中对CPU运行队列的实现,其中,有两个定义一模一样的结构,大致结构内容如下。

cpp 复制代码
struct Qq
{
    int nr_active;
    int bitmap[5];
    struct task_struct* queue[140];
}

先来介绍queue,这是一个PCB指针数组,一共有140个地址,但是【0,99】是用来给实时操作系统调度的,而【100,139】一共40个地址刚好对应进程的40个优先级,是给分时操作系统调度用的。


这里要简单介绍一下调度上对操作系统的分类。

调度上把操作系统分为实时操作系统和分时操作系统两种,区别仅仅在于CPU调度的时候。正常情况下,计算机只有一个CPU,CPU的执行都是轮转时间片,这是分时操作系统。但是,在有些特殊场景下,不需要轮转这个动作,CPU执行进程必须是实时、立刻、马上,因此Linux也考虑了实时操作系统的设计而定义了【0,99】这100个空间。


【100,139】对应40个优先级。

这里的设计采用了类似哈希桶的设计,优先级相同的进程会被链接在一起,如下所示。


那么,CPU在轮转进程的时候,是从优先级为60的位置一个个向后遍历吗?

不是的,为了解决遍历的低效率,便设计了bitmap[5],这是一个整型数组,利用了位图这种数据结构的思想,STL位图,用比特位来表示是与否的两种状态,一个整型类型有32个比特位,5个整型就是有160个比特位。

queue的一个位置就映射着bitmap的一个比特位,如果比特位为0,表示对应数组的某一个位置没有进程,如果比特位为1,表示对应数组的某个位置有进程。


nr_active用来表示queue中有多少个位置是有进程的。


Linux设计了这样的一种结构后,发现解决了进程优先级和效率问题,但是进程饥饿问题还没有解决。因此,为了解决进程饥饿问题,Linux把这种结构再复制了一份,并且其中一份叫活跃进程队列,一份叫过期进程队列。


进程在被执行的时候,CPU在一个时间片内轮转一个进程,轮转结束后,把这个进程放在过期进程队列里,于是,活跃进程队列里面的进程数量不断减少,过期进程队列里面的数量不断增加。

CPU只在活跃进程队列里面执行进程,一个时间片轮转一个进程,在CPU轮转活跃进程队列的时候,如果有一个进程优先级设置的更高需要被执行,那么可能造成进程饥饿问题。因此,当CPU轮转进程的时候,操作系统又把其他进程放进来时,是放在了过期进程队列里面,不影响CPU轮转当前进程,这就解决了进程饥饿问题。


那么,直到活跃进程队列里面的进程全部被执行完,此时过期进程队列里面有着许许多多需要被CPU执行的进程。

Linux又设计了两个指针,这两个指针的内容如下。

cpp 复制代码
struct Qq array[2];//结构体数组,内有两个进程队列
struct Qq* active = array[0];
struct Qq* expired = array[1];

array是结构体数组,用来存放Linux设计的两个一模一样的结构体。而这两个指针分别指向array数组的两个元素。

本质上,active指向活跃队列,expired指向过期队列,当活跃队列为空,此时过期队列"满满当当",于是active就和expired交换指针内容

结果就是,CPU执行时只会去轮转active指向的队列,因为当active指向的队列为空的时候,操作系统会让它去指向另外一个"满满当当"的队列。

Linux调度算法的设计,完美解决了CPU轮转进程的不足!!!

相关推荐
heater4044 分钟前
【Linux】驱动的基本架构和编译
linux·driver
一名技术极客10 分钟前
Linux安装go-fastdfs
linux·运维·golang
小阿轩yx12 分钟前
小阿轩yx-Ansible部署与应用基础
linux·云计算·ansible·运维开发·云平台管理·部署应用
Alone804615 分钟前
ansible远程自动化运维、常用模块详解
运维·自动化·ansible
铁板鱿鱼14025 分钟前
docker操作的基本命令加容器的基本命令(仅供自己参考)
运维·docker·容器
会洗碗的CV工程师30 分钟前
828华为云征文——使用Flexus云服务器X实例CentOS镜像下创建MySQL服务器教程
运维·服务器·mysql·centos·华为云
华为云PaaS服务小智33 分钟前
华为云发布全栈可观测平台AOM,以AI赋能应用运维可观测
运维·人工智能·华为云
PatrickYao04221 小时前
[Simpfun游戏云1]搭建MC Java+基岩互通生存游戏服务器
服务器·游戏
谌vv1 小时前
.NET IIS发布项目后设置虚拟路径访问文件 404
服务器·.net
qq_407151572 小时前
linux如何对c++进行内存分析
java·linux·jvm