进程优先级、切换与调度

2114 字
11 分钟
进程优先级、切换与调度

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]

    • 用户可以通过 nice 或 renice 命令调整此值。

  • 计算公式:

最终优先级(PRI) = 基础优先级(默认80) + NI值
  • NI值越低(可为负数),优先级越高。

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

1.3 查看与调整优先级#

1.3.1 查看进程优先级-ps#

Terminal window
# 使用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权限)#

Terminal window
# 启动时指定优先级(普通用户只能增加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 系统调用接口#

#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结构#

// 任务状态段数据结构
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中的位置#

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

4. O(1)调度算法#

调度器目标

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

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

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

4.1 核心数据结构#

// 简化表示
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. 分时操作系统强调公平性,实时操作系统强调确定性,适用于不同应用场景。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Profile Image of the Author
HandlerOne
おかえり
公告
本站已整理 138 篇技术笔记,并按 8 条主线排成学习路线(141 站);站内搜索与桌宠对话均已上线。
分类
标签
最新动态
站点统计
文章
138
分类
12
标签
42
总字数
597,044
站点信息
构建平台
腾讯云 · Nginx
博客版本
v6.16.8
文章目录