首页 > 编程语言 >eevdf 内核调度器 源码分析 (1)

eevdf 内核调度器 源码分析 (1)

时间:2024-11-19 19:19:22浏览次数:3  
标签:curr rq 源码 内核 vruntime eevdf avg cfs se

eevdf 内核调度器 源码分析 (1)

简介

本文会从源码角度,简单分析eevdf(Earliest Eligible Virtual Deadline First)内核调度器。阅读本文需要一定cfs调度器的基础知识。

vruntime的变化

在cfs中,有一个虚拟运行时间的概念 vruntime, 参考函数calc_delta_fair。和真实时间相比,可以大体看作 虚拟运行时间 = 真实运行时间/权重(实际情况会复杂一些)。cfs调度器会尽量让每个调度实体虚拟时间运行时间相同,而因为每一个调度实体的权重不同,所以权重大的调度实体,分得的真实时间更多。
eevdf这个函数并没有变化。记一个任务i, 虚拟运行时间v_i, 权重为w_i, 真实运行时间s_i, 则关系为:

v_i = s_i / w_i

eevdf相比基础的cfs,又多了一个 system virtual time的概念,为:
在这里插入图片描述
(不会打公式,而且懒得学)
看着出现了积分,但是逻辑很简单,相当于系统所有任务的,虚拟运行时间。
真实时间在上面公式上走过了t, 但是为了好看统一一下记为s, 假设在这段时间内,没有任何的任务停止或者加入,那么下面的那个权重w_i 加和,可以记为W,W = \Sum w_i,W是系统在这段时间内,所有任务的权重之和,那么这个V就能得到:

V=s/W

对比上面的v_i = s_i / w_i, 可以看到这个V,相当于一个总任务的虚拟运行时间。
当然,系统中的任务是不停加入或停止的,所以正规的需要用积分表示,当然如果只是简单理解,用V=s/W就可以了。
那么这个V有什么用呢,这是在真实时间轴的基础上,增添了一个虚拟时间轴,虚拟时间轴的快慢与系统负载有关。通过这个虚拟时间轴,运行时间的判断就全部来到了虚拟时间上,这也是eevdf的基础。
s是真实时间,可以写作\Sum s_i * w_i, 即这段时间每一个任务经历的真实时间(仍然假设不会有任务停止开始,这样会容易理解很多,下文不再提起)
那么公式可以变为

    \Sum v_i * w_i
V = -------------- 
           W

不过 v_i * w_i 是可能溢出的,所以需要剪去一个偏移,公式为

      \Sum ((v_i - v0) + v0) * w_i   \Sum (v_i - v0) * w_i
  V = ---------------------------- = --------------------- + v0
                   W                            W

最终这个V的计算,体现在代码中,将其分成三部分来进行计算:

                     v0 := cfs_rq->min_vruntime
  \Sum (v_i - v0) * w_i := cfs_rq->avg_vruntime
               \Sum w_i := cfs_rq->avg_load

而将这三处统一起来的函数,为avg_vruntime

u64 avg_vruntime(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	s64 avg = cfs_rq->avg_vruntime;
	long load = cfs_rq->avg_load;

	if (curr && curr->on_rq) {
		unsigned long weight = scale_load_down(curr->load.weight);

		avg += entity_key(cfs_rq, curr) * weight;
		load += weight;
	}

	if (load) {
		/* sign flips effective floor / ceil */
		if (avg < 0)
			avg -= (load - 1);
		avg = div_s64(avg, load);
	}

	return cfs_rq->min_vruntime + avg;
}

可以看出,avg_vruntime基本就是上述公式的一个计算。

到底什么是Eligible?

Eligible翻译为有资格的,合格的。eevdf每次选择,都会在合格的任务中选取有最早deadline的任务,所以合格,就是一个必要不充分条件,那么到底什么任务是合格任务呢?大体可以总结为,在某个时间点,这个任务还没有把上次分配给它的时间用完,就为合格任务。

判断一个任务是否合格的核心任务是entity_eligible,这个函数在选择下一个运行调度实体时被频繁使用。

static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	return (s64)(se->vruntime - cfs_rq->min_vruntime);
}

static int vruntime_eligible(struct cfs_rq *cfs_rq, u64 vruntime)
{
	struct sched_entity *curr = cfs_rq->curr;
	s64 avg = cfs_rq->avg_vruntime;
	long load = cfs_rq->avg_load;

	if (curr && curr->on_rq) {
		unsigned long weight = scale_load_down(curr->load.weight);

		avg += entity_key(cfs_rq, curr) * weight;
		load += weight;
	}

	return avg >= (s64)(vruntime - cfs_rq->min_vruntime) * load;
}

