Linux系统篇,进程概念(四):内核链表的深度理解、进程优先级的底层理解

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、理解内核链表
    • [1.1 原有链表的不足](#1.1 原有链表的不足)
    • [1.2 Linux进程链表](#1.2 Linux进程链表)
    • [1.3 连接器结构设计的意义](#1.3 连接器结构设计的意义)
      • [1.3.1 类型转换和地址偏移定位](#1.3.1 类型转换和地址偏移定位)
      • [1.3.2 实现信息的"流动"](#1.3.2 实现信息的“流动”)
        • [1.3.2.1 组件的体现](#1.3.2.1 组件的体现)
        • [1.3.2.2 流动的体现](#1.3.2.2 流动的体现)
  • 二、进程优先级
    • [2.1 进程优先级的定义](#2.1 进程优先级的定义)
    • [2.2 进程优先级的意义](#2.2 进程优先级的意义)
      • [2.2.1 对比:优先级 vs 权限](#2.2.1 对比:优先级 vs 权限)
    • [2.3 进程优先级的实现](#2.3 进程优先级的实现)
      • [2.3.1 分时操作系统](#2.3.1 分时操作系统)
      • [2.3.2 时间片轮转](#2.3.2 时间片轮转)
  • 三、优先级的查看与实现
    • [3.1 UID:user id](#3.1 UID:user id)
    • [3.2 进程优先级查看](#3.2 进程优先级查看)
    • [3.3 进程优先级实现](#3.3 进程优先级实现)
    • [3.4 优先级的修改](#3.4 优先级的修改)
      • [3.4.1 命令行修改](#3.4.1 命令行修改)
        • [3.4.1.1 nice 与 renice](#3.4.1.1 nice 与 renice)
        • [3.4.1.2 top](#3.4.1.2 top)
      • [3.4.2 系统调用](#3.4.2 系统调用)
    • [3.5 nice的极值问题](#3.5 nice的极值问题)
      • [3.5.1 nice 的范围](#3.5.1 nice 的范围)
      • [3.5.1 范围限定的意义](#3.5.1 范围限定的意义)
  • 总结

前言

  大家好,我是曦夜日长。在上一节我们了解了进程的状态以及具体的表现形式,本节我们将回到进程的运行过程中------进程优先级,让我们话不多说,开始本节的学习。


一、理解内核链表

  从前面的学习可知:计算机都是以进程为基本单位执行的。而PCB作为进程的信息,在os中数量肯定数不胜数,而我们之前所说的队列一般只存储运行态R的PCB,那其他的PCB怎么管理呢?

1.1 原有链表的不足

  Linux设定用链表管理,但 "此链表,非彼链表"。我们理解的链表结构belike:

  而Linux链表,站在全局考虑,解决原有链表不足:

问题 说明
每种类型都要写一套 task_node、file_node、device_node......代码爆炸
多一次内存分配 挂一个对象,要额外分配一个 node
多一次指针跳转 node->data->pid,缓存不友好
对象和节点生命周期不一致 节点释放了对象还在?对象释放了节点还在?
一个对象只能挂一条链表 因为只有一个 data 指针

1.2 Linux进程链表

  于是将数据,与封装后的指针解耦,形成连接器结构:

  具体的代码:

bash 复制代码
//部分代码
struct list_head {
    struct list_head *next, *prev;
};
struct task_struct{
    int x, y, z;
    struct list_head links;
};

1.3 连接器结构设计的意义

1.3.1 类型转换和地址偏移定位

  通过这样的封装指针便于我们直接获取结构体地址,而这样的指针我们如何去获取其指向的数据呢?

  我们之前学习C语言时,有这样一种语法现象:当取结构体地址时,获取的地址等于其第一个变量的地址。同理,我们之前学习的宏定义计算相对偏移:

c 复制代码
(struct xxx*)0

本质:将0号地址模拟成起始地址

同时,结构体首地址=成员地址−偏移量,因此我们就可以从式子中得到结构体的首地址。

  对该内核链表,就有这样的操作:

bash 复制代码
(struct task_struct *)((char *)next - (char *)&((struct task_struct *)0)->links)

  当然我们也可以直接使用函数offsetof(struct task_struct, links);

1.3.2 实现信息的"流动"

  我们可知Linux:一个单线程进程有一个 task_struct(后面线程知识),而PCB作为进程信息的合集,包含大量的数据,拷贝和自身转移都是非常麻烦的,但struct list_head的设计,使得PCB成为一个组件一样的东西。

1.3.2.1 组件的体现

  研究Linux源码,可以看见其多个list_head的结构定义,虽然所有的插口都长在同一个 PCB 上,但每个插口连接的都是不同的、相互独立的、没有交集的独立链表。这就是著名的"用绳子穿过实体"的设计:

bash 复制代码
struct task_struct {
    // ...
    struct list_head tasks;         // 全局链表节点
    struct list_head run_list;      // 运行队列节点 (RT)
    struct rb_node   run_node;      // 运行队列节点 (CFS)
    wait_queue_entry_t wait;        // 等待队列节点
    // ...
};
  • 全局链表: 连接所有进程的tasks,无论哪个PCB,都会存在全局链表这根线上,完成PCB的全局访问

  • 运行队列: 连接状态为 "就绪(Ready)""运行(Running)" 的 PCB 的 run_list的插口

  • 等待队列: 每个硬件设备(网卡、键盘、磁盘等)都有自己专属的一条等待队列线。

1.3.2.2 流动的体现

  当一个进程的状态发生变化时,os就不会对整个PCB处理,而是通过 "断开""连接" 的方法,即添加对应的PCB结构体指针,实现数据的变化,体现数据的流动。


二、进程优先级

2.1 进程优先级的定义

  即字面意思:进程获得CPU资源的先后顺序,也就是被执行的位置前后

2.2 进程优先级的意义

  CPU单位时间运行的程序数量有限,而这种稀缺性就会造成进程在等待时有着先后顺序,而优先级的作用就是对进程在运行队列中顺序的描述**。**

2.2.1 对比:优先级 vs 权限

  初学时,这两者常被混淆,其实它们的本质区别是:可不可以运行

  • 优先级: 得到资源的先后顺序,如:排队打饭
  • 权限: 能否得到资源,如:root和普通用户的区别

2.3 进程优先级的实现

  进程优先级作为进程的一种属性,那么一定存在于PCB当中,而优先级肯定是要比较大小的,因此进程优先级就是PCB中一个整数

  想要清楚优先级的实现,我们还应先理解系统对于进程运行的操作方式。

2.3.1 分时操作系统

  现代Linux和大量OS都是基于时间片的分时操作系统,这样的设计可以保证可控范围内的公平性。

分时操作系统(Time-Sharing Operating System)是一种允许多个用户或多个进程通过时间片轮流使用 CPU 的操作系统。它的核心目标是:公平、快速响应、支持交互。

  而其中的时间片我们应该怎么理解呢?

2.3.2 时间片轮转

  时间片轮转是分时操作系统的指导思想,它通过将CPU运行的时间分为多个简短的时间片(通常为毫秒级别),然后以这样的策略运行(后面会详细解释):

  • 时间片用完 → 时钟中断 → 调度器换下一个进程;

  • 进程主动阻塞(等 I/O、等锁)→ 提前让出 CPU;

  • 高优先级进程可抢占。

  通过这样的设计可以使得进程的运行出现:宏观并行、微观串行的运行方式。

  • 宏观并行: 在人类对秒的敏感单位中,对于多个毫秒级的时间片的运行与切换无法察觉,在肉眼上看到的结果:多个程序同时运行
  • 微观串行: 进程的运行都是随着时间片,逐个进行的

  总的来说:宏观并行、微观串行,是单处理机分时系统通过时间片轮转和上下文切换制造的"同时运行"假象。用户看到多个程序一起推进,但 CPU 在任一时刻只执行一个程序。多核出现后,微观上才可能真正并行。


三、优先级的查看与实现

3.1 UID:user id

  如Linux这样的操作系统,在识别用户时并不是通过用户名这样的字符串,而是根据标识符------UID(用户描述符)辨认的。

/etc/passwd 文件保存用户名 ↔ UID 的映射

  • 内核对身份的绑定: 每当一个用户在终端启动一个程序时,内核在为其创建进程控制块(task_struct)时,会将该用户的 UID 直接写入其中。这意味着,系统中的每一个进程,底层都有那个UID数字。

  • UID划分:

    1. UID = 0 : root 超级用户 ,权限最高,只有一个。

    2. UID = 1‑999(CentOS7+/RHEL8);1‑1000(Ubuntu) :系统用户(服务账号,nginx、mysql、daemon),无登录 shell,后台进程使用。

    3. UID ≥ 1000:普通新建用户,普通人登录账号

  通过ps -ln指令查看UID:

  UID的作用:Linux中访问任何资源都是进程访问的,进程代表用户,OS会将进程的UID和资源的创建角色比对,确认后才可以通过。

3.2 进程优先级查看

  在Linux中可以通过ps -al的指令,查看用户启动后进行中的进程。

3.3 进程优先级实现

  在展现的众多属性中,我们注重的是:CRI和NI

  • PRI(Priority,内核动态优先级): 内核真正调度使用的优先级数字;数值越小,优先级越高,越优先抢到 CPU默认为80
  • NI(Nice 值,友好值,静态修正值) 用户可以修改的偏移量 ,用来间接影响 PRI 取值范围:‑20 ~ 19

  进程的真实优先级就是由两者决定的:

P R I n e w = P R I b a s e + N I \boldsymbol{PRI_{new}=PRI_{base}+NI} PRInew=PRIbase+NI

PRI基准为80,下文会谈到如何修改

3.4 优先级的修改

  优先级的修改不仅可以发生在未执行的进程上,也可在进行的进程中。

3.4.1 命令行修改

3.4.1.1 nice 与 renice
  • nice: 修改动进程的时候设置 nice(新建时指定 NI)
bash 复制代码
nice -n NI值 命令
# -n 后面跟nice偏移
  • renice: 修改已经正在运行进程的 Nice(动态改 NI)
bash 复制代码
renice 新NI值 -p PID

普通用户只能将 nice 值调大,只有root才能调小

3.4.1.2 top

  top是对执行中的进程的nice进行调整,使用top指令,下方为具体步骤:

  1. 进入top: 命令行输入top指令,打开top面板
  2. 进入修改模式: 按下R键,进入renice模式
  3. 选择目标进程: 输入想要修改进程的UID
  4. 调整nice值: 随后系统会提示 Renice PID 25110 to value,输入想要改变成的值

3.4.2 系统调用

  在代码中通过以下两个调用修改nice值:

c 复制代码
int getpriority(int which, int who);
int setpriority(int which, int who, int prio);

// 具体代码
errno = 0;
int ni = getpriority(PRIO_PROCESS, 0);
printf("当前进程nice = %d\n", ni);

// 设置nice=5
int ret = setpriority(PRIO_PROCESS,0,5);
if(ret == -1) perror("setpriority");

3.5 nice的极值问题

3.5.1 nice 的范围

  为了防止用户肆意的改变进程的nice值,造成os的安全问题,于是规定nice的范围是-20,19。这意味着一个进程的优先级必然处于60,99的区间范围内。

3.5.1 范围限定的意义

  Linux这套机制,是为了防止 "进程饥饿"。OS为了确保每个进程都有被调度的公平性,如果由用户随意的拉大或调低某个进程的优先级,就可能造成某个进程长时间占用CPU和某个进程根本没用CPU执行的极端情况。


总结

  下一节我们将学习Linux对于进程的调度算法和优先级是如何影响进程执行的先后顺序,感谢您的阅读,我们下节再见。

相关推荐
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
Linux 库制作与原理
linux
Wang's Blog1 小时前
Java 接入Redis: Redis下载与源码编译安装
java·服务器·redis
liulilittle1 小时前
Linux AI 开发环境搭建实录
linux·ai·llm·agent·hyper-v·dev·opencode
Ivanqhz1 小时前
矩阵引擎的数据流模式与 BM1684X 架构
java·服务器·网络·深度学习·神经网络
高亦真2 小时前
今天是学习嵌入式的第39天
linux·学习·算法
AR-26710-3 小时前
Linux Day8——FHS文件系统层次
linux
小葱运维3 小时前
日志规范(K8s 结构化日志)
运维·开源·云计算
其实防守也摸鱼3 小时前
DeepSeek Harness 开源贡献手记:从入门到合入主线
服务器·数据库·windows·https·ssl
qq_297670873 小时前
Ubuntu 打开系统应用老弹密码?三步彻底关闭免密
linux