进程调度的实现

本文深入探讨Linux内核的Completely Fair Scheduler(CFS)调度器,主要关注时间记账、进程选择、调度器入口和睡眠唤醒四个核心部分。CFS通过红黑树管理进程,选择vruntime最小的进程执行,实现进程的公平调度。当进程休眠或唤醒时,会相应地从红黑树中移进移出,并通过等待队列处理。schedule()函数作为调度器入口,根据优先级选择下一个运行的进程。
摘要由CSDN通过智能技术生成

内容参考摘录自 《Linux内核设计与实现 第三版》

CFS调度实现的相关代码位于/kernel/sched/fair.c中,接下来重点关注4个部分
在这里插入图片描述

  • 时间记账
  • 进程选择
  • 调度器入口
  • 睡眠和唤醒

一、时间记账
所有的调度都必须对进程运行做时间记账。当每次系统时钟节拍发生时,时间片都会被减少一个节拍周期。当一个进程的时间片被减少到0时,它就会被另一个尚未减到0的时间片可运行进程抢占。

1.调度器实体结构
CFS不再有时间片的概念,但是它也必须维护每个进程运行的时间记账,因为它需要确保每个进程只在公平分配给它的处理器时间内运行。CFS使用调度器实体结构
(路径:在这里插入图片描述

在这里插入图片描述
调度器实体结构实se的成员变量,嵌入在进程描述符struct task_struct内。

2.虚拟实时
vruntime变量存放进程的虚拟运行时间,该运行时间(花在运行上的时间和)的计算是经过了所有可运行进程总数的标准化(或者说是被加权的)。虚拟时间是以ns为单位的,所以vruntime和定时器节拍不再相关。虚拟运行时间可以帮助我们逼近CFS模型所追求的“理想多任务处理器”。优先级相同的所有进程的虚拟运行时间都是相同的——所有任务都将接收到相等的处理器份额。但是因为处理器无法实现完美的多任务,它必须依次运行每个任务。因此CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久。

二、进程选择
若存在一个完美的多任务处理器,所有可运行进程的vruntime值将一致。实际上完美的多任务处理器不存在,因此CFS试图利用一个简单的规则去均衡进程的虚拟运行时间:当CFS需要选择下一个进程时,它会挑一个具有最小vruntime的进程。这其实就是CFS调度算法的核心:选择具有最小vruntime的任务。
CFS使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值得进程。

选择过程
1.挑选下一个任务
假设,一个红黑树存储了系统中所有的可运行进程,其中节点的键值便是可运行进程的虚拟时间。CFS调度器选取待运行的下一个进程,是所有进程中vruntime最小的那个,它对应的便是树中最左侧的叶子节点。<从树的根节点沿着左边的子节点向下找,一直找到叶子节点,便可以找到vruntime值最小的那个进程。>

2.向树中加入进程
CFS将进程加入rbtree中是通过在进程变为可运行状态(被唤醒)或者是通过fork()调用第一次创建进程时。

3.从树中删除进程
删除动作发生在进程堵塞或终止时。

三、调度器入口
进程调度的主要入口点是函数schedule()。它是内核其它部分用于调用进程调度器的入口:选择哪个进程可以运行,何时将其投入运行。Schedule()通常都需要和一个具体的调度类相关联,也就是说,它会找到一个最高优先级的调度类——后者需要有自己可运行队列,然后问后者谁才是下一个该运行的进程。期间会调用pick_next_task(),pick_next_task()会以优先级为序,从高到低,依次检查每一个调度类,并且从最高优先级的调度类中,选择最高优先级的进程。

四、睡眠和唤醒
休眠(被阻塞)的进程处于一个特殊的不可执行状态。如果没有这种状态,调度程序就可能选出一个本不愿意被执行的进程,更糟糕的是,休眠必须以轮询的方式实现了。进程休眠有多种原因,但肯定都是为了等待一些事件。<例子:1>休眠最常见的一个原因就是文件I/O——如进程对一个文件执行了read操作,而这需要从磁盘里读取。2>进程在获取键盘输入的时候需要等待。>无论那种情况,内核的操作都相同:进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒进程刚好相反,进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中。
【注】进程运行队列,每个CPU都有一个,而睡眠队列有很多,用户可以自己创建。

1.等待队列
休眠通过等待队列进行处理。等待队列是由等待某些事件发生的进程组成的简单的链表。内核用wake_queue_head_t来代表等待队列。等待队列可以通过DECLARE_WAITQUEUE()静态创建,也可以由init_waitqueue_head()动态创建。进场把自己放入等待队列中并设置成不可执行状态。当与等待队列相关的事情发生的时候,队列上的进程会被唤醒。

2.唤醒
唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。它调用函数try_to_wake_up(),该函数负责将进程设置为TASK_RUNNING状态,调用enqueue_task()将此进程放入红黑树中,如果被唤醒的进程优先级比当前正在执行的进程的优先级高,还要设置need_resched标志。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值