<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Scheduler | Easton Man's Blog</title>
	<atom:link href="https://blog.eastonman.com/blog/tag/scheduler/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.eastonman.com</link>
	<description>临渊羡鱼，不如退而结网</description>
	<lastBuildDate>Fri, 26 Feb 2021 14:24:50 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.4</generator>

<image>
	<url>https://blog.eastonman.com/wp-content/uploads/2021/02/cropped-Logo-e1613298891313-32x32.png</url>
	<title>Scheduler | Easton Man's Blog</title>
	<link>https://blog.eastonman.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Linux内核CFS调度器的实现</title>
		<link>https://blog.eastonman.com/blog/2021/02/cfs/</link>
					<comments>https://blog.eastonman.com/blog/2021/02/cfs/#respond</comments>
		
		<dc:creator><![CDATA[Easton Man]]></dc:creator>
		<pubDate>Fri, 26 Feb 2021 14:24:47 +0000</pubDate>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[CFS]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Scheduler]]></category>
		<guid isPermaLink="false">https://blog.eastonman.com/?p=477</guid>

					<description><![CDATA[<p>预计阅读时间： 5 分钟本文简易而快速地介绍了Linux内核中完全公平类调度类CFS调度器的原理和实现。 原理 [&#8230;]</p>
The post <a href="https://blog.eastonman.com/blog/2021/02/cfs/">Linux内核CFS调度器的实现</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></description>
										<content:encoded><![CDATA[<p class="wpwc-reading-time">预计阅读时间： 5 分钟</p>
<p class="wp-block-paragraph">本文简易而快速地介绍了Linux内核中完全公平类调度类CFS调度器的原理和实现。</p>



<h2 class="wp-block-heading">原理</h2>



<p class="wp-block-paragraph">其实CFS（Completely Fair Scheduler）的核心原理很简单，就是使得每个进程都尽可能“公平”地获得运行时间。因此每次都选择过去运行得最少得进程运行。当然，作为一个调度器，它要满足的需求远不止于此。Linux的抢占式进程和在完全公平类<code>fair_sched_class</code>中支持优先级等一系列特性，使得CFS的设计变得比简单的选出运行时间最少要复杂得多。但是不要着急，我们一步一步来理解CFS的原理。</p>



<h2 class="wp-block-heading">最小运行时间</h2>



<p class="wp-block-paragraph">为了避免过度频繁的抢占发生，Linux内核设置了每个Task（进程）的最小运行时间（或称运行时间粒度），在这个时间内，这个进程的CPU资源是不可被抢占的。除非进程主动让出CPU或者执行了阻塞的系统调用，一般而言进程都可以执行最少执行完这个时间。最小运行时间可以通过内核参数<code>sched_min_granularity_ns</code>来查看。</p>



<pre class="EnlighterJSRAW" data-enlighter-language="shell" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">cat /proc/sys/kernel/sched_min_granularity_ns
3000000</pre>



<h2 class="wp-block-heading">时间片</h2>



<p class="wp-block-paragraph">CFS通过引入权重来保证高优先级的进程能够获得更多的CPU时间，进程间按照权重比例分配时间片。调度周期内分配给进程的运行时间按照这个公式计算：</p>



<p class="has-text-align-center wp-block-paragraph"><strong>运行时间tn=调度周期T * 进程权重w / 运行队列中全部进程的权重之和S</strong></p>



<p class="wp-block-paragraph">权重是一个和nice值有关的量，现在在Linux内核中的定义位于<a href="https://github.com/torvalds/linux/blob/2c87f7a38f930ef6f6a7bdd04aeb82ce3971b54b/kernel/sched/core.c#L9516" target="_blank" rel="noreferrer noopener" title="https://github.com/torvalds/linux/blob/2c87f7a38f930ef6f6a7bdd04aeb82ce3971b54b/kernel/sched/core.c#L9516">kernel/sched/core.c#L9516</a>（文章发布时），数值如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">const int sched_prio_to_weight[40] = {
 /* -20 */     88761,     71755,     56483,     46273,     36291,
 /* -15 */     29154,     23254,     18705,     14949,     11916,
 /* -10 */      9548,      7620,      6100,      4904,      3906,
 /*  -5 */      3121,      2501,      1991,      1586,      1277,
 /*   0 */      1024,       820,       655,       526,       423,
 /*   5 */       335,       272,       215,       172,       137,
 /*  10 */       110,        87,        70,        56,        45,
 /*  15 */        36,        29,        23,        18,        15,
};</pre>



<p class="wp-block-paragraph">其中nice值为0的权重NICE_0_LOAD=1024，nice值每差1，权重大约差1.25倍。这里的1.25计算依据来源于nice值差1，运行时间相差10%这样的设计。</p>



<h2 class="wp-block-heading">虚拟运行时间</h2>



<p class="wp-block-paragraph">解决完每次分配多少时间的问题后，还有一个问题需要解决，就是下一个运行的进程是谁？CFS实现的原则是“完全的公平”，那么高优先级的进程如何保证多一点的运行时间？这就要引入一个概念，叫“虚拟运行时间”了。假设我们希望有如下的情况出现：</p>



<p class="has-text-align-center wp-block-paragraph"><strong>高优先级进程运行15ms=低优先级运行5ms</strong></p>



<p class="wp-block-paragraph">那么我们就将这个相等的量设置为一个“虚拟运行时间”，这样就可以保证高优先级进程运行的时间几乎总是低优先级的3倍。在Linux中，这个虚拟运行时间与实际运行时间的关系就是刚刚提到的权重：</p>



<p class="has-text-align-center wp-block-paragraph"><strong>虚拟运行时间=真实运行时间 * NICE_0_LOAD / 进程的权重</strong></p>



<p class="wp-block-paragraph">Linux在调度元素中定义了vruntime变量（<a href="https://github.com/torvalds/linux/blob/66f73fb3facd42d0a7c899d7f4c712332b28499a/include/linux/sched.h#L453" target="_blank" rel="noreferrer noopener" title="https://github.com/torvalds/linux/blob/66f73fb3facd42d0a7c899d7f4c712332b28499a/include/linux/sched.h#L453">include/linux/sched.h#L453</a>），用于记录进程的累计虚拟运行时间，代码如下：</p>



<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">struct sched_entity {
	/* For load-balancing: */
	struct load_weight		load;
	struct rb_node			run_node;
	struct list_head		group_node;
	unsigned int			on_rq;

	u64				exec_start;
	u64				sum_exec_runtime;
	u64				vruntime;
	u64				prev_sum_exec_runtime;

	u64				nr_migrations;

	struct sched_statistics		statistics;
        ...
}</pre>



<p class="wp-block-paragraph">进程每次运行完毕后就会更新vruntime变量，至于如何挑选出vruntime最少的进程，这将由红黑树完成。</p>



<h2 class="wp-block-heading">红黑树</h2>



<p class="wp-block-paragraph">红黑树是一种自平衡的二叉树，最左的叶子节点永远是key最小的节点。红黑树通过插入（更新）和删除时的操作保证这些原子操作之间红黑树永远是平衡的。</p>



<p class="wp-block-paragraph">具体的操作参见<a href="https://www.jianshu.com/p/e136ec79235c" title="https://www.jianshu.com/p/e136ec79235c">https://www.jianshu.com/p/e136ec79235c</a></p>



<p class="wp-block-paragraph">内核将调度队列上的进程按照vruntime排列成红黑树，这样选取最小vruntime的进程就变为了简单地选出最左边的叶子节点了。</p>



<h2 class="wp-block-heading">最小vruntime</h2>



<p class="wp-block-paragraph">内核除了红黑树以外还维护一个min_vruntime变量，以记录此时最小的虚拟运行时间。</p>



<p class="wp-block-paragraph">为什么需要这个min_vruntime？我们设想以下几种状况：</p>



<ul class="wp-block-list"><li>新的进程加入了红黑树，那么它的vruntime应当是多少？</li><li>休眠了10万年的进程等待到了它要求的事件，现在被唤醒了，它还保持原有的vruntime吗？</li><li>进程被负载均衡迁移到另一个CPU上（另一个调度队列），那么它的vruntime如何改变？</li></ul>



<p class="wp-block-paragraph">由此可以看出，min_vruntime的作用就是帮助Linux内核解决这些情况。</p>



<h2 class="wp-block-heading">深入阅读</h2>



<ul class="wp-block-list"><li><a href="https://github.com/torvalds/linux/tree/master/kernel/sched" target="_blank" rel="noreferrer noopener" title="https://github.com/torvalds/linux/tree/master/kernel/sched">https://github.com/torvalds/linux/tree/master/kernel/sched</a></li><li><a href="https://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html" target="_blank" rel="noreferrer noopener" title="https://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html">https://www.kernel.org/doc/html/latest/scheduler/sched-design-CFS.html</a></li><li>有意思的BFS<a href="https://www.utappia.org/2012/02/bfs-vs-cfs-some-personal-observations.html" target="_blank" rel="noreferrer noopener" title="https://www.utappia.org/2012/02/bfs-vs-cfs-some-personal-observations.html">https://www.utappia.org/2012/02/bfs-vs-cfs-some-personal-observations.html</a></li></ul>



<p class="wp-block-paragraph"></p>The post <a href="https://blog.eastonman.com/blog/2021/02/cfs/">Linux内核CFS调度器的实现</a> first appeared on <a href="https://blog.eastonman.com">Easton Man's Blog</a>.]]></content:encoded>
					
					<wfw:commentRss>https://blog.eastonman.com/blog/2021/02/cfs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
