进程调度(二CFS调度算法)

1.Linux调度的实现

CFS调度算法的实现由四部分组成:

(1)时间记账:

  • 所有的调度器都必须对进程运行时间做记账
  • CFS使用调度器实体机结构来追踪进程运行记账(定义在sched。好的struct sched_entity)调度器实体结构作为一个名为se的成员变量,嵌入在进程描述符struct task_struct内。
  • CFS使用vruntime变量来记录一个程序到底运行了多长时间以及它还应该再运行多久(定义在struct sched_entity中)
  • 定义在kemeVsched_fair.c文件中的update_curr()函数实现了该记账功能
  • update_curr()计算了当前进程的执行时间,并且将其存放在变量delta_exec中。Update_curr()是由系统定时器周期性调用

(2)进程选择

  • CFS调度算法的核心:选择具有最小的Vruntime的任务
  • 使用红黑树来组织可运行进程队列,并利用其迅速找到最小vruntime值的进程
  • 红黑树(rb_tree)是一个自平衡二叉搜索树,是一种以树节点形式存的储数据,这些数据会对应一个键值,可以通过这些键值来快速检索节点上的数据(重要的是,通过键值检索到对应节点的速度),通常CFS调度算法进行进程选择时会:

A:挑选下一个任务:CFS的进程选择算法简单总结为运行红黑树中的最左边叶子节点所代表的那个进程 ,通过_pick_next_entity()实现,该函数本身不会遍历树找到最左的叶子节点,该值缓存在rb_leftmost字段中,函数返回值就是CFS选择的下一个运行进程。如果返回NULL表示树空没有可运行的进程,这是选择idle任务运行

B:向树中加入进程,发生在进程被唤醒或者通过fork()调用第一次创建进程时。函数enqueue_entity():更新运行时间和其他一些统计数据,然后调用__enqueue_entity(),进行繁重的插入工作,把数据项真的插入到红黑树中。在父节点上调用rb_link_node(),使新插入的进程成为其子节点。函数rb_inserrt_color()更新书的自平衡相关特性。

C:从树中删除进程:删除动作发生在进程堵塞或者终止时。相关函数是dequeue_entity()和__dequeue_entity(),rb_eraase()函数删除进程,更新rb_leftmost缓存,如果删除的是最左节点,还要调用rb_next()按照顺序遍历,找到新的最左节点

(3)调度器入口

  • 进程调度的主要入口点函数是schedule(),他会调用pick_next_task(),pick_nexttask()会以优先级为序,从高到低依次检查每一个调度类,并且从最高优先级的调度类中选择最高优先级的进程,pick_next_task()会返回指向下一个可运行进程的指针,没有的话返回NULL。
  • Pick_next_task()函数实现会调用pick_next_entity(),而该函数会调用__pick_next_entity()函数。

(4)睡眠和唤醒

  • 休眠(被阻塞)的进程处于一个特殊的不可执行状态,为了等待一些事件
  • 休眠的一个常见的原因就是文件I/O
  • 休眠内核的操作都相同:进程把自己标记成休眠状态,从可执行红黑树中移出,放入等待序列,然后调用schedule()选择和执行一个其他进程
  • 唤醒过程:进程被设置为可执行状态,然后再从等待队列中移到可执行红黑树中
  • 休眠两种状态:TASK_INTERRUPTIBLE(接收到信号会被唤醒)和TASK_UNINTERRUPTIBLE(忽略信号)
  • 等待队列:等待队列是由等待某些事件发生的进程组成的简单链表,休眠通过等待队列进行处理,内核用wake_queue_head_t来表示等待队列。等待队列可以通过DECLARE_WAITQUEUE()静态创建,也可以由init_waitqueue_head()动态创建。
  • 进程通过执行以下几个步骤将自己加入到一个等待队列中

1)调用宏DEFINE_WAIT()创建一个等待队列的选项。

2)调用add_wait_queue()把自己加入到队列中。

3)调用prepare_to_wait()方法将进程的状态变更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE。

4)如果状态被设置成TASK_INTERRUPTIBLE,则信号唤醒进程。

5)当进程被唤醒的时候,会再次检查条件是否为真,真则退出循环,否则再次调用schedule()并且一直重复这步动作。

6)当条件满足后,进程将自己设置为TASK_RUNNING并调用finish_wait()方法把自己移出等待序列。

函数inotify_read():负责从通知文件描述符中读取信息。

  • 关于休眠有一点需要注意,存在虚假的唤醒。有时候进程被唤醒并不是因为它所等待的条件达成了才需要用一个循环处理来保证它等待的条件真正达成。
  • 唤醒操作通过函数wake_up()进行,它会唤醒指定的等待队列上的所有进程。它调用try_to_wake_up(),该函数负责将进程设置成TASK_RUNNING状态,调用enqueue_task()将此进程放入红黑树中,如果被唤醒的进程优先级比正在执行的进程优先级高,设置need_resched标志。通常哪段代码促成等待条件达成,它就负责随后调用wake_up()函数