int entity_eligible(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	return vruntime_eligible(cfs_rq, se->vruntime);
}

entity_eligible 判断逻辑很简单,但是想要读明白必须再了解几个eevdf的核心概念。

首先需要介绍lag这个概念,对于一个任务i,

lag_i = S_i - s_i

其中

S_i = w_i * V

w_i * V 是什么意思?在上面中,我们知道,

V = s/W 

所以

S_i = w_i * s / W 

其实也就是,各个进程通过权重,将真实时间s分割了,比如两个权重为3和7的进程,经过10s后,这时虚拟时间才过去1s,理论上权重为3的应该给他3秒钟,权重为7的理论给他7s。所以,S_i 是这个进程的应得时间。
考虑到一些进程可能停止或开始,一段时间的S,正规表示为

S_i(t1; t2) = w_i * (V_t2 -  V_t1)

而lag_i 则是进程的应得时间剪去实际运行时间。
如果lag_i 大于0,说明进程比较“委屈”,本来要给他更多的时间,但是因为一些情况进程没有使用到,而lag_i 小于0 ,则说明进程较为“讨人厌”,贪婪的占用了更多时间的cpu。

那么什么是eligible?说穿了,就是lag 大于等于0。也就是这个进程的应得时间还没有使用完,可以继续被调度, 有了这个理论后,entity_eligible这个函数的逻辑就可以被推导出来了。

lag_i = S_i - s_i  = w_i*(V - v_i)  
--> V >= v_i
		\Sum (v_i - v0) * w_i
-->  ---------------------      + v0 >= v_i
					W
--> \Sum (v_i - v0)*w_i >= (v_i - v0)*(\Sum w_i)

同时,内核中记录lag的部分如下

static s64 entity_lag(u64 avruntime, struct sched_entity *se)
{
	s64 vlag, limit;

	vlag = avruntime - se->vruntime;
	limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se);

	return clamp(vlag, -limit, limit);
}

static void update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	SCHED_WARN_ON(!se->on_rq);

	se->vlag = entity_lag(avg_vruntime(cfs_rq), se);
}

看出来,内核中记录的是vlag而不是lag,类似于vruntime,其实就是lag 除权重,

lag_i = S_i - s_i  = w_i*(V - v_i)
vlag_i = V - v_i

eligible time 和 deadline time

一个任务是否是eligible,上面已经推导清了,但是eligible time又是什么。
这里有一个正经的定义

t^i_0 代表着这个任务active的时间, t代表着任务又请求一次新的时间。
t^i_0 可以理解为开始时间,在这之前状态就认为一切刚开始。那么t如何理解呢?这里和普通的cfs可能有些歧义。在一些调度中,是任务自己请求运行资源的,某个任务到时间t时,可能会再请求运行1s这样。当然,放到内核调度器中,这个是内核tick代劳的。每个tick到来,内核会为每个任务请求新的运行时间。
所以,这个公式可以翻译为,一个任务刚从时间t^i_0开始运行后,经历的第一个tick,此时时间为t, 那么 任务在这个时间段内,真实的运行时间,
相当于 t^i_0 到 e这段的真实时间。
把S_i(t1; t2) = w_i * (V_t2 - V_t1)带入,则可以得到
在这里插入图片描述
如果e < t,毫无疑问 S_i(t^i_0 , e ) < S_i(t^i_0 , t ), 也就是当时间t时,应当获取的运行时间会更多,这个任务时则也就更“客气”,该用的时间没有用,自然可以让其继续运行。
如果e > t, 当时间t时,发现这个任务较为“贪婪”,提前预支了自己资源,就不会给它调度。那么什么时候会给他调度呢,那就需要等到时间e了,因为到时间e, 任务就平衡了。

那么deadline time又是什么呢,定义为

S_i (e ,d) = r

