Linux进程队列的原理

进程优先级

1.进程优先级是什么

对比优先级<->权限

  • 权限:是能不能的问题;
  • 优先级:已经能了,先后的问题。

优先级:进程在已经能得到某种资源的情况下,得到资源的先后顺序。

2.为什么要有

资源不足,需要分配资源,就要设置优先级:决定进程,获得资源的先后顺序。

3.Linux下,怎么设计的?

查看系统进程 :命令ps -l

  • UID:对应此进程对应的用户编号,不同用户都有各自的编号;
  • PRI :代表此进程可被执行的优先级,值越小越早被执行;
  • NI :一个进程的nice值,优先级的修正数值。当nice值为负数,进程优先级将会变高,越快被进行。

所以,在Linux下,调整进程优先级就是调整进程的nice值,nice的范围是-20~19,一共40个级别。

通过top命令,按r进入修改指定进程的nice值;将某进程nice值改变后观察:

优先级不能频繁更改

注意 :通过nice值改动的PRI始终是PRI默认值(80)-nice值。比如当一开始对一个进程的nice值赋10,PRI变为90;第二次改nice赋值为-10,PRI变为70,而不是变回80。

4.nice范围

在修改某进程nice值的过程中,无论赋任何值,最终只能是在-20~19这个范围内;那么进程PRI范围只能是60~99

5.为什么优先级变化范围有限?

常见的操作系统Linux、windows等,都是分时操作系统

分时操作系统:给进程分配时间片,相对公平、公正的调度策略,较为均衡地让不同的进程,都能得到CPU的资源。

所以:改变优先级就不能改变得太狠,就要控制范围。

补充:实时操作系统

与分时操作系统相对。

只有执行完一个进程后,才会去执行下一个进程,在工业运用中比较多。

6.其他修改优先级的命令

1)nice

nice -n <调整值> <命令/程序>

  • <调整值>:是在默认值 0 的基础上加减,比如 -n 5 就是把 nice 值设为 0+5=5;
  • 普通用户:只能把 nice 值调大(降低优先级),范围 0~19;
  • root 用户:可以调小(提高优先级),范围 -20~19.

注意:当运行完nice这个命令时,会自动启动对应的进程/程序。

复制代码
# 示例1:降低优先级启动程序(普通用户也能做)
nice -n 10 ./main

# 示例2:提高优先级启动程序(需要root权限)
sudo nice -n -5 ./cpu_intensive_program

# 示例3:查看当前nice默认值
nice

2)renice

renice <调整值> -p <PID>

  • <调整值>:指定最终的 nice 值;
  • -p <PID>:目标进程的 PID;
  • 权限规则和 nice 一样:普通用户只能调大(降优先级),root 可以任意调。

注意:修改的是已经在运行的进程的nice值。

复制代码
# 示例1:降低某个进程的优先级(普通用户)
renice 15 -p 12345

# 示例2:提高某个进程的优先级(需要root)
sudo renice -5 -p 12345

7.补充概念

1)竞争性:CPU资源必然有限,进程必然众多,所以进程之间必然有竞争;

2)独立性 :多进程运行,需要独享各自的资源,进程间互不干扰;父子进程也有独立性!

3)并行 :多个进程在多个CPU下分别,同时进行运行,这称之为并行;(少见)

4)并发 :多个进程在⼀个CPU下采用进程切换 的方式,在⼀段时间之内,让多个进程都得以推进,称

之为并发。

进程切换

进程上下文

1)作用进程切换 :保存旧进程上下文 → 加载新进程上下文,保证进程暂停后再恢复,能接着原来位置继续跑,不乱序、不丢数据。

2)包含三部分(了解):

  1. 用户级上下文:程序代码、数据、栈、全局变量、用户寄存器
  2. 寄存器上下文 (最核心):程序计数器 PC、栈指针、通用寄存器、状态字
  3. 系统级上下文:进程 PCB、内核栈、页表、内存映射、打开文件等内核信息

切换

因为并发 特点,当进程进行切换,因寄存器是共享的,A进程运行时,将A的上下文存进寄存器中,当需要中途离开系统进行下一个B进程时,A进程将寄存器里的上下文剥离存到A自己的task_struct中,然后B进程的上下文再存进寄存器中,运行B进程。

