浅析 Linux 内核进程调度

1. 前言

进程调度是操作系统中一个重要的管理任务,它决定在给定时间点哪个进程可以被调度执行。进程调度的目标是最大程度地利用系统资源、提高系统吞吐量、降低响应时间以及确保公平性和优先级的合理分配。

Linux 内核提供了强大的进程调度能力以及丰富的调度策略。让 Linux 系统可以轻松应对 CPU 密集型、实时以及 IO 密集型等各种各样的计算任务。

本文主要通过结合上文提供的流程图,简要介绍一下 Linux 内核中进程的调度逻辑。

2. 基础数据结构

2.1 进程描述符

在 Linux 中描述进程的数据结构是task_strct,里面包含了进程描述信息、分配给进程的内存资源、进程打开的文件列表以及进程调度相关的信息。下面是节选了跟进程调度相关的字段。

c 复制代码
struct task_struct {
        /*
         * 进程优先级
         */
	int				prio;
	int				static_prio;
	int				normal_prio;
	unsigned int			rt_priority;
        /*
         * 进程关联的调度实体
         */
	struct sched_entity		se;
	struct sched_rt_entity		rt;
	struct sched_dl_entity		dl;
        /*
         * 进程关联的调度类
         */
	const struct sched_class	*sched_class;
}

2.2 调度实体

调度实体(sched_entity),在 Linux 内核中所有可以被调度器调度的对象都会有一个对应的调度实体。

这里需要注意的是,调度实体并不是一个结构体,而是一系列结构的总称。它包括sched_entitysched_rt_entity以及sched_dl_entity等。其中sched_entity是被CFS调度类使用,而sched_rt_entity则为RT调度类使用。

2.3运行队列RQ

rq 结构体(struct rq)是一个进程调度一个核心的数据结构,它表示一个运行队列。其用于跟踪和管理在特定 CPU 上等待运行的进程和线程。rq 结构体定义了一系列的字段,这些字段用于存储和管理关于 CPU 调度的各种信息。包括当前正在运行的任务、等待运行的任务队列、调度器统计信息、负载平衡信息等。

2.4 调度类

在 Linux 内核中调度类负责实际执行下一个要运行进程的挑选工作。sched_class主要由一系列函数指针构成,这些函数指针由具体的调度类负责实现。

c 复制代码
struct sched_class {
    const struct sched_class *next; // 指向下一个调度类的指针

    void (*enqueue_task)(struct rq *rq, struct task_struct *p, int flags);
    void (*dequeue_task)(struct rq *rq, struct task_struct *p, int flags);
    void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);

    struct task_struct *(*pick_next_task)(struct rq *rq, struct task_struct *prev);

    ...
};
  • enqueue_task:将一个进程添加进队列。
  • dequeue_task:将一个进程从队列里面移除。
  • check_preempt_curr:判断当前进程是否可以被抢占。
  • pick_next_task:选择下一个要运行的进程。

3. 调度流程

Linux 内核中进程的调度入口函数是schedule函数,在schedule函数的逻辑中,首先会提取出当前进程所运行的 CPU,然后从 CPU 中取出关联的 rq 运行队列。

在 Linux 内核中rq是一个per-CPU变量,会为每个 CPU 保存一份单独的拷贝,避免了多个 CPU 之间的数据竞争。per-CPU变量的实现具体取决与 CPU 的架构,有些 CPU 架构是存放与 CPU 的本地存储器(local memory)中。有些实现在是存放与一个特殊的段(Segment)中。

由于2.3小节的介绍可知,rq是 CPU 表征运行队列的结构体。所有等待CPU调度的进程都会在rq中排队(排队的队列可能是FIFO队列,也可能是优先级队列,具体取决于调度类)。

Linux 内核提供了多种调度策略,它们被抽象为一个个调度类。目前,内核中提供的调度类包括停机调度类(stop_class)、限期调度类(dl_class)、实时调度类(rt_class)、完全公平调度类(cfs_class)等等。

在 Linux 内核中维护了一个全局的调度类数组sched_class,当开始挑选下一个进程上CPU运行时,内核代码会遍历此数组,分别调用这些调度类上定义的pick_next_task函数获取下一个要运行的进程。这也意味着排在数组前面的调度类进程更有机会进入CPU运行。这也是调度类优先级实现的机制。排在数组前面的调度类优先级越高。

当挑选出下一个运行的进程之后,内核代码会进入CPU上下文切换流程调用context_switch函数。自此,进程的调度工作已经完成。

4. 总结

本文是我第一次写 Linux 内核相关的博文,内容上难免存在纰漏和瑕疵。如能邮件指出不胜感激。

5. 参考资料

6. 文章链接

www.zhuoxiong.top/2023/12/28/...

相关推荐
zsyzClb几秒前
总结用ubuntu一直以来遇到的问题
linux·运维·ubuntu
will_net25 分钟前
Linux入门(十八)read&函数
linux·运维·chrome
运维小杨30 分钟前
linux云计算学习第八周,第九周
linux·学习·云计算
亮亮亮亮!1 小时前
Linux之Python定制篇——新版Ubuntu24.04安装
linux·运维·服务器
_小猪沉塘2 小时前
【Create my OS】5 内核线程
linux·操作系统·unix
小慧10243 小时前
2.5 Rviz使用教程
linux·ros
桑晒.3 小时前
系统入侵排查实战指南:从Windows到Linux的应急响应与溯源分析
linux·运维·windows
dragon_perfect5 小时前
adoc(asciidoc)转为markdown的方法,把.adoc文件转换为markdown格式
linux·运维·deepseek本地知识库
编码小笨猪11 小时前
浅谈Linux中一次系统调用的执行过程
linux·服务器·c++
早起鸟儿12 小时前
docker-Dockerfile 配置
java·linux·运维·docker