Linux 中的调度任务是一项复杂的任务。Linux 能在机型(如企业服务器、客户端桌面、甚至嵌入式设备)运行,涉及的处理器拓扑结构范围非常广泛(单核、多核、多核/多线程等等)。但是令人惊讶的是,在 Linux 中只是少量的调度策略在工作。
更糟糕的是,在 Linux 中测量调度策略的效率很困难,因为调度器位于内核深处。添加跟踪等自检功能实际上更改了调度器的行为并隐藏缺陷或低效率。甚至,建立调度方案以便在各种处理器拓扑中验证给定的工作负荷,您要做好应对烦恼的准备。
幸运的是,类似 LinSched(用户空间 Linux 调度器模拟器)的项目可以帮助您解决此问题。
LinSched 是驻留在用户空间中的 Linux 调度器模拟器。它隔离 Linux 调度器子系统并围绕它构建足够的内核环境,从而可以在用户空间内执行该模拟器。它还将围绕其构建一系列应用程序编程接口(Application Programming Interfaces,APIs)以提供必要的刺激来验证调度工作负荷并收集足够的相关数据来了解其行为(稍后将介绍其整体架构)。
将调度器放入用户空间很容易就可以执行该调度器(因为调度器的崩溃不会引起整机崩溃)。这也使得收集有关调度器的数据变得简单,因为其可以在本地文件系统中轻松而又有效地收集数据并存储数据。
使用虚拟化是一种方案,但是 LinSched 的优势是简单且有效(因为没有必要启动另一个内核)。作为用户空间进程,也很容易附加一个调试器(如 GNU Debugger (GDB))来更深入地了解调度器操作。
LinSched 最初是在北卡罗来纳大学开发的(IBM 和 Intel 提供赞助),但是最近又被 Google 恢复了,用来验证调度策略。这是有趣的举动,可能代表一种未来趋势。虽然 Linux 支持许多调度类并跨许多工作负荷进行了合理的调度工作,但是对于给定硬件拓扑上的特定工作负荷,它可能不是最理想的。因为 Google 在执行特定任务(例如 MapReduce)的大量同型机集群上投资,所以针对该任务和环境调整调度有意义。在 LinSched 使用中,Google 还有助于增强适用于完全公平调度器(Completely Fair Scheduler,CFS)的 Linux,包括当任务间存在很大差异时的带宽设置和负载平衡。
LinSched 不仅仅是在用户空间中执行的调度器模拟器:它是通过必要组件的瘦模拟将 Linux 调度器迁移到用户空间,以便该调度器能够在内核以外执行。对于包装器和模拟引擎来说,LinSched 源实际上是带有名为 ./linsched 的新子目录的 Linux 版本(目前是 2.6.35)。因为 LinSched 为其模拟在 Linux 内使用 Linux 调度器子系统,所以进行更改,然后将更改集成回内核要简单得多。
LinSched 的总体架构如图 1 所示。底部是主机操作系统。LinSched 是由许多组件构建的用户空间应用程序(包括部分 Linux 内核本身)。
图 1. LinSched 调度器模拟器架构
环境模块提供 Linux 内核的抽象概念(通过在模拟模式中支持编译的标志)。在环境模块上的是模拟引擎,扩展了 API 以用于配置环境并执行模拟。模拟引擎 API 提供同步功能,定义处理器拓扑的初始化函数以及用于任务创建、回调(例如,在调度任务时)的函数和用于执行对一些时钟节拍执行模拟的 run
函数(其反过来调用内部 Linux schedule()
函数来制定调度决策)。
上面的模拟引擎是脚本环境(真实模拟的代码)。虽然它可能是单一脚本,但是正如后面的示例所示,最简单的用法是修改现有脚本环境来增加测试场景。
LinSched 的基本架构非常简单。我们首先来研究如何安装 LinSched,然后再讨论一些扩展的 API 以及如何在模拟中使用它们。
LinSched 最近得到更新以便支持 2.6.35 内核。您可以以两种方式获取当前的 LinSched 包 — 通过 git 或通过项目网页(请参考 参考资料 )。要通过 git 获取分布,复制下列库:
$ git clone git://google3-2.osuosl.org/linsched/2.6.35.git |
无论是哪种情况,您都将获得名为 2.6.35/ 的子目录,其包含分布。在 LinSched 子目录中,您可以执行以下命令行进行快速测试:
$ make run_all_tests |
此命令行可跨各种不同处理器拓扑执行一些调度器并发布测试结果。
要执行 LinSched 模拟,您要创建脚本并执行一系列初始化来建立所需的方案。此初始化通过一系列针对 LinSched API 的调用来执行。本小节研究了一些重要的 API 函数(后面将将演示它们的使用)。请注意下面并没有介绍整个 API,但是 ./linsched 中的源文件提供了更详细的列表。
在运行模拟之前,必须初始化模拟器。可以通过针对 linsched_init
的调用来执行这一操作。此函数接受模拟处理器拓扑作为其参数并在内部配置模拟器环境,同时启动用户空间 Linux 内核。
void linsched_init ( struct linsched_topology *topo ); |
linux_linsched.h 中定义的拓扑结构定义了处理器的数量以及它们如何相互关联(映射到物理包和节点分布图)。您可以通过TOPO_UNIPROCESSOR
指定单处理器包或通过 TOPO_QUAD_CPU_QUAD_SOCKET
指定 4 套接字、4 核处理器拓扑。
虽然 LinSched 允许您创建大量任务场景,但是 LinSched 中的任务是模拟实体。一个最有用的函数创建了一些遵循繁忙/睡眠配置的任务。调用程序提供要创建的任务数量 (count
)、任务可执行的处理器的掩码 (mask
),然后是繁忙/睡眠时钟节拍数(标识为 sleep
和run
)。
void create_tasks ( unsigned int count, unsigned long mask, int sleep, int run ); |
要创建更具体的任务场景,可以使用一些其他创建任务的 API 函数(请参考清单 1)。这些函数允许创建正常、批处理或实时任务(先入,先出 [FIFO] 或循环),具有特定的优先级或高等级。创建正常任务将调度策略设置为 SCHED_NORMAL
,其中 batch
、RTfifo
和 RTrr
分别设置为 SCHED_BATCH
、SCHED_FIFO
和 SCHED_RR
。用户可提供任务结构以便控制特定任务如何操作或使用linsched_create_sleep_run
API 函数来创建通用繁忙/睡眠任务。您可以在 linux_linsched.c 中发现这些任务中的每一个。
清单 1. LinSched 创建任务的 API 函数
int linsched_create_normal_task ( struct task_data* td, int niceval ); void linsched_create_batch_task ( struct task_data* td, int niceval ); void linsched_create_RTfifo_task ( struct task_data* td, int prio ); void linsched_create_RTrr_task ( struct task_data* td, int prio ); struct task_data *linsched_create_sleep_run ( int sleep, int busy ); |
通过 API,还可以创建任务组、附加任务到那些组以及将处理器共享添加到任务调度组。清单 2 提供了这些函数的列表(其位于 linux_linsched.c 中)。
清单 2. LinSched 任务组和组共享 API 函数
int linsched_create_task_group ( int parent_group_id ); void linsched_set_task_group_shares ( int group_id, unsigned long shares ); int linsched_add_task_to_group ( int task_id, int group_id ); |
要启动一次模拟,需要调用 linsched_run_sim
。该调用接收要运行的时钟节拍数作为其惟一的参数。在每一时钟节拍,要制定调度决策的潜能。在模拟完成时,函数返回(此函数位于 hrtimer.c 中)。
void linsched_run_sim ( int sim_ticks ); |
最后,您可以通过如清单 3 所示的调用来查看模拟结果。这些调用分别发布单个任务统计和组统计。
清单 3. 发布模拟统计的 LinSched 函数
void linsched_print_task_stats ( void ); void linsched_print_group_stats ( void ); |
要获得更多详细的分析,您还可以使用以下函数来基于其处理器 ID 获取任务:
struct task_struct *linsched_get_task ( int task_id ); |
通过任务结构,您可以发现任务的总执行时间(使用 task_exec_time(task)
)、没有运行的时间 (task->sched_info.run_delay
)、调度器调用任务的次数 (task->sched_info.pcount
)。
从这个简短的列表,您可以看到 LinSched 为建设调度方案提供了有用的 API。还可以迭代执行这些函数,这样,一旦方案启动和结束,就可以添加新的任务且通过另一个针对 linsched_run_sim
的调用继续模拟。此功能允许构建可重复的动态调度场景。
现在您已经研究了 LinSched 模拟引擎 API,让我们来看看它的操作。对于此示例来说(如 清单 4 所示),我使用了 LinSched 的basic_test
框架添加新的场景。在此框架中,传递的第二参数是要使用的拓扑。存在一个 topo_db
以提供各种支持的拓扑(框架对其运行所有可用选项)。定义拓扑后,您可以调用 linsched_init
以便为此处理器拓扑初始化环境。然后您可以在三个类别中创建 11 项任务。前五个任务使用简单的 create_tasks
函数来创建,其模拟处理器绑定任务(90% 的时间繁忙,10% 的时间睡眠)。然后您可以创建五个输入/输出 (I/O) 绑定任务(10% 的时间繁忙,90% 的时间睡眠)。最后,您可以创建一个实时任务(通过linsched_create_RTrr_task
),其 100% 的时间都是繁忙且优先度是 90。然后您可以通过针对 linssched_run_sim
的调用启动模拟器并通过 linsched_print_task_stats
发布结果。
注意: 此示例使用标准 CFS(如通过 linsched_run_sim
调用 schedule()
)。
启动 4. 示例 LinSched 脚本
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 linsched_init (&topo); // Create five processor-bound tasks (sleep 10, busy 90) create_tasks (5, mask, 10, 90); // Create five I/O-bound tasks (sleep 90, busy 10) create_tasks (5, mask, 90, 10); // Create a busy real-time round-robin task with a priority of 90 linsched_create_RTrr_task ( linsched_create_sleep_run (0,100), 90 ); // Run the simulation linsched_run_sim (TEST_TICKS ); // Emit the task statistics linsched_print_task_stats (); return; } |
在给定运行的每个任务的各种输出中运行该场景结果。例如,在 清单 5 中,您可以看到单处理器拓扑上场景运行的输出(来自 清单 4 )。此输出显示任务的列表(根据任务 ID 编号)、其总执行时间(时钟节拍内)、等待执行的时间量以及调用的次数。请注意虽然可能通过 API 结束任务,但是这些任务从未存在过。
这该场景中,前五项任务都是繁忙任务且 10% 的时间睡眠。第二个五项任务大多数时间睡眠只有 10% 时间繁忙。最后一项任务时实时任务,其是100% 的时间繁忙。如示例所示,实时任务接收到单一处理器的最大份额且只能调用 61 次。将其与繁忙和非繁忙任务进行比较,对于显著减少的处理器,这些任务的调用频率大概为三倍。还请注意调度器在繁忙和非繁忙任务中是公平的,给予它们大概相同的处理器访问量。
清单 5. 在单处理器拓扑上调度测试
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 |
现在,让我们看看来自 清单 4 的相同测试,但是这次是在 4 套接字、4 核拓扑上(16 个逻辑处理器)。因为每一个任务都有其自己的逻辑处理器,所以在同一给定的测试期间它会收到相当多的处理器时间。虽然可以调用繁忙和非繁忙任务相同的次数,但是非繁忙任务相对于繁忙任务只收到 10% 的执行时间。此差异是睡眠/繁忙配置的结果(繁忙任务执行 90 个时钟节拍,而非繁忙任务执行 10 个时钟节拍)。另外值得注意的是您的实时任务调用一次,在测试期间执行(因为其从不睡眠,所以在其处理器上也从不重新调度)。清单 6 显示了测试。
清单 6. 在 4 核、 4 套接字上调度测试
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 |
您还可以虚拟化从 LinSched 发布的数据。让我们看看另一个具有图形虚拟化输出的示例。在清单 7 所示的示例中,您通过范围从 -20 到 19 的精细值创建 40 项任务。回想一下任务精细值可以更改任务的优先级(这里 -20 是最高的优先级,而 20 是最低的)。对于正常的任务调度来说,任务将收到与其精细值成比例的处理器部分。
清单 7. 演示精细值对正常任务调度的影响
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 linsched_init (&topo); for (i = 0 ; i < 40 ; i++) { linsched_create_normal_task ( linsched_create_sleep_run (0,100), i-20 ); } // Run the simulation linsched_run_sim (TEST_TICKS *10); // Emit the task statistics linsched_print_task_stats (); return; } |
此应用程序的结果绘制在图 2(对于单处理器拓扑来说)。如图所示,在指数曲线中,高优先级任务收到绝大多数处理器,而低优先级任务收到的大大减少。最高优先级任务(精细值为 -20)收到大概 1200 亿个时钟节拍,而最低优先级任务(精细值为 19)接收到21,000,000 个时钟节拍。
图 2. 来自清单 7 的每一个任务的执行次数绘图
虽然 LinSched 在其用户空间中调度器模拟的功能方面是独一无二的,但是还存在用于虚拟化内核调度和其他活动的其他工具。Linux 跟踪工具包(Linux Trace Toolkit,LTT)详细地将系统事件编制目录,以便提供 Linux 内核的内部操作可见度。此项目已经升级到 LTT 下一代(LTT next generation,LTTng)。LTTng 为图形化(或以文本)虚拟内核操作提供用户空间工具。除了合并跟踪以外您还可以使用此工具进行用户空间跟踪,其可以汇总来自内核和用户空间的跟踪。要获得更多信息,请查看 参考资料 部分。
从这篇简短的文章,您可以看到用户空间中模拟 Linux 调度器的价值。与 LinSched 的工作已经发现用户空间模拟和实际基于内核的调度具有相当良好的相关性,因此这种方法适用于在生产中预测调度器的行为。同样有趣的是 LinSched 使用的方法。LinSched 使用 Linux 内核代码自身(通过仿真平台的包装器),而不是构建新的框架(通过该框架可以在用户空间中完成调度)。这意味着一旦在用户空间中验证了调度器,将此调度器迁移到内核以供使用会非常简单。
要获得除了用于调度器模拟和虚拟化的其他工具和技术以外有关 LinSched 的更多信息,请查看 参考资料 。
学习
- LinSched,Linux 调度器模拟器 ,是托管 Linux 调度子系统的用户空间应用程序。它是非常有用的分析 Linux 调度策略行为的工具。您还可以在 github (针对 LinSched 的 git 库)上联机查看最新的源。
- 了解 LinSched 细节的最佳方式之一就是题为 LinSched:Linux 调度器模拟器 的有关该工具的简短白皮书。本文除了示例用法和工作负荷模拟以外还提供了 LinSched 的概述。
- 在 2.6 Linux 内核开发的早期,就引入了新的调度器以便取代现有的(复杂的)调度器。这被称为 O(1) 调度器 ,因为其在固定时间内工作,而无论要调度的任务数量。您可以在 Linux 调度器内幕 (developerWorks,2006 年 6 月)中了解有关此调度器的更多信息。
- 遵循 O(1) 调度器,Linux 内核引入并采用了 CFS。CFS 是基于所有过程都应该给予相当数量的可用处理器这一理念的。您可以在本文中提供的示例中看到此公平性,其中所有进程都被授予了对服务器的访问,即便是最低的优先级。您可以在 Linux 2.6 Completely Fair Scheduler 内幕 (developerWorks,2009 年 12 月)中了解有关 CFS 的更多信息。
- Linux 跟踪工具包下一代 (LTTng ) 为跟踪内核和用户空间活动提供用户空间虚拟化设施。此工具用于了解系统行为并可帮助调试软件问题以及性能问题。另外一个有用的站点是 分布式多核跟踪研究项目 ,其开发了以最小的开销跟踪多核系统跟踪的技术和算法。
- 要了解有关调度的更多信息,包括那些在 Linux 内核中实现的算法,请在 Wikipedia 中查看 调度(计算) 页面。该页简要概述了每个主要的操作系统中的调度外还涵盖了 FIFO 和循环调度。
- 观看 developerWorks 演示中心 ,包括面向初学者的产品安装和设置演示,以及为经验丰富的开发人员提供的高级功能。
- 在 developerWorks Linux 专区 寻找为 Linux 开发人员(包括 Linux 新手入门 )准备的更多参考资料,查阅我们 最受欢迎的文章和教程 。
- 在 developerWorks 上查阅所有 Linux 技巧 和 Linux 教程 。
- 随时关注 developerWorks 技术活动 和网络广播 。
获得产品和技术
- 以最适合您的方式 IBM 产品评估试用版软件 :下载产品试用版,在线试用产品,在云环境下试用产品,或者在 IBM SOA Sandbox for People 中花费几个小时来学习如何高效实现面向服务架构。
讨论
- 加入 developerWorks 中文社区 ,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。