这个公式可以转为
在这里插入图片描述
这个公式又是什么意思?首先 e 是合规时间,在这个时间节点,等到下次请求时间产生时,已经可以申请新的时间了。
r是申请的时间,事实上,在内核中这个时间是一个固定值。
deadline time的含义为,在e 到 d的这个时间点,应当把这个申请的r完成(当然,至于实际完不完的成,是另一回事了)。
为什么是从e开始?
参考上面,如果e<t ,进程在t时已经是“委屈”了,所以我们需要补偿它,就让他deadline提前一会,如果还是通过 t + t/w_i ,对于这个进程就不公平了,e> t同理。
所以,有了下面这三个公式,这也是eevdf的核心,即任务总是在deadline time前,将申请的r用完(即deadline定义保证到deadline时应得时间r,同时ve k 到ve k+1 应得正好使用了r),然后进入下一个e 。
在这里插入图片描述
那么内核中,面对复杂的真实情况,尤其是怎么保证在ve(k+1)时用完r, 是怎么实现的呢, 首先看核心函数 update_curr,这里只截取核心部分。

static void update_curr(struct cfs_rq *cfs_rq)
{
	struct sched_entity *curr = cfs_rq->curr;
	u64 now = rq_clock_task(rq_of(cfs_rq));
	u64 delta_exec;

	if (unlikely(!curr))
		return;

	delta_exec = now - curr->exec_start;
	if (unlikely((s64)delta_exec <= 0))
		return;

	curr->exec_start = now;

	curr->sum_exec_runtime += delta_exec;
	schedstat_add(cfs_rq->exec_clock, delta_exec);

	curr->vruntime += calc_delta_fair(delta_exec, curr);
	update_deadline(cfs_rq, curr);
	update_min_vruntime(cfs_rq);


	account_cfs_rq_runtime(cfs_rq, delta_exec);
}

首先是计算 sum_exec_runtime 和 vruntime,这一部分和普通的cfs没有太大区别。
update_deadline 这里就不同了

static void update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
	if ((s64)(se->vruntime - se->deadline) < 0)
		return;

	/*
	 * For EEVDF the virtual time slope is determined by w_i (iow.
	 * nice) while the request time r_i is determined by
	 * sysctl_sched_base_slice.
	 */
	se->slice = sysctl_sched_base_slice;

	/*
	 * EEVDF: vd_i = ve_i + r_i / w_i
	 */
	se->deadline = se->vruntime + calc_delta_fair(se->slice, se);

	/*
	 * The task has consumed its request, reschedule.
	 */
	if (cfs_rq->nr_running > 1) {
		resched_curr(rq_of(cfs_rq));
		clear_buddies(cfs_rq, se);
	}
}

首先判断,如果 se->vruntime 小于 se->deadline 直接就返回,因为这次deadline 还没结束,用不到更新下一次deadline 。
然后就是一个固定的 sysctl_sched_base_slice,这个值以后再详细分析。
然后就看到了,deadline的更新。
se->deadline = se->vruntime + calc_delta_fair(se->slice, se);
对应到公式,vd_i = ve_i + r_i / w_i, 其他都好理解,为什么ve_i 会是 se->vruntime呢。
这里有一个隐含信息,那就是se->vruntime 已经大于等于 se->deadline了。并且我们有
在这里插入图片描述
所以se->vruntime 是大于等于ve k+1的,同时,vruntime的更新是很频繁的,我们就假设其是实时更新的,当大于deadline的一瞬,就认为是下一个ve了。
vruntime记录着任务的虚拟运行时间,当大于deadline的时候,说明正好增加了r/w(可能稍微大一点),也就是任务真实运行了r ,而应得运行时间,正也是r ,在这个时间节点,系统认为这个任务使用r这个申请时间是合理的,没问题的。
对于一个调度实体而言,se的vruntime记录着se的虚拟运行时间,deadline记录着截止时间,vruntime更新频繁,每当达到deadline,就让deadline再加 r/w ,对应vruntime可以再运行r/w虚拟时间。每当两者一对应,就再往后走。
所以内核在实现eevdf时,ve这个概念隐含在vruntime中了。

再看下一个函数 update_min_vruntime。之前说过在计算V时,为了防止溢出,会减去一个v0,不过v0用处不止于此,进程创建睡眠唤醒都是需要的。

static inline
void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta)
{
	/*
	 * v' = v + d ==> avg_vruntime' = avg_runtime - d*avg_load
	 */
	cfs_rq->avg_vruntime -= cfs_rq->avg_load * delta;
}

static u64 __update_min_vruntime(struct cfs_rq *cfs_rq, u64 vruntime)
{
	u64 min_vruntime = cfs_rq->min_vruntime;
	/*
	 * open coded max_vruntime() to allow updating avg_vruntime
	 */
	s64 delta = (s64)(vruntime - min_vruntime);
	if (delta > 0) {
		avg_vruntime_update(cfs_rq, delta);
		min_vruntime = vruntime;
	}
	return min_vruntime;
}

