重要的 CFS 数据结构
2009-03-27 10:33
141 查看
对于每个 CPU,CFS 使用按时间排序的红黑(red-black)树。
该树方法能够良好运行的原因在于:
红黑树可以始终保持平衡。
由于红黑树是二叉树,查找操作的时间复杂度为对数。但是,除了最左侧查找以外,很难执行其他查找,并且最左侧的节点指针始终被缓存。
对于大多数操作,红黑树的执行时间为 O(log n),而以前的调度程序通过具有固定优先级的优先级数组使用 O(1)。O(log n) 行为具有可测量的延迟,但是对于较大的任务数无关紧要。Molnar 在尝试这种树方法时,首先对这一点进行了测试。
红黑树可通过内部存储实现 — 即不需要使用外部分配即可对数据结构进行维护。
让我们了解一下实现这种新调度程序的一些关键数据结构。
struct task_struct 的变化
CFS 去掉了
清单 1. task_struct 结构
struct sched_entity
该结构包含了完整的信息,用于实现对单个任务或任务组的调度。它可用于实现组调度。调度实体可能与进程没有关联。
清单 2. sched_entity 结构
struct sched_class
该调度类类似于一个模块链,协助内核调度程序工作。每个调度程序模块需要实现
清单 3. sched_class 结构
我们来看一下清单 3 中的函数:
运行队列中与 CFS 有关的字段
对于每个运行队列,都提供了一种结构来保存相关红黑树的信息。
清单 4. cfs_rq 结构
CFS 工作原理
CFS 调度程序使用安抚(appeasement)策略确保公平性。当某个任务进入运行队列后,将记录当前时间,当某个进程等待 CPU 时,将对这个进程的
CFS 维护任务运行时(相对于运行队列级时钟,称为
例如,如果具有 4 个可运行的任务,那么
加权任务引入了优先级。假设我们有两个任务:其中一个任务占用 CPU 的时间量是另一个任务的两倍,比例为 2:1。执行数学转换后,对于权重为 0.5 的任务,时间流逝的速度是以前的两倍。
我们根据
请注意,CFS 没有使用时间片(time slices),至少,没有优先使用。CFS 中的时间片具有可变的长度并且动态确定。
对于负载平衡程序,调度模块实现了迭代器,使用它遍历由调度模块管理的所有任务,从而进行负载平衡。
运行时调优选项
引入了重要的
下面是系统中运行时参数的典型值:
清单 5. 典型的运行时参数值
新的调度程序调试接口
新调度程序附带了一个非常棒的调试接口,还提供了运行时统计信息,分别在 kernel/sched_debug.c 和 kernel/sched_stats.h 中实现。要提供调度程序的运行时信息和调试信息,需要将一些文件添加到 proc pseudo 文件系统:
/proc/sched_debug:显示运行时调度程序可调优选项的当前值、CFS 统计信息和所有可用 CPU 的运行队列信息。当读取这个 proc 文件时,将调用
/proc/schedstat:为所有相关的 CPU 显示特定于运行队列的统计信息以及 SMP 系统中特定于域的统计信息。kernel/sched_stats.h 中定义的
/proc/[PID]/sched:显示与相关调度实体有关的信息。在读取这个文件时,将调用 kernel/sched_debug.c 中定义的
内核 2.6.24 中的变化
Linux 2.6.24 版本中有哪些值得期待的新变化?新版本中不再追赶全局时钟(
其他重要改动将影响关键数据结构。下面展示了
清单 6. 2.6.24 版本中 sched_entity 结构的预期变动
以下是
清单 7. 2.6.24 版本中 cfs_rq 结构的预期变动
组任务中引入了一个全新结构:
清单 8. 新添加的 task_group 结构
每个任务都跟踪它的运行时,并根据该值对任务进行排队。这意味着运行最少的任务将位于树的最左侧。同样,通过对时间加权划分优先级。每个任务在下面的时间段内力求获得精确调度:
其中
因此,如果每个可运行任务运行与
因此,当一个新任务变为可运行状态时,对其位置有严格的要求。在所有其他任务运行之前,此任务不能运行;否则,将破坏对这些任务作出的承诺。然而,由于该任务确实进行了排队,对运行队列的额外权重将缩短其他所有任务的时间片,在
2.6.24 中的组调度增强
在 2.6.24 中,您将能够对调度程序进行调优,从而实现对用户或组的公平性,而不是任务公平性。可以将任务进行分组,形成多个实体,调度程序将平等对待这些实体,继而公平对待实体中的任务。要启用这个特性,在编译内核时需要选择
可以使用两个独立的方法对任务进行分组,它们分别基于:
用户 ID。
cgroup pseudo 文件系统:这个选项使管理员可以根据需要创建组。有关更多细节,阅读内核源文档目录中的 cgroups.txt 文件。
内核配置参数
|
红黑树可以始终保持平衡。
由于红黑树是二叉树,查找操作的时间复杂度为对数。但是,除了最左侧查找以外,很难执行其他查找,并且最左侧的节点指针始终被缓存。
对于大多数操作,红黑树的执行时间为 O(log n),而以前的调度程序通过具有固定优先级的优先级数组使用 O(1)。O(log n) 行为具有可测量的延迟,但是对于较大的任务数无关紧要。Molnar 在尝试这种树方法时,首先对这一点进行了测试。
红黑树可通过内部存储实现 — 即不需要使用外部分配即可对数据结构进行维护。
让我们了解一下实现这种新调度程序的一些关键数据结构。
struct task_struct 的变化
CFS 去掉了
struct prio_array,并引入调度实体(scheduling entity)和调度类 (scheduling classes),分别由
struct sched_entity和
struct sched_class定义。因此,
task_struct包含关于
sched_entity和
sched_class这两种结构的信息:
清单 1. task_struct 结构
struct task_struct { /* Defined in 2.6.23:/usr/include/linux/sched.h */ .... - struct prio_array *array; + struct sched_entity se; + struct sched_class *sched_class; .... .... }; |
该结构包含了完整的信息,用于实现对单个任务或任务组的调度。它可用于实现组调度。调度实体可能与进程没有关联。
清单 2. sched_entity 结构
struct sched_entity { /* Defined in 2.6.23:/usr/include/linux/sched.h */ long wait_runtime; /* Amount of time the entity must run to become completely */ /* fair and balanced.*/ s64 fair_key; struct load_weight load; /* for load-balancing */ struct rb_node run_node; /* To be part of Red-black tree data structure */ unsigned int on_rq; .... }; |
该调度类类似于一个模块链,协助内核调度程序工作。每个调度程序模块需要实现
struct sched_class建议的一组函数。
清单 3. sched_class 结构
struct sched_class { /* Defined in 2.6.23:/usr/include/linux/sched.h */ struct sched_class *next; void (*enqueue_task) (struct rq *rq, struct task_struct *p, int wakeup); void (*dequeue_task) (struct rq *rq, struct task_struct *p, int sleep); void (*yield_task) (struct rq *rq, struct task_struct *p); void (*check_preempt_curr) (struct rq *rq, struct task_struct *p); struct task_struct * (*pick_next_task) (struct rq *rq); void (*put_prev_task) (struct rq *rq, struct task_struct *p); unsigned long (*load_balance) (struct rq *this_rq, int this_cpu, struct rq *busiest, unsigned long max_nr_move, unsigned long max_load_move, struct sched_domain *sd, enum cpu_idle_type idle, int *all_pinned, int *this_best_prio); void (*set_curr_task) (struct rq *rq); void (*task_tick) (struct rq *rq, struct task_struct *p); void (*task_new) (struct rq *rq, struct task_struct *p); }; |
enqueue_task:当某个任务进入可运行状态时,该函数将得到调用。它将调度实体(进程)放入红黑树中,并对
nr_running变量加 1。
dequeue_task:当某个任务退出可运行状态时调用该函数,它将从红黑树中去掉对应的调度实体,并从
nr_running变量中减 1。
yield_task:在
compat_yield sysctl关闭的情况下,该函数实际上执行先出队后入队;在这种情况下,它将调度实体放在红黑树的最右端。
check_preempt_curr:该函数将检查当前运行的任务是否被抢占。在实际抢占正在运行的任务之前,CFS 调度程序模块将执行公平性测试。这将驱动唤醒式(wakeup)抢占。
pick_next_task:该函数选择接下来要运行的最合适的进程。
load_balance:每个调度程序模块实现两个函数,
load_balance_start()和
load_balance_next(),使用这两个函数实现一个迭代器,在模块的
load_balance例程中调用。内核调度程序使用这种方法实现由调度模块管理的进程的负载平衡。
set_curr_task:当任务修改其调度类或修改其任务组时,将调用这个函数。
task_tick:该函数通常调用自 time tick 函数;它可能引起进程切换。这将驱动运行时(running)抢占。
task_new:内核调度程序为调度模块提供了管理新任务启动的机会。CFS 调度模块使用它进行组调度,而用于实时任务的调度模块则不会使用这个函数。
运行队列中与 CFS 有关的字段
对于每个运行队列,都提供了一种结构来保存相关红黑树的信息。
清单 4. cfs_rq 结构
struct cfs_rq {/* Defined in 2.6.23:kernel/sched.c */ struct load_weight load; unsigned long nr_running; s64 fair_clock; /* runqueue wide global clock */ u64 exec_clock; s64 wait_runtime; u64 sleeper_bonus; unsigned long wait_runtime_overruns, wait_runtime_underruns; struct rb_root tasks_timeline; /* Points to the root of the rb-tree*/ struct rb_node *rb_leftmost; /* Points to most eligible task to give the CPU */ struct rb_node *rb_load_balance_curr; #ifdef CONFIG_FAIR_GROUP_SCHED struct sched_entity *curr; /* Currently running entity */ struct rq *rq; /* cpu runqueue to which this cfs_rq is attached */ ... ... #endif }; |
|
CFS 调度程序使用安抚(appeasement)策略确保公平性。当某个任务进入运行队列后,将记录当前时间,当某个进程等待 CPU 时,将对这个进程的
wait_runtime值加一个数,这个数取决于运行队列当前的进程数。当执行这些计算时,也将考虑不同任务的优先级值。 将这个任务调度到 CPU 后,它的
wait_runtime值开始递减,当这个值递减到其他任务成为红黑树的最左侧任务时,当前任务将被抢占。通过这种方式,CFS 努力实现一种理想 状态,即
wait_runtime值为 0!
CFS 维护任务运行时(相对于运行队列级时钟,称为
fair_clock(
cfs_rq->fair_clock)),它在某个实际时间的片段内运行,因此,对于单个任务可以按照理想的速度运行。
|
fair_clock将按照实际时间速度的四分之一增加。每个任务将设法跟上这个速度。这是由分时多任务处理的量子化特性决定的。也就是说,在任何一个时间段内只有一个任务可以运行;因此,其他进程在时间上的拖欠将增大(
wait_runtime)。因此,一旦某个任务进入调度,它将努力赶上它所欠下的时间(并且要比所欠时间多一点,因为在追赶时间期间,
fair_clock不会停止计时)。
加权任务引入了优先级。假设我们有两个任务:其中一个任务占用 CPU 的时间量是另一个任务的两倍,比例为 2:1。执行数学转换后,对于权重为 0.5 的任务,时间流逝的速度是以前的两倍。
我们根据
fair_clock对树进行排队。
请注意,CFS 没有使用时间片(time slices),至少,没有优先使用。CFS 中的时间片具有可变的长度并且动态确定。
对于负载平衡程序,调度模块实现了迭代器,使用它遍历由调度模块管理的所有任务,从而进行负载平衡。
运行时调优选项
引入了重要的
sysctls来在运行时对调度程序进行调优(以 ns 结尾的名称以纳秒为单位):
sched_latency_ns:针对 CPU 密集型任务进行目标抢占延迟(Targeted preemption latency)。
sched_batch_wakeup_granularity_ns:针对
SCHED_BATCH的唤醒(Wake-up)粒度。
sched_wakeup_granularity_ns:针对
SCHED_OTHER的唤醒粒度。
sched_compat_yield:由于 CFS 进行了改动,严重依赖
sched_yield()的行为的应用程序可以要求不同的性能,因此推荐启用
sysctls。
sched_child_runs_first:child 在
fork之后进行调度;此为默认设置。如果设置为 0,那么先调度 parent。
sched_min_granularity_ns:针对 CPU 密集型任务执行最低级别抢占粒度。
sched_features:包含各种与调试相关的特性的信息。
sched_stat_granularity_ns:收集调度程序统计信息的粒度。
下面是系统中运行时参数的典型值:
清单 5. 典型的运行时参数值
[root@dodge ~]# sysctl -A|grep "sched" | grep -v "domain" kernel.sched_min_granularity_ns = 4000000 kernel.sched_latency_ns = 40000000 kernel.sched_wakeup_granularity_ns = 2000000 kernel.sched_batch_wakeup_granularity_ns = 25000000 kernel.sched_stat_granularity_ns = 0 kernel.sched_runtime_limit_ns = 40000000 kernel.sched_child_runs_first = 1 kernel.sched_features = 29 kernel.sched_compat_yield = 0 [root@dodge ~]# |
新调度程序附带了一个非常棒的调试接口,还提供了运行时统计信息,分别在 kernel/sched_debug.c 和 kernel/sched_stats.h 中实现。要提供调度程序的运行时信息和调试信息,需要将一些文件添加到 proc pseudo 文件系统:
/proc/sched_debug:显示运行时调度程序可调优选项的当前值、CFS 统计信息和所有可用 CPU 的运行队列信息。当读取这个 proc 文件时,将调用
sched_debug_show()函数并在 sched_debug.c 中定义。
/proc/schedstat:为所有相关的 CPU 显示特定于运行队列的统计信息以及 SMP 系统中特定于域的统计信息。kernel/sched_stats.h 中定义的
show_schedstat()函数将处理 proc 条目中的读操作。
/proc/[PID]/sched:显示与相关调度实体有关的信息。在读取这个文件时,将调用 kernel/sched_debug.c 中定义的
proc_sched_show_task()函数
|
Linux 2.6.24 版本中有哪些值得期待的新变化?新版本中不再追赶全局时钟(
fair_clock),任务之间将彼此追赶。将引入每个任务(调度实体)的时钟
vruntime(
wall_time/
task_weight),并且将使用近似的平均时间初始化新任务的时钟。
其他重要改动将影响关键数据结构。下面展示了
struct sched_entity中的预期变动:
清单 6. 2.6.24 版本中 sched_entity 结构的预期变动
struct sched_entity { /* Defined in /usr/include/linux/sched.h */ - long wait_runtime; - s64 fair_key; + u64 vruntime; - u64 wait_start_fair; - u64 sleep_start_fair; ... ... } |
struct cfs_rq中的变动:
清单 7. 2.6.24 版本中 cfs_rq 结构的预期变动
struct cfs_rq { /* Defined in kernel/sched.c */ - s64 fair_clock; - s64 wait_runtime; - u64 sleeper_bonus; - unsigned long wait_runtime_overruns, wait_runtime_underruns; + u64 min_vruntime; + struct sched_entity *curr; +#ifdef CONFIG_FAIR_GROUP_SCHED ... + struct task_group *tg; /* group that "owns" this runqueue */ ... #endif }; |
清单 8. 新添加的 task_group 结构
struct task_group { /* Defined in kernel/sched.c */ #ifdef CONFIG_FAIR_CGROUP_SCHED struct cgroup_subsys_state css; #endif /* schedulable entities of this group on each cpu */ struct sched_entity **se; /* runqueue "owned" by this group on each cpu */ struct cfs_rq **cfs_rq; unsigned long shares; /* spinlock to serialize modification to shares */ spinlock_t lock; struct rcu_head rcu; }; |
sched_period = (nr_running > sched_nr_latency) ? sysctl_sched_latency : ((nr_running * sysctl_sched_latency) / sched_nr_latency)
其中
sched_nr_latency=
(sysctl_sched_latency / sysctl_sched_min_granularity)。这表示,当可运行任务数大于
latency_nr时,将线性延长调度周期。sched_fair.c 中定义的
sched_slice()是进行这些计算的位置。
因此,如果每个可运行任务运行与
sched_slice()等价的时间,那么将花费的时间为
sched_period,每个任务将运行与其权重成比例的时间量。此外,在任何时刻,CFS 都承诺超前运行
sched_period,因为最后执行调度的任务将在这个时限内再次运行。
因此,当一个新任务变为可运行状态时,对其位置有严格的要求。在所有其他任务运行之前,此任务不能运行;否则,将破坏对这些任务作出的承诺。然而,由于该任务确实进行了排队,对运行队列的额外权重将缩短其他所有任务的时间片,在
sched_priod的末尾释放一点位置,刚好满足新任务的需求。这个新的任务就被放在这个位置。
2.6.24 中的组调度增强
在 2.6.24 中,您将能够对调度程序进行调优,从而实现对用户或组的公平性,而不是任务公平性。可以将任务进行分组,形成多个实体,调度程序将平等对待这些实体,继而公平对待实体中的任务。要启用这个特性,在编译内核时需要选择
CONFIG_FAIR_GROUP_SCHED。目前,只有
SCHED_NORMAL和
SCHED_BATCH任务可以进行分组。
可以使用两个独立的方法对任务进行分组,它们分别基于:
用户 ID。
cgroup pseudo 文件系统:这个选项使管理员可以根据需要创建组。有关更多细节,阅读内核源文档目录中的 cgroups.txt 文件。
内核配置参数
CONFIG_FAIR_USER_SCHED和
CONFIG_FAIR_CGROUP_SCHED可帮助您进行选择。
相关文章推荐
- 文件系统重要的数据结构
- ARM嵌入式系统网络驱动中的重要数据结构
- 三个重要的数据结构
- 2.3 linux存储管理-重要的数据结构和函数
- Linux内核中一些重要的数据结构(一)
- Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构
- LibreOffice源码开发_重要数据结构
- Linux内存管理重要的数据结构与函数(二)
- ffmpeg 重要数据结构接口及分析
- Linux内核--网络协议栈深入分析(一)--与sk_buff有关的几个重要的数据结构
- linux驱动移植重要的数据结构
- linux C 开发中重要的数据结构——结构体
- SPI驱动之子系统架构及重要数据结构
- 网卡驱动程序的一个重要数据结构
- 对关于字符驱动的一些重要数据结构(file_operations, file, inode, cdev)的理解
- udhcp源码剖析(五)——DHCP客户端重要的数据结构和初始化配置
- 1、Linux驱动重要的数据结构
- linux驱动移植的重要数据结构(一)
- 基础驱动相关的3个重要内核数据结构 -from ldd3
- postgres中重要的数据结构(三)