在Linux2.6.23之后,内核默认的调度器是CFS,也就是”Completely Fair Scheduler”。这个调度器替换了早先的o(1)调度器。
Linux提供了下面的系统调用来控制CPU调度的行为、策略和线程优先级。
每个线程都有一个相关联的调度策略和一个静态调度优先级。调度器基于系统的所有线程的调度策略和静态优先级来制定调度决定调度结果。
当线程处于某个普通的调度策略(SCHED_OTHER, SCHED_IDLE,SCHED_BATCH)
时,调度优先级(sched_proirity)
并没有被使用,被设置为0。
当进程处于一个实时策略(SCHED_FIFO, SCHED_RR)
的时候,会拥有一个sched_priority
值(1-99)
。注意其他UNIX系统可能取值范围不在这个范围内,所以需要通过sched_get_priority_min
和sched_get_priority_max
来获得合适的范围,来提高移植性。
一般调度器会为每个sched_priority
维护一个可执行的线程的链表。调度器会去找非空且静态优先级最高的链表,取得它的头节点上的线程执行。
线程的调度策略会决定它会被插入到那个链表里,如何在这链表内移动排序。
所有的调度都是可抢占的,如果一个优先级更高的线程开始准备执行,那么当前的执行线程会被抢占,并返回在优先级对应的等待链表。所以调度策略只决定它在链表里的位置。
它的静态优先级只能高于0,也就是当一个SCHED_FIFO线程变成可执行的时候,会立马抢占SCHED_OTHER, SCHED_BATCH, SCHED_IDLE
线程。SCHED_FIFO
是一个简单的调度策略,没有什么时间分片。
SCHED_FIFO
的线程被其他高优先级线程抢占,那么这个线程会被放回到链表的头部等待。sched_setscheduler, sched_setparam, sched_setattr
之后,指定的线程如果是可执行的,则会被放到它的链表的头部,更可能直接抢占当前拥有同样优先级的线程的CPU。sched_yield
的时候,会被放到链表的尾部,所以这个函数只会然给同优先级的。SCHED_FIFO
线程会执行直到它阻塞等待IO,或者被高优先级抢占,或调用sched_yield
。
它是SCHED_FIFO
的加强版。SCHED_FIFO
上面描述的所有东西都适用于SCHED_RR
,除了每个线程只能被允许执行一段定量的时间,即时间片大小。当它的线程执行的时间等于或超过一个时间片大小之后,线程会被放到链表的最后。当线程被高优先级抢占并恢复执行后,会完成它之前剩余的时间片。可以通过sched_rr_get_interval
来获得时间片大小。
在3.14之后,Linux提供一个deadline调度策略。这个策略当前使用GEDF(Global Earliest Deadline First)
结合CBS(Constant Bandwith Server)
实现。必须通过sched_setattr
和sched_getattr
来设置或读取这个策略相关的属性。
一个琐碎任务(sporadic task)拥有一系列的工作(job),每个工作每次最多被激活一次。每个工作都有一个relative deadline
(也就是它必须在这之前完成这个任务),还有一个computation time
(也就是执行这个任务必要的CPU时间)。当因为有一个新工作必须被执行,而导致一个任务被唤醒,这个时刻我们成为arrival time
。一个任务开始执行的时刻被称为start time
。因此absolute deadline
时间就是arrival time
加上relative dealine
。可以用下面的图进行表示:
arrival/wakeup absolute deadline
| start time |
| | |
v v v
-----x--------xooooooooooooooooo--------x--------x---
|<- comp. time ->|
|<------- relative deadline ------>|
|<-------------- period ------------------->|
当使用sched_setattr
为一个线程设置SCHED_DEADLINE
策略,可以指定三个参数Runtime, Deadline, Period
。一般情况下,最好是设置比平均情况(最坏的情况下)大一些。
arrival/wakeup absolute deadline
| start time |
| | |
v v v
-----x--------xooooooooooooooooo--------x--------x---
|<-- Runtime ------->|
|<----------- Deadline ----------->|
|<-------------- Period ------------------->|
这三个参数对应了sched_attr
里的sched_runtime, sched_deadline, sched_period
字段。这些字段值的单位是纳秒(ns)。如果sched_period
设置为0,那么它会被设置为sched_deadline
的值。内核里是要求需要满足下面的条件
sched_runtime <= sched_deadline <= sched_period
而每个值现在必须大于等1024,小于2^63。
CBS会扼杀掉那些想运行超过Runtime的线程,来保证了任务之间不会互相干涉。内核会检查设定的参数是否合适。如果不合适,那么sched_setattr
函数会返回EBUSY
错误。
属于这个策略的线程会拥有最高的优先级,所以只要线程就绪,就会抢占掉其他策略的线程的CPU。
当调用sched_yield
的时候会让出当前的工作,等待一个新的period
取开始。
这是Linux默认的time-sharing scheduling
。它的优先级只能是0,不是实时的策略。它通过在0优先级上的链表里基于动态优先级选择线程执行。而这个动态优先级是基于nice
值。当线程就绪,但是被调度器拒绝运行,这时候nice就就会增长,从而保证公平。
这个值会影响SCHED_OTHER
和SCHED_BATCH
进程的调度。可以通过nice, setpriority, sched_setattr
来设置。
根据POSIX规定,同一个进程的所有线程共享同一个nice值。但是在Linux上,nice值是针对单个线程的。在Linux上nice值的范围是(-20 ~ +19)
。注意值越小,优先级越高。
在CFS中,nice值的相对差异会产生很强的影响。所以nice值为+19
的进程几乎得不到CPU,而nice值为-20
的进程几乎占有了大部分的CPU。在Linux中,可以通过RLIMIT_NICE
资源限制来规定非特权进程的nice值范围。
它跟SCHED_OTHER
很类似,静态优先级为0,使用基于nice值的动态优先级进行调度。只不过不同的是这个策略会是调度器任务这个线程是CPU密集型的任务。所以会导致调度器会对它的唤醒行为做一点调度惩罚,从而这个线程在被调度的时候会被轻微地讨厌。
它的静态优先级也是0,而且进程的nice值对他没有影响。属于这个调度策略的线程优先级非常低,甚至比+19
的进程还要低。
每个线程都有一个reset-on-fork
的flag。当这个flag被设置,那么fork之后,子进程将不会继承父进程的调度策略。
SCHED_FIFO
和SCHED_RR
调度策略,则子进程的调度策略会被重置为SCHED_OTHER
。在Linux 2.6.12版本前,只有特权线程可以设置一个非0的静态优先级。在2.6.12版本之后,PLIMIT_RTPRIO
资源限制机制为使用SCHED_RR
和SCHED_FIFO
策略的非特权线程定义了一个上限。
PLIMIT_RTPRIO soft limit
,那么这个线程可以被设置实时调度策略,并且静态优先级不能超过那个软限制值。SCHED_IDLE
的线程在2.6.39之前是不被允许修改策略的,现在可以更改到SCHED_BATCH, SCHED_OTHER
策略,只要它的nice值在PLIMIT_NICE
限制范围内。特权进程(CAP_SYS_NICE)忽略RLIMIT_PTPRIO
的限制。
因为实时策略的进程可能会阻塞其他线程导致他们永远无法获得cpu。在Linux2.6.25之前,只能在测试实时进程的时候,把shell进程设置一个更高的优先级来避免实时进程无法被阻塞或中断。真是惨,哈哈哈。
在2.6.25之后,出现许多新的解决方案:
RLIMIT_RTTIME
来设置实时进程的CPU时间的上限。待续