linux进程调度器模拟

<p>Linux 中的调度任务是一项复杂的任务。Linux 能在各种机型(如企业服务器、客户端桌面、甚至嵌入式设备)上运行,涉及的处理器拓扑结构范围非常广泛(单核、多核、多核/多线程等等)。但是令人惊讶的是,在 Linux 中只是少量的调度策略在工作。</p>
<p>更糟糕的是,在 Linux 中测量调度策略的效率很困难,因为调度器位于内核深处。添加跟踪等自检功能实际上更改了调度器的行为并隐藏缺陷或低效率。甚至,建立调度方案以便在各种处理器拓扑中验证给定的工作负荷,您要做好应对烦恼的准备。</p>
<p>幸运的是,类似 LinSched(用户空间 Linux 调度器模拟器)的项目可以帮助您解决此问题。</p>
<p><a name="linsched"><span class="atitle">介绍 LinSched</span></a></p>
<p>LinSched 是驻留在用户空间中的 Linux 调度器模拟器。它隔离 Linux 调度器子系统并围绕它构建足够的内核环境,从而可以在用户空间内执行该模拟器。它还将围绕其构建一系列应用程序编程接口(Application Programming Interfaces,APIs)以提供必要的刺激来验证调度工作负荷并收集足够的相关数据来了解其行为(稍后将介绍其整体架构)。</p>
<p>将调度器放入用户空间很容易就可以执行该调度器(因为调度器的崩溃不会引起整机崩溃)。这也使得收集有关调度器的数据变得简单,因为其可以在本地文件系统中轻松而又有效地收集数据并存储数据。</p>
<p>使用虚拟化是一种方案,但是 LinSched 的优势是简单且有效(因为没有必要启动另一个内核)。作为用户空间进程,也很容易附加一个调试器(如 GNU Debugger (GDB))来更深入地了解调度器操作。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="origin"><span class="atitle">LinSched 的起源</span></a></p>
<p>LinSched 最初是在北卡罗来纳大学开发的(IBM 和 Intel 提供赞助),但是最近又被 Google 恢复了,用来验证调度策略。这是有趣的举动,可能代表一种未来趋势。虽然 Linux 支持许多调度类并跨许多工作负荷进行了合理的调度工作,但是对于给定硬件拓扑上的特定工作负荷,它可能不是最理想的。因为 Google 在执行特定任务(例如 MapReduce)的大量同型机集群上投资,所以针对该任务和环境调整调度有意义。在 LinSched 使用中,Google 还有助于增强适用于完全公平调度器(Completely Fair Scheduler,CFS)的 Linux,包括当任务间存在很大差异时的带宽设置和负载平衡。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="architecture"><span class="atitle">LinSched 架构</span></a></p>
<p>LinSched 不仅仅是在用户空间中执行的调度器模拟器:它是通过必要组件的瘦模拟将 Linux 调度器迁移到用户空间,以便该调度器能够在内核以外执行。对于包装器和模拟引擎来说,LinSched 源实际上是带有名为 <em>./linsched</em> 的新子目录的 Linux 版本(目前是 2.6.35)。因为 LinSched 为其模拟在 Linux 内使用 Linux 调度器子系统,所以进行更改,然后将更改集成回内核要简单得多。</p>
<p>LinSched 的总体架构如图 1 所示。底部是主机操作系统。LinSched 是由许多组件构建的用户空间应用程序(包括部分 Linux 内核本身)。</p>
<p><br><img src="http://hi.csdn.net/attachment/201104/25/0_13037235679MHd.gif" alt=""><br></p>
<p>环境模块提供 Linux 内核的抽象概念(通过在模拟模式中支持编译的标志)。在环境模块上的是模拟引擎,扩展了 API 以用于配置环境并执行模拟。模拟引擎 API 提供同步功能,定义处理器拓扑的初始化函数以及用于任务创建、回调(例如,在调度任务时)的函数和用于执行对一些时钟节拍执行模拟的 <code>run</code> 函数(其反过来调用内部 Linux <code>schedule()</code> 函数来制定调度决策)。</p>
<p>上面的模拟引擎是脚本环境(真实模拟的代码)。虽然它可能是单一脚本,但是正如后面的示例所示,最简单的用法是修改现有脚本环境来增加测试场景。</p>
<p>LinSched 的基本架构非常简单。我们首先来研究如何安装 LinSched,然后再讨论一些扩展的 API 以及如何在模拟中使用它们。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="installation"><span class="atitle">安装 LinSched</span></a></p>
<p>LinSched 最近得到更新以便支持 2.6.35 内核。您可以以两种方式获取当前的 LinSched 包 — 通过 git 或通过项目网页,要通过 git 获取分布,复制下列库:</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">$ <strong>git clone git://google3-2.osuosl.org/linsched/2.6.35.git</strong>
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>无论是哪种情况,您都将获得名为 <em>2.6.35/</em> 的子目录,其包含分布。在 LinSched 子目录中,您可以执行以下命令行进行快速测试:</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">$ <strong>make run_all_tests</strong>
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>此命令行可跨各种不同处理器拓扑执行一些调度器并发布测试结果。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="api"><span class="atitle">LinSched API</span></a></p>
<p>要执行 LinSched 模拟,您要创建脚本并执行一系列初始化来建立所需的方案。此初始化通过一系列针对 LinSched API 的调用来执行。本小节研究了一些重要的 API 函数(后面将将演示它们的使用)。请注意下面并没有介绍整个 API,但是 ./linsched 中的源文件提供了更详细的列表。</p>
<p>在运行模拟之前,必须初始化模拟器。可以通过针对 <code>linsched_init</code> 的调用来执行这一操作。此函数接受模拟处理器拓扑作为其参数并在内部配置模拟器环境,同时启动用户空间 Linux 内核。</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">void <strong>linsched_init</strong>( struct linsched_topology *topo );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>linux_linsched.h 中定义的拓扑结构定义了处理器的数量以及它们如何相互关联(映射到物理包和节点分布图)。您可以通过 <code>TOPO_UNIPROCESSOR</code> 指定单处理器包或通过 <code>TOPO_QUAD_CPU_QUAD_SOCKET</code> 指定 4 套接字、4 核处理器拓扑。</p>
<p>虽然 LinSched 允许您创建大量任务场景,但是 LinSched 中的任务是模拟实体。一个最有用的函数创建了一些遵循繁忙/睡眠配置的任务。调用程序提供要创建的任务数量 (<code>count</code>)、任务可执行的处理器的掩码 (<code>mask</code>),然后是繁忙/睡眠时钟节拍数(标识为 <code>sleep</code> 和 <code>run</code>)。</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">void <strong>create_tasks</strong>( unsigned int count, unsigned long mask, int sleep, int run );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>要创建更具体的任务场景,可以使用一些其他创建任务的 API 函数(请参考清单 1)。这些函数允许创建正常、批处理或实时任务(先入,先出 [FIFO] 或循环),具有特定的优先级或高等级。创建正常任务将调度策略设置为 <code>SCHED_NORMAL</code>,其中 <code>batch</code>、<code>RTfifo</code> 和 <code>RTrr</code> 分别设置为 <code>SCHED_BATCH</code>、<code>SCHED_FIFO</code> 和 <code>SCHED_RR</code>。用户可提供任务结构以便控制特定任务如何操作或使用 <code>linsched_create_sleep_run</code> API 函数来创建通用繁忙/睡眠任务。您可以在 linux_linsched.c 中发现这些任务中的每一个。</p>
<p><br><a name="list1"><strong>清单 1. LinSched 创建任务的 API 函数</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
int <strong>linsched_create_normal_task</strong>( struct task_data* td, int niceval );
void <strong>linsched_create_batch_task</strong>( struct task_data* td, int niceval );
void <strong>linsched_create_RTfifo_task</strong>( struct task_data* td, int prio );
void <strong>linsched_create_RTrr_task</strong>( struct task_data* td, int prio );
struct task_data *<strong>linsched_create_sleep_run</strong>( int sleep, int busy );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>通过 API,还可以创建任务组、附加任务到那些组以及将处理器共享添加到任务调度组。清单 2 提供了这些函数的列表(其位于 linux_linsched.c 中)。</p>
<p><br><a name="list2"><strong>清单 2. LinSched 任务组和组共享 API 函数</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
int <strong>linsched_create_task_group</strong>( int parent_group_id );
void <strong>linsched_set_task_group_shares</strong>( int group_id, unsigned long shares );
int <strong>linsched_add_task_to_group</strong>( int task_id, int group_id );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>要启动一次模拟,需要调用 <code>linsched_run_sim</code>。该调用接收要运行的时钟节拍数作为其惟一的参数。在每一时钟节拍,要制定调度决策的潜能。在模拟完成时,函数返回(此函数位于 hrtimer.c 中)。</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">void <strong>linsched_run_sim</strong>( int sim_ticks );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>最后,您可以通过如清单 3 所示的调用来查看模拟结果。这些调用分别发布单个任务统计和组统计。</p>
<p><br><a name="list3"><strong>清单 3. 发布模拟统计的 LinSched 函数</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
void <strong>linsched_print_task_stats</strong>( void );
void <strong>linsched_print_group_stats</strong>( void );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>要获得更多详细的分析,您还可以使用以下函数来基于其处理器 ID 获取任务:</p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">struct task_struct *<strong>linsched_get_task</strong>( int task_id );
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>通过任务结构,您可以发现任务的总执行时间(使用 <code>task_exec_time(task)</code>)、没有运行的时间 (<code>task->sched_info.run_delay</code>)、调度器调用任务的次数 (<code>task->sched_info.pcount</code>)。</p>
<p>从这个简短的列表,您可以看到 LinSched 为建设调度方案提供了有用的 API。还可以迭代执行这些函数,这样,一旦方案启动和结束,就可以添加新的任务且通过另一个针对 <code>linsched_run_sim</code> 的调用继续模拟。此功能允许构建可重复的动态调度场景。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="experiment"><span class="atitle">体验 LinSched</span></a></p>
<p>现在您已经研究了 LinSched 模拟引擎 API,让我们来看看它的操作。对于此示例来说(如 <a href="#list4"><span style="color: #4c6e94;">清单 4</span></a> 所示),我使用了 LinSched 的 <code>basic_test</code> 框架添加新的场景。在此框架中,传递的第二参数是要使用的拓扑。存在一个 <code>topo_db</code> 以提供各种支持的拓扑(框架对其运行所有可用选项)。定义拓扑后,您可以调用 <code>linsched_init</code> 以便为此处理器拓扑初始化环境。然后您可以在三个类别中创建 11 项任务。前五个任务使用简单的 <code>create_tasks</code> 函数来创建,其模拟处理器绑定任务(90% 的时间繁忙,10% 的时间睡眠)。然后您可以创建五个输入/输出 (I/O) 绑定任务(10% 的时间繁忙,90% 的时间睡眠)。最后,您可以创建一个实时任务(通过 <code>linsched_create_RTrr_task</code>),其 100% 的时间都是繁忙且优先度是 90。然后您可以通过针对 <code>linssched_run_sim</code> 的调用启动模拟器并通过 <code>linsched_print_task_stats</code> 发布结果。</p>
<p><strong>注意:</strong>此示例使用标准 CFS(如通过 <code>linsched_run_sim</code> 调用 <code>schedule()</code>)。</p>
<p><br><a name="list4"><strong>启动 4. 示例 LinSched 脚本</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
void new_test(int argc, char **argv)
{
int count, mask;
struct linsched_topology topo;
int type = parse_topology(argv[2]);

topo = topo_db[type];

// Allow all tasks to use any processor.
mask = (1 << count) - 1;

// Initialize the topology as provided by the script interpreter
<strong>linsched_init</strong>(&topo);

// Create five processor-bound tasks (sleep 10, busy 90)
<strong>create_tasks</strong>(5, mask, 10, 90);

// Create five I/O-bound tasks (sleep 90, busy 10)
<strong>create_tasks</strong>(5, mask, 90, 10);

// Create a busy real-time round-robin task with a priority of 90
<strong>linsched_create_RTrr_task</strong>( <strong>linsched_create_sleep_run</strong>(0,100), 90 );

// Run the simulation
<strong>linsched_run_sim</strong>(<strong>TEST_TICKS</strong>);

// Emit the task statistics
<strong>linsched_print_task_stats</strong>();

return;
}
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>在给定运行的每个任务的各种输出中运行该场景结果。例如,在 <a href="#list5"><span style="color: #4c6e94;">清单 5</span></a> 中,您可以看到单处理器拓扑上场景运行的输出(来自 <a href="#list4"><span style="color: #4c6e94;">清单 4</span></a>)。此输出显示任务的列表(根据任务 ID 编号)、其总执行时间(时钟节拍内)、等待执行的时间量以及调用的次数。请注意虽然可能通过 API 结束任务,但是这些任务从未存在过。</p>
<p>这该场景中,前五项任务都是繁忙任务且 10% 的时间睡眠。第二个五项任务大多数时间睡眠只有 10% 时间繁忙。最后一项任务时实时任务,其是100% 的时间繁忙。如示例所示,实时任务接收到单一处理器的最大份额且只能调用 61 次。将其与繁忙和非繁忙任务进行比较,对于显著减少的处理器,这些任务的调用频率大概为三倍。还请注意调度器在繁忙和非繁忙任务中是公平的,给予它们大概相同的处理器访问量。</p>
<p><br><a name="list5"><strong>清单 5. 在单处理器拓扑上调度测试</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
Task id = 1, exec_time = 305000000, run_delay = 59659000000, pcount = 156
Task id = 2, exec_time = 302000000, run_delay = 58680000000, pcount = 154
Task id = 3, exec_time = 304000000, run_delay = 58708000000, pcount = 155
Task id = 4, exec_time = 304000000, run_delay = 58708000000, pcount = 155
Task id = 5, exec_time = 304000000, run_delay = 58708000000, pcount = 155
Task id = 6, exec_time = 296000000, run_delay = 56118000000, pcount = 177
Task id = 7, exec_time = 296000000, run_delay = 56118000000, pcount = 177
Task id = 8, exec_time = 296000000, run_delay = 56118000000, pcount = 177
Task id = 9, exec_time = 296000000, run_delay = 56118000000, pcount = 177
Task id = 10, exec_time = 296000000, run_delay = 56118000000, pcount = 177
Task id = 11, exec_time = 57001000000, run_delay = 2998000000, pcount = 61
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>现在,让我们看看来自 <a href="#list4"><span style="color: #4c6e94;">清单 4</span></a> 的相同测试,但是这次是在 4 套接字、4 核拓扑上(16 个逻辑处理器)。因为每一个任务都有其自己的逻辑处理器,所以在同一给定的测试期间它会收到相当多的处理器时间。虽然可以调用繁忙和非繁忙任务相同的次数,但是非繁忙任务相对于繁忙任务只收到 10% 的执行时间。此差异是睡眠/繁忙配置的结果(繁忙任务执行 90 个时钟节拍,而非繁忙任务执行 10 个时钟节拍)。另外值得注意的是您的实时任务调用一次,在测试期间执行(因为其从不睡眠,所以在其处理器上也从不重新调度)。清单 6 显示了测试。</p>
<p><br><a name="list6"><strong>清单 6. 在 4 核、 4 套接字上调度测试</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
Task id = 1, exec_time = 54000000000, run_delay = 0, pcount = 601
Task id = 2, exec_time = 54000156250, run_delay = 0, pcount = 600
Task id = 3, exec_time = 54000281250, run_delay = 0, pcount = 600
Task id = 4, exec_time = 54000406250, run_delay = 0, pcount = 600
Task id = 5, exec_time = 54000031250, run_delay = 0, pcount = 600
Task id = 6, exec_time = 6000187500, run_delay = 0, pcount = 600
Task id = 7, exec_time = 6000312500, run_delay = 0, pcount = 600
Task id = 8, exec_time = 6000437500, run_delay = 0, pcount = 600
Task id = 9, exec_time = 6000062500, run_delay = 0, pcount = 600
Task id = 10, exec_time = 6000218750, run_delay = 0, pcount = 600
Task id = 11, exec_time = 59999343750, run_delay = 0, pcount = 1
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>您还可以虚拟化从 LinSched 发布的数据。让我们看看另一个具有图形虚拟化输出的示例。在清单 7 所示的示例中,您通过范围从 -20 到 19 的精细值创建 40 项任务。回想一下任务精细值可以更改任务的优先级(这里 -20 是最高的优先级,而 20 是最低的)。对于正常的任务调度来说,任务将收到与其精细值成比例的处理器部分。</p>
<p><br><a name="list7"><strong>清单 7. 演示精细值对正常任务调度的影响</strong></a></p>
<table style="width: 65%;" border="0" cellspacing="0" cellpadding="0"><tbody><tr>
<td class="code-outline">
<pre class="displaycode">
void new_test(int argc, char **argv)
{
int count, mask, i;
struct linsched_topology topo;
int type = parse_topology(argv[2]);

topo = topo_db[type];

// Allow all tasks to use any processor.
mask = (1 << count) - 1;

// Initialize the topology as provided by the script interpreter
<strong>linsched_init</strong>(&topo);

for (i = 0 ; i < 40 ; i++) {
<strong>linsched_create_normal_task</strong>( <strong>linsched_create_sleep_run</strong>(0,100), i-20 );
}

// Run the simulation
<strong>linsched_run_sim</strong>(<strong>TEST_TICKS</strong>*10);

// Emit the task statistics
<strong>linsched_print_task_stats</strong>();

return;
}
</pre>
</td>
</tr></tbody></table>
<p></p>
<p>此应用程序的结果绘制在图 2(对于单处理器拓扑来说)。如图所示,在指数曲线中,高优先级任务收到绝大多数处理器,而低优先级任务收到的大大减少。最高优先级任务(精细值为 -20)收到大概 1200 亿个时钟节拍,而最低优先级任务(精细值为 19)接收到21,000,000 个时钟节拍。</p>
<p><br><a name="fig2"><strong>图 2. 来自清单 7 的每一个任务的执行次数绘图</strong></a></p>
<p><img src="http://hi.csdn.net/attachment/201104/25/0_13037233040sxY.gif" alt=""><br></p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="options"><span class="atitle">调度虚拟化的其他选项</span></a></p>
<p>虽然 LinSched 在其用户空间中调度器模拟的功能方面是独一无二的,但是还存在用于虚拟化内核调度和其他活动的其他工具。Linux 跟踪工具包(Linux Trace Toolkit,LTT)详细地将系统事件编制目录,以便提供 Linux 内核的内部操作可见度。此项目已经升级到 LTT 下一代(LTT next generation,LTTng)。LTTng 为图形化(或以文本)虚拟内核操作提供用户空间工具。除了合并跟踪以外您还可以使用此工具进行用户空间跟踪,其可以汇总来自内核和用户空间的跟踪。要获得更多信息,请查看 <a href="#resources"><span style="color: #4c6e94;">参考资料</span></a> 部分。</p>
<div class="ibm-alternate-rule">
<hr>
</div>
<p><a name="N10279"><span class="atitle">未来展望</span></a></p>
<p>从这篇简短的文章,您可以看到用户空间中模拟 Linux 调度器的价值。与 LinSched 的工作已经发现用户空间模拟和实际基于内核的调度具有相当良好的相关性,因此这种方法适用于在生产中预测调度器的行为。同样有趣的是 LinSched 使用的方法。LinSched 使用 Linux 内核代码自身(通过仿真平台的包装器),而不是构建新的框架(通过该框架可以在用户空间中完成调度)。这意味着一旦在用户空间中验证了调度器,将此调度器迁移到内核以供使用会非常简单。</p>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值