进程调度负责决定哪个进程投入运行,何时运行以及运行多长时间。
进程调度:非抢占式和抢占式
Linux这么酷的系统当然是抢占式的喽。
进程在被抢占之前可以运行的时间是预先设定好的,叫做时间片。有效管理时间片能使调度程序从系统全局角度作出调度决定,避免个别进程独占系统资源。
I/O消耗型/处理器消耗型
I/O消耗型:进程的大部分时间用来提交I/O请求或是等待I/O请求,这样的进程经常处于可运行状态,但通常只运行很短时间,在等待I/O时会阻塞。
处理器消耗型:进程大部分时间都在执行代码,除非被抢占,否则一直执行,没太多I/O需求。调度器不应该经常让他们执行,应尽量降低它们的调度频率,而延长其运行时间。
调度策略就是要在这两个矛盾中寻找平衡:进程响应迅速(响应时间短)和最大系统利用率(高吞吐量),linux倾向于优先调度IO消耗性。
时间片:进程在被抢占前所能持续运行的时间。
时间片过长会导致系统对交互响应表现欠佳,时间片太短会明显增加进程切换耗时。
IO消耗型不需要长的时间片,而处理器消耗型的进程希望越长越好(提高高速缓存命中率)。
Linux的CFS调度器没有直接分配时间片到进程,而是分配处理器的使用比。这样进程所获得的处理器时间其实是和系统负载密切相关的,这个比例进一步还会受进程nice值(优先级)影响。具有更小nice值的进程会被赋予高权重,从而有用更多的处理器使用比。
我们先来看在什么情况下要执行调度程序。
Linux调度时机主要有:
1)Process/thread creation Process/thread exit
2)Blocking on I/O or synchronization
3) I/O interrupt
4) Clock interrupt (pre-emptive scheduling)
1、进程状态转换的时刻:进程终止、进程睡眠;
2、当前进程的时间片用完时(current->counter=0);
3、设备驱动程序
4、进程从中断、异常及系统调用返回到用户态时;
时机1,进程要调用sleep()或exit()等函数进行状态转换,这些函数会主动调用调度程序进行进程调度;
时机2,由于进程的时间片是由时钟中断来更新的,因此,这种情况和时机4是一样的。
时机3,当设备驱动程序执行长而重复的任务时,直接调用调度程序。在每次反复循环中,驱动程序都检查need_resched的值,如果必要,则调用调度程序schedule()主动放弃CPU。
时机4,如前所述,不管是从中断、异常还是系统调用返回,最终都调用ret_from_sys_call(),由这个函数进行调度标志的检测,如果必要,则调用调用调度程序。那么,为什么从系统调用返回时要调用调度程序呢?这当然是从效率考虑。从系统调用返回意味着要离开内核态而返回到用户态,而状态的转换要花费一定的时间,因此,在返回到用户态前,系统把在内核态该处理的事全部做完。
Linux的调度算法:CFS(完全公平调度)
CFS 背后的主要想法是维护为任务提供处理器时间方面的平衡(公平性)。当分给某个任务的时间失去平衡时(意味着一个或多个任务相对于其他任务而言未被给予相当数量的时间),应给失去平衡的任务分配时间,让其执行。
CFS在所有可运行总数基础上计算出一个进程应该运行多久的时间。允许每个进程运行一段时间,循环轮转,选择运行最少的进程作为下一个运行进程。每个进程都按其权重在全部可运行进程中所占比例的“时间片”来运行。
当运行进程数量很大时,为避免频繁的切换消耗,CFS设置了底线。每个进程时间片的最小粒度是1ms,也就是说就算进程数量无限大,每个最少也能获得1ms的运行时间。
与之前的 Linux 调度器不同,它没有将任务维护在运行队列中,CFS 维护了一个以时间为顺序的红黑树。 我们知道红黑树是是自平衡的,树上的操作 时间复杂度为O(log n),可以快速高效地插入或删除任务。
Linux 内的所有任务都由称为 task_struct 的任务结构表示,task_struct 中会创建一个名为 sched_entity 的新结构来跟踪调度信息。
task_struct 描述进程任务并包含 sched_entity 结构。该结构包含 rb_node 引用、负载权重以及各种统计数据。最重要的是, sched_entity 包含 vruntime(64 位字段),它表示任务运行的时间量,并作为红黑树的索引。
rb_node 包含在 sched_entity 结构中,用于表示红黑树的每个节点,它只包含子引用和父对象的颜色。
此外,红黑树的根通过 rb_root 元素通过 cfs_rq 结构引用。红黑树的叶子不包含信息,但是内部节点代表一个或多个可运行的任务。
对处理器需求最多的任务 (最低虚拟运行时)存储在树的左侧,处理器需求最少的任务(最高虚拟运行时)存储在树的右侧。 为了公平,调度器然后选取红黑树最左端的节点调度为下一个以便保持公平性。任务通过将其运行时间添加到虚拟运行时, 说明其占用 CPU 的时间,然后如果可运行,再插回到树中。这样,树左侧的任务就被给予时间运行了,树的内容从右侧迁移到左侧以保持公平。 因此,每个可运行的任务都会追赶其他任务以维持整个可运行任务集合的执行平衡。
红黑树的键值一般由进程已经占用的CPU时间和进程的优先级决定。
其中进程已经占用的CPU时间对键值的影响最大,我们可以简单地认为键值就等于进程已占用的 CPU时间。因此该值越大,键值越大,从而使得当前进程向红黑树的右侧移动。
就 CFS 部分而言,调度函数非常简单。 在 ./kernel/sched.c 中,您会看到通用 schedule() 函数,它会先抢占当前运行任务(除非它通过 yield() 代码先抢占自己)。注意 CFS 没有真正的时间切片概念用于抢占,因为抢占时间是可变的。 当前运行任务(现在被抢占的任务)通过对 put_prev_task 调用(通过调度类)返回到红黑树。 当 schedule 函数开始确定下一个要调度的任务时,它会调用 pick_next_task 函数。此函数也是通用的(在 ./kernel/sched.c 中),但它会通过调度器类调用 CFS 调度器。 CFS 中的 pick_next_task 函数可以在 ./kernel/sched_fair.c(称为 pick_next_task_fair())中找到。 此函数只是从红黑树中获取最左端的任务并返回相关 sched_entity。通过此引用,一个简单的 task_of() 调用确定返回的 task_struct 引用。通用调度器最后为此任务提供处理器。这部分会在后面文章详细剖析。
下面是找到的一段英文总结,个人感觉不错:
? Scheduling decision:
– Pick the leftmost task (smallest virtual runtime)
? When a task is moved from running → ready:
– Add execution time to the per-task run time count
– Insert the task back in the sorted tree
? Heuristic: decay factors
– Determine how long a task can execute
– Higher priority tasks have lower factors of decay.
– Avoids having run queues per priority level
The Linux CFS scheduler provides an efficient algorithm for selecting which task to run next. Each runnable task is placed in a red-black tree—a balanced binary search tree whose key is based on the value of vruntime.
When a task becomes runnable, it is added to the tree. If a task on the tree is not runnable ( for example, if it is blocked while waiting for I/O ), it is removed. Generally speaking, tasks that have been given less processing time ( smaller values of vruntime ) are toward the left side of the tree, and tasks that have been given more processing time are on the right side. According to the properties of a binary search tree, the leftmost node has the smallest key value, which for the sake of the CFS scheduler means that it is the task with the highest priority . Because the red-black tree is balanced, navigating it to discover the leftmost node will require O(lgN) operations (where N is the number of nodes in the tree). However, for efficiency reasons, the Linux scheduler caches this value in the variable rb_leftmost, and thus determining which task to run next requires only retrieving the cached value.
参考:
http://www.ibm.com/developerworks/cn/linux/l-completely-fair-scheduler/
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/kzq_qmi/article/details/47259007