Linux 内核剖析:进程优先级、上下文切换与 O(1) 调度算法

1. 进程优先级

1.1 基本概念

  • 是什么 :进程获得CPU资源的先后顺序

  • 为什么需要 :因为CPU资源稀缺,需要确定哪个进程优先执行。

  • 如何实现 :在进程的 task_struct 结构中用整数表示优先级。

  • 区分

    • 优先级 (Priority):能否以及何时获得资源。

    • 权限 (Permission):能否访问资源(安全控制)。

1.2 Linux优先级表示

  • PRI (Priority) :进程的最终优先级,用户无法直接修改。

    • Linux默认值:80

    • Linux范围:[60, 99](共40级)

  • NI (Nice Value) :进程优先级的修正值(调整参数)

    • 范围:[-20, 19]

    • 用户可以通过 nicerenice 命令调整此值。

  • 计算公式

    最终优先级(PRI) = 基础优先级(默认80) + NI值

  • NI值越低(可为负数),优先级越高。

  • NI值越高(最大19),优先级越低。

1.3 查看与调整优先级

1.3.1 查看进程优先级-ps

bash 复制代码
# 使用ps命令查看进程详细信息,包括优先级
ps -al | head -1 && ps -al | grep myprocess
# 输出示例:
# F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
# 0 S 1000 24261 24260 0 80 0 1054 hrtime pts/0 00:00:00 myprocess

1.3.2 调整进程优先级-nice(需root权限)

bash 复制代码
# 启动时指定优先级(普通用户只能增加NI值,降低优先级)
nice -n 10 ./myprocess  # 设置NI为10

# 调整运行中进程的优先级
renice -n -100 -p 25110  # 调整进程25110的NI值(实际会限制在[-20,19]内)
renice -n 100 -p 25110   # 调整进程25110的NI值(实际会限制在[-20,19]内)

1.4 系统调用接口

c 复制代码
#include <sys/time.h>
#include <sys/resource.h>

int getpriority(int which, int who);  // 获取优先级
int setpriority(int which, int who, int prio);  // 设置优先级

1.5 进程权限与文件访问

  • Linux中,访问任何资源都是以进程身份进行,进程代表用户执行操作。

  • 系统通过进程的有效用户ID (EUID) 判断文件访问权限(所有者、所属组、其他用户)。

2. 进程切换与上下文

2.1 时间片与进程调度

  • 时间片 (Time Slice):每个进程被分配的执行时间单元。

  • 关键事实

    1. 进程不会一直占用CPU直到代码执行完毕,而是会在时间片用完后被切换。

    2. 死循环进程不会"打死"系统,因为它们也会被调度器切换出去。

2.2 CPU寄存器与进程上下文

  • 寄存器的作用 :CPU内部的临时存储空间,用于保存当前正在执行进程的临时数据。

  • 寄存器内容 = 进程的硬件上下文

    • 程序计数器 (PC/EIP):下一条要执行的指令地址。

    • 栈指针 (EBP/ESP):栈帧相关信息。

    • 通用寄存器 (EAX, EBX, ECX, EDX等):计算中间结果。

    • 段寄存器 (CS, DS, ES等):内存段信息。

    • 标志寄存器 (EFLAGS):状态标志。

2.3 进程切换的类比

"学生当兵"模型

  • CPU (学校):执行任务的地方。

  • 进程 (学生):需要执行的任务。

  • 调度器 (辅导员):决定哪个学生使用学校资源。

  • 上下文 (学籍):学生当前的学习状态。

  • 切换过程

    1. 保存上下文 :学生离校当兵前,保存学籍信息(相当于保存进程的寄存器内容)。

    2. 恢复上下文 :学生返校后,恢复学籍信息(相当于恢复进程的寄存器内容)。

    3. 继续执行:从上次中断的地方继续学习。

2.4 进程切换的核心机制

  1. 上下文保存位置 :进程的上下文数据被保存到其 task_struct 结构体中。

  2. 具体实现 :在Linux中,上下文信息通常保存在 TSS (Task State Segment,任务状态段) 结构中。

  3. 切换核心 :进程切换最核心的操作就是保存当前进程的硬件上下文到其PCB中,并将下一个进程的上下文从PCB恢复到CPU寄存器

3. 任务状态段 (TSS)

概念 :TSS 是 x86 架构里用于保存任务上下文、管理特权级栈、处理中断栈和控制 I/O 权限的专用硬件数据结构。

3.1 TSS结构

c 复制代码
// 任务状态段数据结构
struct tss_struct {
    long back_link;     // 链接字段
    long esp0;          // 0级栈指针
    long ss0;           // 0级栈段选择符
    long esp1;          // 1级栈指针
    long ss1;           // 1级栈段选择符
    long esp2;          // 2级栈指针
    long ss2;           // 2级栈段选择符
    long cr3;           // 页目录基址寄存器
    long eip;           // 指令指针
    long eflags;        // 标志寄存器
    long eax, ecx, edx, ebx;  // 通用寄存器
    long esp;           // 栈指针
    long ebp;           // 基址指针
    long esi, edi;      // 变址寄存器
    long es, cs, ss, ds, fs, gs;  // 段寄存器
    long ldt;           // 局部描述符表选择符
    long trace_bitmap;  // 调试跟踪位图
    struct i387_struct i387;  // 浮点运算单元状态
};

