IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux内核浅析之CFS完全公平调度 -> 正文阅读

[系统运维]Linux内核浅析之CFS完全公平调度

前言

《Linux内核对比学习系列(3)——进程调度》一文中,笔者对 Linux2.6 版本关于进程调度流程进行了梳理。其中提及,该版本通过调度器类实现调度,提升了内核关于调度算法的拓展性。本文对其中一种调度器类,CFS完全公平调度进行浅析

CFS概念

试想原先通过时间片控制进程调度会有什么问题

场景 1 不考虑优先级
A,B,C三个进程,均分60ms,每个进程获得20ms的时间片。60ms能够保证轮执该三个进程

场景 2 考虑优先级
A,B,C三个进程,根据优先级,获得时间片为100ms,20ms,5ms。125ms能够保证轮执该三个进程。优先级只影响了时间片多少,实际上并没有体现“优先”这一概念,真正实现“优先”,必须实现抢占功能。该情况下,若C进程为交互进程,则久久无法响应。

如何才能实现抢占并体现“优先”呢?

首先,优先应该如何定义?自然地,当进程实际执行时间小于内核分配时间,下一次调度时应优先对其进行调度,此为优先。怎么理解?内核分配时间指的是内核综合考量每个进程优先级后给出的每个进程最多执行时间。而实际执行时间为进程实际运行的时间。进程实际执行时间小于内核分配时间就比如我(内核)允许你最多花100块,而你(进程)实际只花了50块,那么你(进程)还有50块的额度可以花。你们(多个进程)谁剩的额度多,我(内核)就让谁优先花钱(被调度)。让剩余额度最多的进程优先执行,还能降低其透支额度的风险。

因此,优先与否,可以转化为判断谁余额最多的问题。对于该策略有两种实现方式

方式一

根据每个进程的优先级,分配进程最多执行时间,优先级越高,最多执行时间越长。每次设置重调度标志flag时,判断当前进程最多执行时间-实际执行时间是否仍最大,否则设置重调度。在执行调度时,则选择就绪队列中,进程最多执行时间-实际执行时间最大的进程进行调度

例如A、B、C根据优先级被分配最多执行时间为100ms,50ms,30ms。当前其实际执行时间分别为90ms,45ms,10ms。调度时应执行C进程

方式二

每个进程最多执行时间一致,根据优先级放大或缩小其实际执行时间,优先级越高,实际执行时间缩小越多,反之放大。每次设置重调度标志flag时,判断当前进程最多执行时间-缩放后的实际执行时间是否仍最大,否则设置重调度。在执行调度时,则选择就绪队列中,进程最多执行时间-缩放后的实际执行时间最大的进程进行调度

例如A、B、C根据优先级被分配最多执行时间为100ms,100ms,100ms。当前其实际执行时间分别为50ms,50ms,50ms。根据优先级缩放实际执行时间,50/0.1,50/1,50/10,优先级越大,分母越大,缩放后的实际执行时间越小。调度时应执行C进程

另一方面,由于每个进程最多执行时间一致,实际上只需要比较缩放的实际执行实际谁小,则谁优先被调度。这便是CFS的逻辑,所以可以看到内核源码实现中,不再考虑时间片的概念,而是专注于对vruntime的管理,vruntime便是上述根据优先级缩放后的实际执行时间

代码实现

根据上述分析,我们理解CFS完全公平调度,只需要跟踪代码看看

  1. 何时会更新vruntime
  2. 如何更新vruntime
  3. 如何利用vruntime

何时更新vruntime

笔者在《Linux内核对比学习系列(3)——进程调度》中简单介绍了调度时机,对于CFS调度器类,时钟中断会触发update_process_times->scheduler_tick->task_tick_fair->entity_tick,文中没有对entity_tick进行展开,事实上,该函数除了调用check_preempt_tick检查是否满足调度条件设置重调度标志外,还会通过update_curr进行vruntime的更新

kernel\sched_fair.c

static void
entity_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr, int queued)
{
	/*
	 * Update run-time statistics of the 'current'.
	 */
	update_curr(cfs_rq);

#ifdef CONFIG_SCHED_HRTICK
	/*
	 * queued ticks are scheduled to match the slice, so don't bother
	 * validating it and just reschedule.
	 */
	if (queued) {
		resched_task(rq_of(cfs_rq)->curr);
		return;
	}
	/*
	 * don't let the period tick interfere with the hrtick preemption
	 */
	if (!sched_feat(DOUBLE_TICK) &&
			hrtimer_active(&rq_of(cfs_rq)->hrtick_timer))
		return;
#endif

	if (cfs_rq->nr_running > 1 || !sched_feat(WAKEUP_PREEMPT))
		check_preempt_tick(cfs_rq, curr);
}

