跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C算法

Linux 进程优先级与 O(1) 调度算法详解

Linux 进程优先级由 PRI 和 NI 值决定,调整优先级即调整 nice 值。内核采用 O(1) 调度算法,利用活跃队列和过期队列管理进程,通过位图快速查找非空队列。涉及 list_head 侵入式链表设计,以及竞争、独立、并行、并发等概念。

字节跳动发布于 2026/2/5更新于 2026/7/24844 浏览
Linux 进程优先级与 O(1) 调度算法详解

一、进程优先级的概念

CPU 的资源是有限的,所以 CPU 的运行队列中的所有进程是不可能同时得到资源的。这就是为什么运行队列是一个'队列',而 CPU 分配资源的先后顺序,就是指进程的优先级。

二、查看优先级信息

使用 ps -l 命令,可以查看系统中更详细的进程信息:

在这里插入图片描述

其中,与进程优先级相关的信息是 PRI 与 NI,它们也是存在于 task_struct 中的两个整型成员变量。

  • PRI:代表这个进程可被执行的优先级,这个值越小越早被执行
  • NI:代表这个进程的 nice 值

1. PRI 与 NI 的理解

PRI 比较好理解,就是进程的优先级,也就是程序被 CPU 执行的先后顺序,此值越小进程的优先级越高。 NI,是进程的 nice 值,表示进程优先级的修正数值。

进程 PRI 值 = 80 + 进程 nice 值!

这是进程 PRI 的计算公式,nice 变小,PRI 会变小,进程优先级变高,反之亦然。 所以,调整进程优先级,在 Linux 下就是调整进程的 nice 值! 而优先级的变化范围是有限的!nice 值的取值范围是 -20~19,共 40 个级别。PRI 的取值范围就是 [60, 99] 了!

2. 修改 nice 值

我们还是跑一个循环程序进行演示,此时查看 PRI 和 NI 是默认 80 和 0,没有问题

一种修改 nice 的方式是:用 top 命令,修改已存在进程的 nice 值:输入 top 回车,再输入 r 回车,再输入进程 pid 回车,再输入修改后的 nice 值:

在这里插入图片描述

这里我将 nice 值修改为 20。回车,按 q 退出 top,再查询:

在这里插入图片描述

可以看到 nice 值成为了 19,这是因为 20 超过了规定的 nice 值范围,最高只能修改到 19。PRI 值也就变成了 19 + 80 = 99,没有问题。 但是,实际中我们不建议高频更改 nice 值。

三、进程调度切换

之前我们提到过,Linux 系统会给每一个进程分配一个时间片,它的时间片执行完,就会自动让出 CPU 另一个进程接着执行。CPU 内只有一套寄存器,由所有进程共享使用,每次保存着一个进程的私有上下文数据,它的时间结束后,这些数据转移到 task_struct 中的成员变量'上下文数据'中存放,寄存器中继续存放下一个进程的临时数据。再轮到这个进程执行时,临时数据再转移到寄存器中。这种操作系统,被称为分时操作系统,当代计算机大多数都是这种。

那么,系统是如何高效切换调度进程的呢?

下面讲解的,是 Linux2.6 内核进程的 O(1) 调度实现算法:

1. list_head 与 prio_array 结构

源码的 task_struct 定义中,有这样两个相关的成员变量:

在这里插入图片描述

这两个成员结构的定义如下:

成员 prio_array_t *array,prio_array_t 就是 struct prio_array 结构体。task_struct 里的 array 成员是当前进程所属的优先级数组指针,记录了这个进程现在是在活跃队列里,还是在过期队列里,下面讲。

在这里插入图片描述

在这里插入图片描述

struct list_head 是一个双向链表结构,run_list 就是 CPU 运行队列的核心结构!

在这里插入图片描述

我们分析 prio_array_t 的结构:

在这里插入图片描述

所以,根据优先级选择进程队列的过程,本质是 hash!

可是,queue 中存放的队列的结点是 list_head 类型啊,list_head 中只有两个前后指针,没有进程信息啊!

在这里插入图片描述

知识点:

假设有一个结构体变量 struct A obj,它内部有一个成员变量 a。如果我不知道结构体 obj 的地址,只知道它的 a 的地址。可以通过 &a - &((struct A*)0->a) 计算得出 obj 的地址!原理是结构体成员的地址偏移量是固定的,而且地址是由低到高开辟的,通过成员地址减去其在结构体中的偏移量,即可反推出结构体变量的起始地址。得到了结构体变量的地址,就能访问到其他成员了!

所以,实际上 queue 中的每一个 list_head,就是一个进程的 task_struct 的成员变量 struct list_head run_list!通过上述方法,就能用这个 run_list 的地址算出 task_struct 的地址了,进而能获取到进程的各种信息!