static void update_min_vruntime(struct cfs_rq *cfs_rq)
{
	struct sched_entity *se = __pick_root_entity(cfs_rq);
	struct sched_entity *curr = cfs_rq->curr;
	u64 vruntime = cfs_rq->min_vruntime;

	if (curr) {
		if (curr->on_rq)
			vruntime = curr->vruntime;
		else
			curr = NULL;
	}

	if (se) {
		if (!curr)
			vruntime = se->min_vruntime;
		else
			vruntime = min_vruntime(vruntime, se->min_vruntime);
	}

	/* ensure we never gain time by being placed backwards. */
	u64_u32_store(cfs_rq->min_vruntime,
		      __update_min_vruntime(cfs_rq, vruntime));
}

对比之前的cfs代码,有两处不同,1是不找left节点,因为在eevdf中,最左节点是最近deadline,而不是最小vruntime,2是,找完后,会对avg_vruntime进行更新,原因可以再看V的实现公式,v0变化了,自然也需要变化一下。

结语

本文简要介绍了eevdf的一些基础概念,公式推导,代码实现,不涉及进程睡眠唤醒等逻辑,后文会对进程睡眠等逻辑进行讲解。

eevdf 内核调度器 源码分析 (2)

eevdf 内核调度器 源码分析 (3)

标签:curr,rq,源码,内核,vruntime,eevdf,avg,cfs,se
From: https://blog.csdn.net/father_yingying/article/details/143855768

相关文章

  • 基于SpringBoot+Vue的旅游网站管理系统设计与实现毕设(文档+源码)
    目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:         大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的旅游网站管理系统,项目源码请点击文章末尾联系我哦~目前有各类成品毕设JavaWeb......
  • 基于SpringBoot+Vue的论坛管理系统设计与实现毕设(文档+源码)
    目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:         大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的论坛管理系统,项目源码请点击文章末尾联系我哦~目前有各类成品毕设JavaWeb SSM......
  • 计算机毕设源码 python-基于flask在线考试系统
    标题:python-基于flask在线考试系统设计一个基于Flask框架的在线考试系统,需要考虑考生、教师和管理员的不同需求,确保系统的易用性、公平性和安全性。以下是一个典型的在线考试系统的主要功能模块:1.用户注册与登录•注册:用户可以通过手机号码、邮箱或社交账号注册。•登录:用......
  • 【Python】30个Python爬虫的实战项目!!!(附源码)
    Python爬虫是数据采集自动化的利器。本文精选了30个实用的Python爬虫项目,从基础到进阶,每个项目都配有完整源码和详细讲解。通过这些项目的实战,可以全面掌握网页数据抓取、反爬处理、并发下载等核心技能。一、环境准备在开始爬虫项目前,需要安装以下Python库:......
  • nano框架源码笔记
    nano是开源游戏服务器框架,TODO介绍。从examples/demo/chat/main.go开始看起。group.goGrouprepresentsasessiongroupwhichusedtomanageanumberofsessions,datasendtothegroupwillsendtoallsessioninit.包含四个字段:mu互斥量,status表示当前chennel......
  • 电商ERP系统源码出售
    本人从事电商ERP软件开发有10余年了,先介绍一下手头的这套电商ERP源码核心功能该套电商ERP系统类似聚水潭、万里牛这种,是B/S结构的电商ERP系统,该系统核心功能包括电商ERP常见的比如:1、仓储管理说明:该功能主要是提供给店主管理自己仓库的库存的,子功能包括比如上架、下架、出库,并......
  • 童年游戏——用Python写一个天天酷跑(附源码)
    写出来的效果图就是这样了下面就更新一下全部的代码吧还是老样子先定义importpygame,sysimportrandom写一下游戏配置width=1200#窗口宽度height=508#窗口高度size=width,heightscore=None#分数myFont=myFont......
  • 基于SSM的自驾游网站【附源码+文档】
    ......
  • 基于Springboot公司考勤管理系统【附源码+文档】
    ......
  • Springboot大学生个人财务管理系统13bek(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表学校简介,学生,省钱妙招,收支类型,收入,消费等级,消费预算,借入记录,归还记录,支出开题报告内容一、研究背景随着社会经济的发展和大学教育的普及,大学生经济活......