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轮转进程的不足!!!

相关推荐
sunz_dragon4 分钟前
Claude Code / Codex Git 版本管理完整使用指南
服务器·人工智能
领尚6 分钟前
openclaw 极简安装(Ubuntu 24.04 server)
linux·运维·ubuntu
Gofarlic_OMS20 分钟前
Windchill的license合规使用报告自动化生成与审计追踪系统
大数据·运维·人工智能·云原生·自动化·云计算
迷途之人不知返24 分钟前
shell相关知识与Linux权限
linux
SPC的存折28 分钟前
3、主从复制实现同步数据过滤
linux·运维·服务器
SPC的存折30 分钟前
openEuler 24.03 MariaDB Galera 集群部署指南(cz)
linux·运维·服务器·数据库·mysql
xcbrand32 分钟前
文旅行业品牌策划公司找哪家
大数据·运维·人工智能·python
SPC的存折43 分钟前
MySQL 8.0 分库分表
linux·运维·服务器·数据库·mysql
风吹迎面入袖凉1 小时前
【Redis】Redisson分布式锁原理
java·服务器·开发语言
cyber_两只龙宝1 小时前
【Oracle】Oracle之DQL中WHERE限制条件查询
linux·运维·数据库·云原生·oracle