这种将链表节点嵌入到目标结构体内部,把 run_list 作为 task_struct 的成员,是 Linux 内核中经典的'侵入式链表'设计,好处是无需额外内存开销、灵活复用、增加链式结构管理的扩展性。甚至,你可以将不同类型的结构连接起来,只要它们都含有 run_list 成员!

在这里插入图片描述

现在我们已经明确队列的结构了,那么如何从中选取一个合适的进程呢? 最简单的想法,就是按照优先级的规则,从下标 0 开始遍历 queue,找到第一个非空的队列,依次调度其中的进程。由于 queue 大小固定 140,这种算法的时间复杂度是 O(1)。 但是,我们总是想要更好的效率。在 prio_array 中,还有一个 bitmap 数组,实际上这是一个位图!他用 140 个比特位表示 140 个进程队列是否为空,利用位运算,大大提高查找非空队列的效率!

2. 活跃 140 队列与过期 140 队列

一个 CPU,拥有一个 runqueue 结构,即 CPU 的运行队列,这个结构的定义如下:

在这里插入图片描述

  • active:称为活跃指针,指向的 prio_array_t 结构称为活跃 140 队列
  • expired:称为过期指针,指向的 prio_array_t 结构称为过期 140 队列
  • arrays:这个数组中,存放活跃队列、过期队列

活跃队列中,按照优先级,放着时间片还没有结束的所有进程。变量 nr_active 记录有多少个运行状态的进程。

过期队列中,放着时间片耗尽的进程。当活跃队列上的一个进程执行完,就放到过期队列上,重新计算它的时间片。

随着进程不断被执行,活跃队列上的进程会越来越少,过期队列上的进程会越来越多。当活跃队列上所有进程都被执行完,直接交换 active 与 expired 指针,刚才的过期队列变成新的活跃队列,刚才的活跃队列变成新的过期队列!继续执行进程。

利用两套优先级队列调度切换进程,是非常巧妙的设计,我们会有以下特殊情况:

  • 来了一个新进程,则这个进程先插入到过期队列中,等下一轮再开始执行!
  • 修改一个进程的优先级,则这个进程在本轮中的位置先不变,等插入到过期队列时再计算新优先级位置,这也是为什么要有 nice 这个中间值的原因之一!

如果只搞一套队列,进程时间片用完或修改优先级时,需要在同一个队列里重新计算时间片、调整位置,会导致遍历队列的开销变大,新进程和过期进程混在一起,调度逻辑混乱。

有了这样一套完整的调度逻辑后,在系统中查找一个最合适的调度进程的时间复杂度就是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度 O(1) 算法!

四、补充概念:竞争、独立、并行、并发

  • 竞争:系统进程众多,而 CPU 资源较少,所以进程之间是有竞争性的。为了高效完成任务,竞争合理,就有了进程优先级。
  • 独立:多进程运行,需要独享各种资源,各个进程运行之间互不干扰。父子进程之间也有独立性。
  • 并行:多个进程在多个 CPU 下分别同时运行,这种称之为并行。不过我们目前大部分的计算机都是只有一个 CPU 的。
  • 并发:多个进程在一个 CPU 下采用进程切换的方式,在一段时间内让多个进程都得以推进,称之为并发!

目录

  1. 一、进程优先级的概念
  2. 二、查看优先级信息
  3. 1. PRI 与 NI 的理解
  4. 2. 修改 nice 值
  5. 三、进程调度切换
  6. 1. listhead 与 prioarray 结构
  7. 2. 活跃 140 队列与过期 140 队列
  8. 四、补充概念:竞争、独立、并行、并发
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 链表十大经典 OJ 题目详解与实战技巧
  • 汇川机器人软件 RobotLab 常规操作
  • MyBatisPlus 与 Thymeleaf 全栈分页实战
  • Python 控制周立功 CAN 卡读取总线消息并保存为 BLF 文件
  • FPGA 摄像头采集到 HDMI 显示完整链路实战
  • AI Skills 详解:概念、区别与配置方法
  • 资深安全工程师推荐的9本黑客技术经典书籍
  • Python 爬虫开发常用软件与工具指南
  • Agent 框架设计核心要素与实现路径
  • C++ 核心就业方向与职业发展
  • Ubuntu 22.04 桌面版安装指南
  • 亚洲艺术电影节携澳门文化亮相深圳
  • MongoDB 跨机房容灾架构:多数据中心复制集部署方案
  • llama.cpp docker 镜像pull国内加速地址
  • Nilearn Python 神经影像机器学习完整指南
  • Python Wheel 包 (.whl) 安装指南与常见问题处理
  • GitPuk 代码管理工具安装配置与入门实战
  • 前端部署:从开发到生产的关键实践
  • CentOS 7 Docker 安装指南
  • Java + Spring AI 智能体开发实战:构建全能 AI 助手

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online