linux cfs 源码,CFS调度器

本文详细解读了Linux的CFS调度器如何通过虚拟时间实现公平调度,介绍了调度类、调度实体、就绪队列等关键概念,以及nice值和权重的作用。重点探讨了CFS调度器的工作原理、数据结构及其在Linux内核中的应用。
摘要由CSDN通过智能技术生成

CFS调度的基本思想

CFS调度器的目标是保证每一个进程的完全公平调度。 CFS调度器和以往的调度器不同之处在于没有时间片的概念,而是分配cpu使用时间的比例。理想状态下每个进程都能获得相同的时间片,并且同时运行在CPU上,但实际上一个CPU同一时刻运行的进程只能有一个。 也就是说,当一个进程占用CPU时,其他进程就必须等待。CFS为了实现公平,必须惩罚当前正在运行的进程,以使那些正在等待的进程下次被调度。CFS调度引入虚拟时间代替实际时间,每次在就绪队列中选择虚拟时间最小的task,实现公平.

CFS调度的基本概念

调度类

ac257c87c06d

调度器组织结构

从Linux 2.6.23开始,Linux引入scheduling class的概念,目的是将调度器模块化。这样提高了扩展性,添加一个新的调度器也变得简单起来。一个系统中还可以共存多个调度器。在Linux中,将调度器公共的部分抽象,使用struct sched_class结构体描述一个具体的调度类。系统核心调度代码会通过struct sched_class结构体的成员调用具体调度类的核心算法。先简单的介绍下struct sched_class部分成员作用。

调度类 描述 调度策略

dl_sched_class deadline调度器 SCHED_DEADLINE

rt_sched_class 实时调度器 SCHED_FIFO、SCHED_RR

fair_sched_class 完全公平调度器 SCHED_NORMAL、SCHED_BATCH

idle_sched_class idle task SCHED_IDLE

struct sched_class {

const struct sched_class *next;//指向下一个调度类,按照优先级

void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);//向该调度类的runqueue(就绪队列)中添加进程

void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);//向该调度类的runqueue(就绪队列)中删除进程

void (*check_preempt_curr)(struct rq *rq, struct task_struct *p, int flags);//当一个进程被唤醒或者创建的时候,需要检查当前进程是否可以抢占当前cpu上正在运行的进程,如果可以抢占需要标记TIF_NEED_RESCHED flag。

//从runqueue中选择一个最适合运行的task

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

struct task_struct *prev,

struct rq_flags *rf);

普通进程的优先级及虚拟时间

CFS调度器针对优先级又提出了nice值的概念,其实和权重是一一对应的关系。nice值就是一个具体的数字,取值范围是[-20, 19]。数值越小代表优先级越大,同时也意味着权重值越大,nice值和权重之间可以互相转换。内核提供了一个表格转换nice值和权重。

const int sched_prio_to_weight[40] = {

7527 /* -20 */ 88761, 71755, 56483, 46273, 36291,

7528 /* -15 */ 29154, 23254, 18705, 14949, 11916,

7529 /* -10 */ 9548, 7620, 6100, 4904, 3906,

7530 /* -5 */ 3121, 2501, 1991, 1586, 1277,

7531 /* 0 */ 1024, 820, 655, 526, 423,

7532 /* 5 */ 335, 272, 215, 172, 137,

7533 /* 10 */ 110, 87, 70, 56, 45,

7534 /* 15 */ 36, 29, 23, 18, 15,

7535};

虚拟运行时间是通过进程的实际运行时间和进程的权重(weight)计算出来的。在CFS调度器中,将进程优先级这个概念弱化,而是强调进程的权重。一个进程的权重越大,则说明这个进程更需要运行,因此它的虚拟运行时间就越小,这样被调度的机会就越大

虚拟运行时间的计算公式:

vriture_runtime = wall_time * 1024/weight

virtue_runtime — 虚拟运行时间

wall_time - 实际运行时间

由公式可以看出nice为0的virtue_time与time一致,且weight越大,相同的实际运行时间下,虚拟时间越小,越会被调度。

下图给实际运行时间,虚拟运行时间,nice值之间的关系

ac257c87c06d

实际运行时间,虚拟运行时间,nice之间的关系

系统中使用struct load_weight结构体描述进程的权重信息。weight代表进程的权重,inv_weight等于2^ 32/weight。

struct load_weight {

unsigned long weight;

u32 inv_weight;

};

调度实体

Linux通过struct task_struct结构体描述每一个进程。但是调度类管理和调度的单位是调度实体,并不是task_struct。在支持组调度的时候,一个组也会抽象成一个调度实体,它并不是一个task。所以,我们在struct task_struct结构体中可以找到以下不同调度类的调度实体。

struct task_struct {

struct sched_entity se;

struct sched_rt_entity rt;

struct sched_dl_entity dl;

/* ... */

}

struct sched_entity {

struct load_weight load;//权重信息

struct rb_node run_node;//就绪队列红黑树上的挂载点

unsigned int on_rq;//调度实体se加入就绪队列后,on_rq标志置为1,从就绪队列删除,on_rq置为0

u64 sum_exec_runtime;//调度实体已经运行的实际时间总和

u64 vruntime;//调度实体已经运行的虚拟时间总和

};

就绪队列

系统中每个CPU都会有一个全局的就绪队列(cpu runqueue),使用struct rq结构体描述,它是per-cpu类型,即每个cpu上都会有一个struct rq结构体。每一个调度类也有属于自己管理的就绪队列。例如,struct cfs_rq是CFS调度类的就绪队列,管理就绪态的struct sched_entity调度实体,后续通过pick_next_task接口从就绪队列中选择最适合运行的调度实体(虚拟时间最小的调度实体)。struct rt_rq是实时调度器就绪队列。struct dl_rq是Deadline调度器就绪队列。

struct rq {

struct cfs_rq cfs;

struct rt_rq rt;

struct dl_rq dl;

};

struct rb_root_cached {

struct rb_root rb_root;//红黑树的根节点

struct rb_node *rb_leftmost;//红黑树的最左边节点

};

struct cfs_rq {

struct load_weight load;//就绪队列管理的所有调度实体权重之和

unsigned int nr_running;//调度实体的个数

u64 min_vruntime;//就绪队列上的最小虚拟时间

struct rb_root_cached tasks_timeline;//就绪队列红黑树的信息

};

原理总结

Linux中所有的进程使用task_struct描述。task_struct包含很多进程相关的信息(例如,优先级、进程状态以及调度实体等)。但是,每一个调度类并不是直接管理task_struct,而是引入调度实体的概念。CFS调度器使用sched_entity跟踪调度信息。CFS调度器使用cfs_rq跟踪就绪队列信息以及管理就绪态调度实体,并维护一棵按照虚拟时间排序的红黑树。tasks_timeline->rb_root是红黑树的根,tasks_timeline->rb_leftmost指向红黑树中最左边的调度实体,即虚拟时间最小的调度实体(为了更快的选择最适合运行的调度实体,因此rb_leftmost相当于一个缓存)。每个就绪态的调度实体sched_entity包含插入红黑树中使用的节点rb_node,同时vruntime成员记录已经运行的虚拟时间。我们将这几个数据结构简单梳理,如下图所示.

ac257c87c06d

数据结构之间的关系

源码分析

ac257c87c06d

进程创建

ac257c87c06d

sched_fork()

ac257c87c06d

take_fork_fair()

ac257c87c06d

wake_up_new_task()

ac257c87c06d

wake_up_new_task()

ac257c87c06d

进程调度

ac257c87c06d

pick_new_task_fair()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值