linux线程调度策略
这是一篇非常好的关于线程调度的资料,翻译自shed
从Linux 2.6.23开始,默认的调度器为CFS,即"完全公平调度器"(Completely Fair Scheduler)。CFS调度器取代了之前的"O(1)"调度器。
CFS的实现细节可以参见sched-design-CFS。cgroup的CPU调度也属于CFS扩展的一部分。
Scheduling policies
内核模块使用调度器来决定下一个CPU时钟周期执行的线程。每个线程都包含一个调度策略以及一个静态的调度优先级sched_priority
,调度器根据系统上所有线程的调度策略和静态优先级来决定如何进行调度。
对于使用普通调度策略(SCHED_OTHER
, SCHED_IDLE
, SCHED_BATCH
)的线程来说,sched_priority
并不会影响调度结果,且必须设置为0。
对于使用实时策略(SCHED_FIFO
,SCHED_RR
)的进程,其sched_priority
取值为1到99(1为最低值)。实时线程的调度优先级总是高于普通线程。注:POSIX.1的系统在实现中,会要求实时调度策略有32个优先级设置,因此,为了可移植性,可以使用sched_get_priority_min和sched_get_priority_max来查找调度策略所支持的优先级范围。
调度器会为每个sched_priority
值维护一个可运行的线程列表。调度器通过查看非空且静态优先级最高的列表,并选择该列表首部的元素作为下一个运行的线程。
线程的调度策略决定了如何根据静态优先级来将一个线程插入到同静态优先级的线程列表(list of runnable threads)中,以及如何在该列表中调整线程的位置。
所有的调度都具有抢占性:如果一个具有更高静态优先级的线程准备运行,当前运行的线程会被抢占并返回到其静态优先级对应的等待列表中。调度策略仅根据具有相同静态优先级的可运行线程列表来决定调度顺序。
进程调度中使用了2个队列:进程一开始会进入ready队列等待调度;当进程执行中遇到I/O阻塞,等待子进程结束或软中断等原因会进入wait队列,等阻塞结束后会返回到ready队列
SCHED_FIFO: First in-first out scheduling(实时线程)
SCHED_FIFO
仅适用于静态优先级大于0的线程,即当一个SCHED_FIFO
的线程变为可运行(runnable)状态时,它会立即抢占所有当前运行的SCHED_OTHER
, SCHED_BATCH
或SCHED_IDLE
线程。SCHED_FIFO
不使用时间片进行调度,所有使用SCHED_FIFO
调度策略的线程应该遵守如下规则:
- 当一个运行中的
SCHED_FIFO
线程被其他有更高优先级的线程抢占后,该线程会返回到其优先级对应的列表的首部,当所有更高优先级的线程阻塞后,该线程将会立即恢复运行; - 当一个阻塞的
SCHED_FIFO
线程变为可运行状态时,该线程会返回到其优先级对应的列表末尾; - 如果调用 sched_setscheduler(2),sched_setparam(2),sched_setattr(2),pthread_setschedparam(3),pthread_setschedprio(3) (通过pid)修改了正在运行或可运行状态的
SCHED_FIFO
线程的优先级时,该线程在列表中的位置取决于优先级的变动:
- 如果线程优先级增加了,它将会放置到新优先级对应的列表末尾,同时可能抢占正在运行的具有相同优先级的线程;
- 如果线程优先级没变,其在运行列表中的位置不变;
- 如果线程优先级减小了,它将会放置到新优先级对应的列表的前面。
根据POSIX.1-2008,通过非 pthread_setschedprio(3)方式来修改线程的优先级,可能会导致其放置到对应优先级列表的末尾。
- 调用了sched_yield(2) (用于释放CPU)的线程将会放置到列表末尾
SCHED_FIFO
线程将会一直运行,直到被更高优先级的线程抢占,或调用了sched_yield(2) 。
SCHED_RR: Round-robin scheduling(轮询调度)
SCHED_RR
对SCHED_FIFO
做了简单增强。除每个线程仅允许运行在一个最大时间段下外,SCHED_FIFO
中的所有规则都适用于SCHED_RR
。如果一个SCHED_RR
线程已经运行了等于或大于该最大时间段时,该线程会被放置到其优先级列表的末尾。当一个SCHED_RR
线程被更高优先级的线程抢占,并在后续恢复运行后,会在先前未过期的时间段下运行。最大时间段可以通过sched_rr_get_interval(2)获得。
SCHED_DEADLINE: Sporadic task model deadline scheduling
3.14版本之后的Linux提供了一个新的调度策略SCHED_DEADLINE
。该策略结合了GEDF(Global Earliest Deadline First)和 CBS (Constant Bandwidth Server)。必须通sched_setattr(2)和sched_getattr(2)来设置和获取该策略。
一个Sporadic task被定义为一系列任务,且每个任务每次仅激活一次。每个任务都有一个relative deadline
(该任务应该在该相对时间前停止运行),以及一个computation time
(执行该任务需要的CPU时间,对应下图的comp. time
)。一个新的任务开始执行时会唤醒(wakeup)一个Sporadic task,该时间点被称为arrival time
,start time
为一个任务开始执行的时间,absolute deadline
(绝对截止时间)为arrival time
加上relative deadline
的时间点。
arrival/wakeup absolute deadline
| start time |
| | |
v v v
-----x--------xooooooooooooooooo--------x--------x---
|<- comp. time ->|
|<------- relative deadline ------>|
|<-------------- period ------------------->|
当使用sched_setattr(2)给一个线程设置SCHED_DEADLINE
策略时,可以设置3个参数:Runtime
, Dead‐line
和Period
,对于上面提到的场景来说,通常的做法是将Runtime
设置为大于平均计算时间的值(或更坏的场景下,设置为硬实时任务的执行时间);将Deadline
设置为对应的dead-line,将Period
设置为任务的周期,此时对于SCHED_DEADLINE
的调度如下:
Runtime
对应上图中的comp.time
,Dead-line
对应上图的relative deadline
arrival/wakeup absolute deadline
| start time |
| | |
v v v
-----x--------xooooooooooooooooo--------x--------x---
|<-- Runtime ------->|
|<----------- Deadline ----------->|
|<-------------- Period ------------------->|
3个deadline调度参数对应sched_attr
结构体中的sched_run‐time
,