使用CFS解决了那些问题
最近闲下来了,于是乎找了一本《Linux内核设计与实现》来看,后面发现linux内核是用C写的,鄙人的C水平…(此处省略3个字),后来发现对我阅读这本书的阻碍不大。读到Linux线程调度策略的时候发现有点看不懂,于是上网查找了一下资料,整理出来写到博客当中,以免将来忘记了。
先说明两个非linux2.6+版本的调度方法:
-
简单队列调度程序
调度程序扫描整个列表,根据优先级等条件,找到下一个要运行的进程。采用的是轮询的方法。
缺点:轮询时间消耗久
-
非公平进程调度
Linux内核维护着两个进程队列,当上一个进程运行完成之后,内核会将其放入过期队列中,表明当前进程在当前的调度轮(调度所有进程一次代表一轮)中已被调度,内核从运行队列中取出第一个进程继续运行,如果进程为空,则将过期队列变为运行队列,运行队列变为过期队列。
缺点:nice值映射处理器的绝对时间,举例:两个线程,nice值为0,1的时候,获取的时间片是100ms,95ms,占比是0.95。nice值为18,19的时候他们的时间片是10和5,占比1。随着nice值的递增,两个线程运行的时间差距越来越大,有可能第一个线程执行了两次运算,第二个线程就执行了一次运算。
流程: 1.新进程fork(),调度程序将进程放入根据优先级排列的链表。 2.调度程序将从运行队列取出最高优先级的第一个进程运行。 3.调度程序基于进程优先级和以前的阻塞率给进程分配一个时间片。 4.调度程序将其移动到过期队列相应的优先级列表。 5.从运行列中取出下一个具有最高优先级的进程,重复以上过程。 6.当运行队列为空时,过期队列和运行队列变换,开次开始循环。
Linux2.6版本采用的是CFS公平调度的方式实现进程的调度。
- CFS针对的是普通的进程,位于Linux内核代码的kernel/sched_fair.c中。
- CFS使用红黑树来存储线程。
- CFS没有时间片的概念,采用时间记账的方式(虚拟运行时间)。
- CFS能保证公平调度
流程:
- 调度程序根据进程等待运行时间、竞争CPU进程数量及进程优先级来计算进程虚拟时间。
- 调度程序取出虚拟时间最多的进程并运行。
- 虚拟时间随着使用时间而减少。
- 进程不再拥有最多的虚拟时间,它将被拥有最多虚拟时间的进程抢占。
原理:设定一个调度周期( sched_latency_ns ),目标是让每个进程在这个周期内至少有机会运行一次,换一种说法就是每个进程等待CPU的时间最长不超过这个调度周期;然后根据进程的数量,大家平分这个调度周期内的CPU使用权,由于进程的优先级即nice值不同,分割调度周期的时候要加权;每个进程的累计运行时间保存在自己的vruntime字段里,哪个进程的vruntime最小就获得本轮运行的权利。
当有新进程运行时,他的vruntime会初始化为min_vruntime(最小虚拟时间),这也是保证公平的一种实现。如果这个值为0的话,则这个新进程会在相当长的一段时间内保存抢占CPU的优势,而老线程因为vruntime已经足够的多而运行时间变少,可能会饿晕,当不会饿死。这明显是不公平的。
当有休眠的线程被唤醒时,它的vruntime比别人小很多,会使它获得长时间抢占CPU的优势,也会拥有抢占CPU的优势,所以他的vruntime也会被重置。
扩展:进程可以被分为I/O消耗型和处理器消耗性。一般I/O消耗性通常都是在监听鼠标或键盘的用户交互操作,所以需要经常处于可运行的状态,通常都是运行一小会儿。处理器消耗型进程更多的是执行大量数学计算的程序的进程。Unix系统的调度程序更倾向于I/O消耗型,提供更好的响应速度,但是不能保证公平性(这点后面我会去查阅相关资料,或者希望有知道的大神可以告知我Unix如何保证更好的响应速度)。Linux的为了保证交互式应用和桌面系统的性能,对响应做了优化,更倾向于优先调度I/0消耗型进程,同时也兼顾了处理器消耗型的进程。