(时间片):当代计算机都是分时操作系统,每个进程都有它合适的时间片(其实就是⼀个计数

器)。时间片到达 ,进程就被操作系统从CPU中剥离下来。

组织进程

Linux是由c语言组织的,当创建一个结构体如下,它的地址怎么存的呢?

c 复制代码
struct A {
	int a;
    int b;
    int c;
    double d;
};

提出问题 :引用上面的结构体A,假如我知道一个对象的c成员 的地址,但是我不知道这个对象的地址,我要怎样求得这个对象的地址呢?

由此在进程之间可以重新设计一个双链表

1)如何获取进程的其他属性?

刚刚展示的,由成员变量地址可以求出整体对象的地址。所以这里我们知道了成员变量链表节点的地址,自然能求得整个进程的地址,也就能访问该进程的其他属性;

2)为何如此设计?

增加链式管理的扩展性,其他地方也能用这种结构;代码只需要维护一份;

3)节点连接

当新的进程需要连接上来时,只需要insert这个link node。

4)进程都在哪?

Linux内核,会将所有进程的task_struct统一放在一张全局的双链表list_head task中;

5)进程不是也会在运行队列、阻塞队列吗?

当然,说明task_struct中,不仅有一套list_head task,还会有其他的链表,如:struct list_head run代表的就是运行队列的链表。这也就使得进程能在不同的队列链表中自由切换。

甚至,不同的结构体对象也能连接起来;

甚至task_struct里还能接不同的数据结构,红黑树、hash表等。

Linux的调度(运行)队列

每一个CPU都有一个调度(运行)队列:struct runqueue{};

当系统要运行进程时,会在140个数组元素中找下标较小且非空的,然后在找到的元素中再看其中链表最靠前的,那一个就是要运行的进程。

那此时是需要去循环找到哪个元素吗?

不是的,Linux中,为了提高效率,采用了位图的方法检测:

以上就设计出一个结构:优先级数组

c 复制代码
struct prio_array_t {
	int nr_active;
	long bitmap[5];
	struct list_head queue[140];
}

问题

1)假如系统中有一些进程优先级是61,然而不断的有优先级是60的进程加进来,那么61的进程就一直不运行了?

当然不是,因为这是个分时操作系统,会有时间片的限制。

2)那是将60的进程PRI变大吗?

也不是,因为将PRI变大,将优先级变底,不仅变得是PRI的数字,还要将进程的所在队列更改,成本大。

解决办法

CPU的调度(运行)队列struct runqueue{};中会存在两个相同的优先级数组struct prio_array_t[2],一个是活跃队列 ,一个是过期队列 ,并且会有两个指针*active、*expired,各自指向一个队列。

任何运行的进程都是从活跃队列里拉出来的,当这个进程的时间片结束 了,这个进程会被放进过期队列中 的对应位置;当活跃队列为空时,两个指针指向将会调换,过期队列变成活跃队列,至此又来一轮进程的运行。

那么像问题(1)的现象,新来的60进程,不会直接接到活跃队列后面,而是接到过期队列后面,在下一轮过期-活跃对换时再运行60进程。

以上就是Linux进程队列的O(1)算法。

相关推荐
code monkey.2 小时前
【Linux之旅】Linux 线程同步与互斥实战:从锁机制到生产消费模型全指南
linux·c++·线程·同步·互斥
Mortalbreeze2 小时前
理解文件和缓冲区
linux·服务器
艾莉丝努力练剑2 小时前
【Linux网络】Linux 网络编程:HTTP(一)协议初识
linux·运维·服务器·网络·tcp/ip·计算机网络·http
认真的薛薛2 小时前
Linux基础:nfs-lsyncd-rsync
linux·运维·服务器
艾莉丝努力练剑2 小时前
【Linux网络】Linux 网络编程:HTTP(二)HTTP协议请求应答宏观格式(附代码演示)
linux·运维·服务器·网络·tcp/ip·计算机网络
fiveym2 小时前
Linux systemctl服务管理全解析:命令+原理+运维最佳实践
linux·运维·服务器
xlq223222 小时前
56.自定义协议
linux·服务器·网络·网络协议
learning-striving3 小时前
华为云欧拉操作系统的服务器实例中手工部署 Docker
linux·运维·服务器·docker·容器·华为云
小此方3 小时前
Re:Linux系统篇(十五)工具篇 ·六:GDB 调试从底层逻辑到高阶实战
linux·运维·服务器·chrome