<think>好的,我现在需要帮助用户了解Linux CFS调度器的原理和实现。首先,我得回忆一下自己之前学过的相关知识,同结合用户提供的引用内容来组织回答。用户的问题集中在CFS的原理和实现上,所以需要详细解释这两部分。 首先,我应该从CFS的基本概念开始。根据引用[1]和引用[4],CFS的设计目标是完全公平调度,取代了之前的O(1)调度器。需要提到虚拟运行间(vruntime)这个概念,这是CFS实现公平性的核心。虚拟运行间是通过将实际运行间根据进程的优先级进行加权计算得到的,这样高优先级的进程vruntime增长更慢,低优先级的增长更快。这样调度器在选择下一个进程,只需要选择vruntime最小的,就能保证公平性。 接下来,关于数据结构,引用[3]提到红黑树,这是CFS用来管理可运行进程的数据结构。红黑树是一种自平衡的叉搜索树,能够高效地插入、删除和查找最小节点。这里需要解释为什么选择红黑树,以及如何利用红黑树快速找到vruntime最小进程。 然后,调度过程。根据引用[3],schedule()函数会调用pick_next_task_fair()来选择下一个任务。这里需要描述调度器如何从红黑树中取出最左边的节点(即vruntime最小进程),并将其投入运行。同,当进程被抢占或主动让出CPU,它会被重新插入红黑树中,更新其vruntime。 还需要提到间片的概念。传统调度器有固定的间片,但CFS的动态间片是根据进程数量和优先级动态计算的。引用[3]指出CFS没有真正的间切片,而是根据目标延迟(target latency)来分配CPU间,确保每个进程在目标延迟间内至少运行一次。这可能需要数学公式来说明,比如每个进程间片为target_latency / n,其中n是进程数。 另外,关于进程优先级和权重的关系,引用[2]提到优先级与CFS的关联。在Linux中,优先级通过nice值表示,而每个nice值对应一个权重,这个权重用于计算vruntime。可能需要提到内核中的权重转换表,比如prio_to_weight数组,将nice值转换为权重,从而影响vruntime的计算速度。 在实现细节方面,引用[3]提到了调度类(sched_class)的结构,CFS作为其中的一个调度类,实现了诸如enqueue_task_fair、dequeue_task_fair、pick_next_task_fair等方法。这些方法在调度过程中被调用,管理进程的入队、出队和选择下一个进程。 还需要考虑用户可能的疑问,比如CFS如何处理新创建的进程或唤醒的进程。这,新进程的vruntime会被初始化为当前运行队列中的min_vruntime,避免新进程因vruntime过小而长间占用CPU,导致老进程饥饿。 此外,用户可能对CFS的性能感兴趣。红黑树的O(log n)操作效率较高,即使有大量进程也能有效管理。同CFS的设计避免了传统调度器中的优先级反转问题,通过完全公平的机制来分配CPU间。 最后,总结CFS的优势,比如公平性、高效性、动态调整间片以及良好的交互性响应。这些点可以通过引用中的信息来支持,比如引用[1]提到的CFS引起的争议和其设计目标,引用[4]提到的CFS取代O(1)调度器的背景。 在组织回答,需要结构清晰,分点讲解原理和实现,使用LaTeX公式来展示vruntime的计算和间片分配。同,生成的相关问题要覆盖用户可能进一步询问的方向,比如与O(1)调度器的对比、vruntime的具体计算、红黑树的作用以及性能优化等。</think>### Linux CFS调度器原理与实现 #### 一、核心设计思想 CFS(完全公平调度器)通过**虚拟运行间(virtual runtime, vruntime)**实现进程间的公平性。其核心公式为: $$ vruntime = \frac{实际运行间 \times NICE\_0\_LOAD}{进程权重} $$ 其中,进程权重由优先级(nice值)决定,范围从$2^{15}$到$2^{-20}$[^2]。**vruntime值越小,表示进程应获得更多CPU间**。 #### 、关键数据结构 1. **红黑树(Red-Black Tree)** CFS使用红黑树维护可运行进程队列,按vruntime排序: - 最左节点:当前应调度进程(vruntime最小) - 插入/删除操作间复杂度:$O(\log n)$[^3] 2. **调度实体(sched_entity)** 每个进程/线程对应一个调度实体,包含: ```c struct sched_entity { u64 vruntime; // 虚拟运行间 u64 exec_start; // 本次调度开始间 struct rb_node run_node; // 红黑树节点 }; ``` #### 三、调度流程 1. **选择下一个进程** 调用`pick_next_task_fair()`从红黑树取最左节点: ```c struct task_struct *pick_next_task_fair(struct rq *rq) { struct sched_entity *se = rb_entry(rb_first(&rq->cfs.tasks_timeline)); return task_of(se); } ``` 2. **间片分配** 动态计算间片: $$ time\_slice = \frac{target\_latency \times 进程权重}{总权重} $$ 其中`target_latency`默认为6ms,保证所有进程至少运行一次[^3]。 3. **抢占机制** 周期性检查当前进程vruntime与红黑树最小vruntime差值: $$ if\ (current\_vruntime - min\_vruntime > ideal\_runtime)\ \Rightarrow\ 触发抢占 $$ #### 四、优先级处理 - **nice值映射**:通过`prio_to_weight`数组将nice值转换为权重(共40级) - **实际效果**:nice值每降低1级,CPU间权重增加约10%[^2] #### 五、性能优化 1. **最小vruntime跟踪**:维护`cfs_rq->min_vruntime`避免全树遍历 2. **延迟追踪**:通过`sysctl_sched_latency`控制调度延迟 3. **组调度支持**:实现CPU资源在用户/进程组间的隔离分配 ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值