3.2 TSS在PCB中的位置

c 复制代码
struct task_struct {
    // 各种进程属性...
    long state;         // 进程状态
    long counter;       // 时间片计数器
    long priority;      // 优先级
    // ... 其他字段
    
    /* tss for this task */
    struct tss_struct tss;  // 任务状态段
};

4. O(1)调度算法

调度器目标

  • 公平性:确保所有进程都有机会获得CPU时间。

  • 高效性:快速选择下一个要运行的进程。

  • 响应性:对交互式进程快速响应。****

4.1 核心数据结构

c 复制代码
// 简化表示
struct prio_array {
    unsigned int bitmap[5];     // 优先级位图,指示哪些优先级队列非空
    struct list_head queue[140]; // 140个优先级队列,每个队列对应一个优先级
    unsigned int nr_active;     // 活跃进程总数
};

struct runqueue {
    struct prio_array *active;   // 指向活跃队列
    struct prio_array *expired;  // 指向过期队列
    // ... 其他调度信息
};

4.2 调度过程

bitmap[5] :⼀共140个优先级,⼀共140个进程队列,为了提⾼查找⾮空队列的效率,就可以⽤

5*32个比特位表示列是否为空,这样,便可以⼤⼤提⾼查找效率!

  1. 两个队列设计

    • 活跃队列 (active) :存放仍有时间片的进程。

    • 过期队列 (expired) :存放时间片已用完的进程。

  2. 调度步骤

    a. 从活跃队列的位图中查找最高优先级 的非空队列。

    b. 从该优先级队列中取出第一个进程 执行。

    c. 进程时间片用完时,重新计算优先级和时间片 ,放入过期队列。

    d. 当活跃队列为空时,交换活跃队列和过期队列的指针

  3. 性能特点 :选择下一个进程的时间复杂度为 O(1),与系统进程数无关。

4.2 优先级映射

  • Linux优先级范围 [60, 99] 映射到调度器的140个优先级队列。

  • 映射关系考虑公平性和响应性需求。

4.3 进程饥饿问题

  • 定义 :由于优先级设置不合理,导致低优先级进程长时间得不到CPU资源

  • 解决方案

    1. 动态优先级调整:根据进程行为(CPU密集型 vs I/O密集型)动态调整优先级。

    2. 时间片补偿:为长时间未运行的进程提供额外时间片。

    3. 公平调度算法:如CFS(完全公平调度器),确保所有进程公平分享CPU时间。

4.4 实时操作系统 vs 分时操作系统

  • 分时操作系统 (如桌面Linux)

    • 目标:公平分享CPU时间。

    • 特点:支持优先级,但可能被抢占。

    • 应用:通用计算环境。

  • 实时操作系统 (如VxWorks, RTLinux)

    • 目标:保证任务在规定时间内完成。

    • 特点:严格优先级,高优先级任务可立即抢占低优先级任务。

    • 应用:工业控制、航空航天、医疗设备等关键任务系统。

5. 总结

  1. 孤儿进程是父进程先退出后由init进程领养的子进程,防止僵尸进程累积。

  2. 进程优先级通过PRI和NI值确定,PRI=80+NI,范围[60,99],可通过nice/renice调整。

  3. 进程切换 的核心是保存和恢复硬件上下文(寄存器内容),保存到PCB的TSS结构中。

  4. O(1)调度算法使用活跃/过期双队列设计,实现常数时间的进程选择。

  5. 进程饥饿是低优先级进程长期得不到CPU的问题,现代调度算法通过动态调整避免。

  6. 分时操作系统强调公平性,实时操作系统强调确定性,适用于不同应用场景。

相关推荐
FQNmxDG4S1 小时前
Java泛型编程:类型擦除与泛型方法的应用场景
java·开发语言·python
zhouwy1131 小时前
Linux进程与线程编程详解
linux·c++
minglie11 小时前
实数列的常用递推模式
算法
我星期八休息2 小时前
IT疑难杂症诊疗室:AI时代工程师Superpowers进化论
linux·开发语言·数据结构·人工智能·python·散列表
代码小书生2 小时前
math,一个基础的 Python 库!
人工智能·python·算法
AI科技星2 小时前
全域数学·数术本源·高维代数卷(72分册)【乖乖数学】
人工智能·算法·数学建模·数据挖掘·量子计算
热心网友俣先生2 小时前
2026年第二十三届五一数学建模竞赛C题超详细解题思路+各问题可用模型推荐+部分模型结果展示
c语言·开发语言·数学建模
生成论实验室2 小时前
《事件关系阴阳博弈动力学:识势应势之道》第一篇:生成正在发生——从《即事经》到事件-关系网络
人工智能·科技·算法·架构·创业创新
01漫游者2 小时前
JavaScript函数与对象增强知识
开发语言·javascript·ecmascript