【 linux 】进程的调度算法


目录

1.进程优先级

[2. 操作系统的调度算法](#2. 操作系统的调度算法)

[2.1 运行队列的结构](#2.1 运行队列的结构)

[2.2 active和expired的比较](#2.2 active和expired的比较)

[2.3 O(1)调度器的弊端](#2.3 O(1)调度器的弊端)


1.进程优先级

进程优先级是调度器决定下一个谁运行的核心数据,数值越小,优先级越高。

实时进程的优先级最高,数值为0-99

普通进程可以通过nice renice调整优先级。对于普通用户nice的范围是0-19,超级用户还可以设置-20至-1

cs 复制代码
创建新进程时设置优先级
nice -n -5 ./my_server      
nice -n 19 ./backup.sh      

renice用于修改已存在进程的nice值

cs 复制代码
# 按 PID 调整
renice -n -10 -p 1234       # 将 PID=1234 的进程 NI 改为 -10

# 按用户/进程组批量调整
renice -n 5 -u username     # 该用户所有进程 NI +5
renice -n 0 -g 5678         # 进程组 GID=5678 的所有进程 NI 设为 0

nice值在操作系统内部存储时还要加上默认值,通常为120,最后的范围是100-139

在 Linux 内核头文件 include/linux/sched/prio.h 中,有如下宏定义:

cs 复制代码
#define MAX_RT_PRIO       100      // 实时优先级最大值(不含)
#define MAX_PRIO          140      // 总优先级数量
#define DEFAULT_PRIO      (MAX_RT_PRIO + NICE_WIDTH / 2) // 即 100 + 40/2 = 120
#define NICE_WIDTH        (MAX_PRIO - MAX_RT_PRIO)       // 即 140 - 100 = 40

2. 操作系统的调度算法

2.1 运行队列的结构

一个CPU,一个运行队列(struct runqueue),核心有两个优先级数组 active和expired,数组的结构如下

cs 复制代码
struct prio_array {
    unsigned int nr_active;          // 该数组中的进程总数
    unsigned int bitmap[BITMAP_SIZE]; // 优先级位图
    struct list_head queue[MAX_PRIO];  // 140个优先级链表
};

unsigned int类型一共有32个比特位,数组大小通常为5,共有160个比特位,正好对应140个优先级,每个位为1代表这个位置有进程

nr_active是一个计数器,用于计算进程数,判断系统负载

struct list_head queue是140个优先级链表,每个优先级对应一个双向链表头,挂载该优先级上的所有进程

抽象图如下

通过位图的方法能够快速找到下一个等待执行的进程,从而达到了O(1)级别。

2.2 active和expired的比较

调度器永远只从 active 数组中选取下一个要运行的进程。

expired不参与调度选择,它只是一个**等待区。**存放那些本轮时间片已耗尽、但尚未获得新一轮时间片的普通进程

当nr_active变成0时,交换两个数组的指针,expired变成active继续被调度。上一轮的active变成expired自动刷新进入新一轮

nr_active变成0意味着当前 CPU 的 active 数组中没有任何处于可运行状态的进程

通常由四种场景触发

1.正常耗尽,时间片用完被移入expired

2.主动阻塞,进程进入睡眠/等待状态,直接移出运行队列

3.进程终止,调用exit()或被信号杀死,移出运行队列

4.在多核系统中负载均衡机制,进入其他cpu的运行队列

其中第二种情况最为常见

完整的生命周期

cs 复制代码
新进程 ──→ active
              │
              ├── 被调度执行 → 时间片耗尽(非交互) ──→ expired
              ├── 被调度执行 → 时间片耗尽(交互式) ──→ 重回 active
              ├── 未轮到执行 → 主动睡眠/阻塞 ──→ 移出运行队列(不进expired)
              ├── 未轮到执行 → 被迁移到其他CPU ──→ 移出本CPU active
              └── 被更高优先级抢占 → 留在 active 原位(保留剩余时间片)

当且仅当 active->nr_active == 0 时:
    swap(active, expired)
    // 原 expired 中所有进程自动获得新一轮时间片
    // 注意:这些进程可能在上轮也没全部跑过!

创建两个优先级数组也是一种空间换时间的方法。

2.3 O(1)调度器的弊端

如果一直fork创建新进程,那nr_active永远无法变为0,expired中的老进程就会被饿死。如果一直有高优先级抢占进程,且没有进程阻塞和退出,那理论上也会存在进程饥饿的问题。

为了解决,Linux 2.6.23 引入CFS时,彻底废弃了 active/expired 双数组结构,从根本上消除了"指针交换"这个前提条件:

  • 没有"批次"概念,每个进程独立按 vruntime 排序
  • 新创建的进程继承父进程的 vruntime,并加上一个小的偏移量(防止 fork 轰炸获得不公平优势)
  • 无论有多少新进程涌入,红黑树中最左节点(vruntime 最小的进程)总是会被选中
  • 老进程的 vruntime 不会因为"没轮到交换"而停滞,它们只是在树中位置相对靠右,但仍然有确定的、可计算的调度机会

相关推荐
代码中介商12 小时前
Git 版本控制完全指南:从分支管理到远程协作
linux·git
c++逐梦人12 小时前
多路转接epoll
linux·网络·epoll
r-t-H12 小时前
KVM虚拟化与Docker基础实践-第三章
linux·运维·nginx·docker·容器
嘿嘿嘿x312 小时前
Linux-知识点1-$-POSIX等
linux·ubuntu
艾莉丝努力练剑12 小时前
【Linux网络】Linux 网络编程:传输层UDP
linux·运维·服务器·网络·计算机网络·udp
陈eaten12 小时前
centos 7等保整改学习
linux·运维·服务器·网络安全·centos·等保
牢七12 小时前
契约锁分析
linux·运维·服务器
承渊政道13 小时前
Linux系统学习【进程概念从入门到深入理解】
linux·服务器·笔记·学习·ubuntu·系统架构·bash
无忧.芙桃13 小时前
进程间通信的基本概念(上)
linux·运维·服务器