Linux内核进程调度

CFS

在Linux2.6.23之后,内核默认的调度器是CFS,也就是”Completely Fair Scheduler”。这个调度器替换了早先的o(1)调度器。

API

Linux提供了下面的系统调用来控制CPU调度的行为、策略和线程优先级。

  1. nice:为调用线程设置一个nice值,并返回新的nice值。
  2. getpriority:返回一个线程,或一个进程组,或一个特定用户的线程集合的nice值。
  3. setpriority:设置一个线程,或一个进程组,或一个特定用户的线程集合的nice值。
  4. sched_setscheduler:为一个特定的线程设定调度策略或参数
  5. sched_getscheduler:返回一个特定的线程的调度策略或参数
  6. sched_get_priority_max:返回一个特定调度策略可用的最高优先级值
  7. sched_get_priority_min:返回….
  8. sched_rr_get_interval:获得处于round-robin调度策略的线程的定量(quantum)
  9. shced_yield:调用线程主动让出CPU
  10. sched_setaffinity:为一个特定线程设定CPU亲密度(affinity)
  11. sched_getaffinity:获得…
  12. sched_setattr:同sched_setscheduler
  13. sched_getattr:同sched_getscheduler

Scheduling Policies

每个线程都有一个相关联的调度策略和一个静态调度优先级。调度器基于系统的所有线程的调度策略和静态优先级来制定调度决定调度结果。

当线程处于某个普通的调度策略(SCHED_OTHER, SCHED_IDLE,SCHED_BATCH)时,调度优先级(sched_proirity)并没有被使用,被设置为0。

当进程处于一个实时策略(SCHED_FIFO, SCHED_RR)的时候,会拥有一个sched_priority(1-99)。注意其他UNIX系统可能取值范围不在这个范围内,所以需要通过sched_get_priority_minsched_get_priority_max来获得合适的范围,来提高移植性。

一般调度器会为每个sched_priority维护一个可执行的线程的链表。调度器会去找非空且静态优先级最高的链表,取得它的头节点上的线程执行。

线程的调度策略会决定它会被插入到那个链表里,如何在这链表内移动排序。

所有的调度都是可抢占的,如果一个优先级更高的线程开始准备执行,那么当前的执行线程会被抢占,并返回在优先级对应的等待链表。所以调度策略只决定它在链表里的位置。

SCHED_FIFO

它的静态优先级只能高于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_RR

它是SCHED_FIFO的加强版。SCHED_FIFO上面描述的所有东西都适用于SCHED_RR,除了每个线程只能被允许执行一段定量的时间,即时间片大小。当它的线程执行的时间等于或超过一个时间片大小之后,线程会被放到链表的最后。当线程被高优先级抢占并恢复执行后,会完成它之前剩余的时间片。可以通过sched_rr_get_interval来获得时间片大小。

SCHED_DEADLINE

在3.14之后,Linux提供一个deadline调度策略。这个策略当前使用GEDF(Global Earliest Deadline First)结合CBS(Constant Bandwith Server) 实现。必须通过sched_setattrsched_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取开始。

SCHED_OTHER

这是Linux默认的time-sharing scheduling。它的优先级只能是0,不是实时的策略。它通过在0优先级上的链表里基于动态优先级选择线程执行。而这个动态优先级是基于nice值。当线程就绪,但是被调度器拒绝运行,这时候nice就就会增长,从而保证公平。

nice值

这个值会影响SCHED_OTHERSCHED_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_BATCH

它跟SCHED_OTHER很类似,静态优先级为0,使用基于nice值的动态优先级进行调度。只不过不同的是这个策略会是调度器任务这个线程是CPU密集型的任务。所以会导致调度器会对它的唤醒行为做一点调度惩罚,从而这个线程在被调度的时候会被轻微地讨厌。

SCHED_IDLE

它的静态优先级也是0,而且进程的nice值对他没有影响。属于这个调度策略的线程优先级非常低,甚至比+19的进程还要低。

Resetting scheduling policy for child process

每个线程都有一个reset-on-fork的flag。当这个flag被设置,那么fork之后,子进程将不会继承父进程的调度策略。

  • 如果调用fork的线程使用SCHED_FIFOSCHED_RR调度策略,则子进程的调度策略会被重置为SCHED_OTHER
  • 如果调用fork的进程的nice值是一个负数,则子进程的nice值会被重置为0。

Privileges and Resource Limits

在Linux 2.6.12版本前,只有特权线程可以设置一个非0的静态优先级。在2.6.12版本之后,PLIMIT_RTPRIO资源限制机制为使用SCHED_RRSCHED_FIFO策略的非特权线程定义了一个上限。

  • 如果一个非特权线程有一个非0的PLIMIT_RTPRIO soft limit,那么这个线程可以被设置实时调度策略,并且静态优先级不能超过那个软限制值。
  • 如果软限制值是0,那么它只被允许降低它的优先级和切换到非实时的策略。
  • 同样,另一个无特权的线程也可以对一个无特权的线程进行修改,只要两个线程的两个线程的实际或有效的用户ID相匹配。
  • SCHED_IDLE的线程在2.6.39之前是不被允许修改策略的,现在可以更改到SCHED_BATCH, SCHED_OTHER策略,只要它的nice值在PLIMIT_NICE限制范围内。

特权进程(CAP_SYS_NICE)忽略RLIMIT_PTPRIO的限制。

Real-Time和Deadline进程的cpu使用率的限制

因为实时策略的进程可能会阻塞其他线程导致他们永远无法获得cpu。在Linux2.6.25之前,只能在测试实时进程的时候,把shell进程设置一个更高的优先级来避免实时进程无法被阻塞或中断。真是惨,哈哈哈。

在2.6.25之后,出现许多新的解决方案:

  1. 使用资源限制机制的RLIMIT_RTTIME来设置实时进程的CPU时间的上限。
  2. Linux提供了两个/proc文件可以为非实时进程保留一些CPU时间。
    • /proc/sys/kernel/sched_rt_period_us:设定多少调度周期表示100%的CPU带宽,值从1到INT_MAX。也就是让实时进程觉得已经占用了100%的CPU带宽了,实际是有些保留的。
    • /proc/sys/kernel/sched_rt_runtime_us:

Autogroup特性

待续

你可能感兴趣的:(linux)