update_curr函数内容很好理解,计算delta_exec(进程实际执行时间增量,注意是增量),并调用__update_curr__update_curr做的就是上述概念提及的步骤,即通过calc_delta_fair缩放delta_exec,加到vruntime

kernel\sched_fair.c

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

	if (unlikely(!curr))
		return;

	/*
	 * Get the amount of time the current task was running
	 * since the last time we changed load (this cannot
	 * overflow on 32 bits):
	 */
	delta_exec = (unsigned long)(now - curr->exec_start);
	if (!delta_exec)
		return;

	__update_curr(cfs_rq, curr, delta_exec);
	curr->exec_start = now;

	if (entity_is_task(curr)) {
		struct task_struct *curtask = task_of(curr);

		trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
		cpuacct_charge(curtask, delta_exec);
		account_group_exec_runtime(curtask, delta_exec);
	}
}
static inline void
__update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
	      unsigned long delta_exec)
{
	unsigned long delta_exec_weighted;

	schedstat_set(curr->exec_max, max((u64)delta_exec, curr->exec_max));

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

	curr->vruntime += delta_exec_weighted;
	update_min_vruntime(cfs_rq);
}
static inline unsigned long
calc_delta_fair(unsigned long delta, struct sched_entity *se)
{
	if (unlikely(se->load.weight != NICE_0_LOAD))
		delta = calc_delta_mine(delta, NICE_0_LOAD, &se->load);

	return delta;
}

至此,何时更新vruntime及如何更新vruntime梳理完成(ps:可能还有其他更新时机,就不深入探究了

如何利用vruntime

自然地,vruntime是作为CFS调度器判断是否需要调度的条件,与重调度标志flag的设置有关。因此我们根据在《Linux内核对比学习系列(3)——进程调度》提及的内容,定位check_preempt_tick

check_preempt_tick首先判断cfs的就绪队列cfs_rq上是否还有其他进程,如果有说明可能需要调度。通过__pick_next_entity获取下一个被调度实体,判断两者 vruntime 差距是否大于 ideal_runtime ,若大于则设置重调度标志。意味着,如果下一个被调度实体还是自己,则不需要调度,如果是别的进程,但两者实际运行时间相差不大,也不会调度,如此一来也能够避免频繁调度,减少上下文切换的次数,提升执行效率。(妙啊

static void
check_preempt_tick(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
	// -------省略部分代码----------

	if (cfs_rq->nr_running > 1) {
		struct sched_entity *se = __pick_next_entity(cfs_rq);
		s64 delta = curr->vruntime - se->vruntime;

		if (delta > ideal_runtime)
			resched_task(rq_of(cfs_rq)->curr);
	}
}

根据先前的分析可知,__pick_next_entity 所选出的调度实体 se 必须是当前所有进程中 vruntime 最小的。那么它如何才能极高效率地选出最小值呢,当然是使用红黑树啦!

代码较简单,直接从就绪队列里取个红黑树节点,再通过 container_of 获取到对应 se 即可。意味着,提前已经将红黑树更新,并计算出当前 vruntime 最小的节点。

kernel\sched_fair.c

#define	rb_entry(ptr, type, member) container_of(ptr, type, member) //include\linux\rbtree.h

static struct sched_entity *__pick_next_entity(struct cfs_rq *cfs_rq)
{
	struct rb_node *left = cfs_rq->rb_leftmost;

	if (!left)
		return NULL;

	return rb_entry(left, struct sched_entity, run_node);
}

我们回顾前述,实际上在更新 vruntime 时,就必须更新红黑树了,见 __update_curr 中的update_min_vruntime(),具体就不展开了。同时,进程在创建时也应插入CFS的红黑树中,具体地如书上所言会调用enqueue_entity(),删除时执行dequeue_entity(),在此就不展开探究了

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

	if (cfs_rq->curr)
		vruntime = cfs_rq->curr->vruntime;

	if (cfs_rq->rb_leftmost) {
		struct sched_entity *se = rb_entry(cfs_rq->rb_leftmost,
						   struct sched_entity,
						   run_node);

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

	cfs_rq->min_vruntime = max_vruntime(cfs_rq->min_vruntime, vruntime);
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-04-07 23:07:00  更:2022-04-07 23:11:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 22:21:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码