Linux的线程调度系统

调度策略

调度程序是内核组件,它决定下一个由CPU执行的可运行线程。每个线程都有一个关联的调度策略和一个静态调度优先级sched_priority。调度器根据对调度策略的了解和系统上所有线程的静态优先级做出决策。

对于在常规调度策略(SCHED_OTHER、SCHED_IDLE、SCHED_BATCH)下调度的线程,sched_priority不会用于调度决策(它必须指定为0)。

在一种实时策略(SCHED_FIFO、SCHED_RR)下调度的进程的sched_priority值在1(低)到99(高)之间。(如数字所示,实时线程总是比普通线程具有更高的优先级。)请注意:POSIX.1要求实现只支持实时策略的至少32个不同的优先级级别,一些系统只提供这个最低优先级级别。可移植程序应该使用sched_get_priority_min(2)和sched_get_priority_max(2)来查找特定策略支持的优先级范围。

从概念上讲,调度器为每个可能的sched_priority值维护一个可运行线程列表。为了确定接下来运行哪个线程,调度器查找具有最高静态优先级的非空列表,并选择该列表头部的线程。

线程的调度策略决定了它将被插入到具有同等静态优先级的线程列表中的哪个位置,以及它将如何在这个列表中移动。

所有的调度都是抢占式的:如果一个具有更高静态优先级的线程准备运行,当前运行的线程将被抢占并返回到其静态优先级级别的等待列表中。调度策略仅在具有相同静态优先级的可运行线程列表中确定排序。

SCHED_FIFO:先进先出调度

SCHED_FIFO只能用于静态优先级高于0的情况,这意味着当SCHED_FIFO线程变为可运行状态时,它总是立即抢占任何当前运行的SCHED_OTHER、SCHED_BATCH或SCHED_IDLE线程。SCHED_FIFO是一种没有时间切片的简单调度算法。对于SCHED_FIFO策略下调度的线程,适用以下规则:

1)一个正在运行的SCHED_FIFO线程被另一个更高优先级的线程抢占,它的优先级将保持在列表的头部,并在所有更高优先级的线程再次被阻塞时恢复执行。

2)当阻塞的SCHED_FIFO线程变为可运行状态时,它将被插入到列表的末尾以获得优先级。

3)如果一个叫sched_setscheduler (2), sched_setparam (2), sched_setattr (2), pthread_setschedparam(3),或设置(3)改变运行或runnable SCHED_FIFO线程的优先级确定pid对线程的列表中的位置的影响取决于线程优先级变化的方向:

•如果线程的优先级被提升,它会被放在新的优先级列表的末尾。因此,它可能抢占具有相同优先级的当前正在运行的线程。

如果线程的优先级没有改变,那么它在运行列表中的位置也不会改变。

•如果线程的优先级被降低,它将被放在新的优先级列表的前面。

根据POSIX.1-2008,使用除pthread_setschedprio(3)之外的任何机制更改线程的优先级(或策略)都应该导致该线程被放在其优先级列表的末尾。

4)调用sched_yield(2)的线程将被放在列表的末尾。

没有其他事件会将SCHED_FIFO策略下调度的线程移动到具有同等静态优先级的可运行线程的等待列表中。

SCHED_FIFO线程一直运行,直到它被I/O请求阻塞、被更高优先级的线程抢占,或者它调用sched_yield(2)。

SCHED_RR:循环调度

SCHED_RR是SCHED_FIFO的简单增强。上面描述的针对SCHED_FIFO的所有内容也适用于SCHED_RR,但每个线程只允许在最大时间范围内运行。如果SCHED_RR线程运行的时间等于或大于时间量,那么它将被放在它的优先级列表的末尾。被高优先级线程抢占并随后作为运行线程继续执行的SCHED_RR线程将完成其轮询时间量的未过期部分。时间量的长度可以使用sched_rr_get_interval(2)检索。

SCHED_DEADLINE:零星的任务模型截止时间调度

从3.14版本开始,Linux提供了一个截止时间调度策略(SCHED_DEADLINE)。该策略目前使用GEDF(全球最早截止日期优先)和CBS(恒定带宽服务器)来实现。要设置和获取此策略和相关属性,必须使用特定于linux的sched_setattr(2)和sched_getattr(2)系统调用。

零星任务是指具有一系列作业的任务,其中每个作业在一个周期内最多激活一次。每个作业都有一个相对的截止时间(在此之前完成执行)和一个计算时间(执行作业所需的CPU时间)。由于必须执行新作业而唤醒任务的时刻称为到达时间(也称为请求时间或释放时间)。开始时间是任务开始执行的时间。因此,通过将相对截止时间与到达时间相加,就得到了绝对截止时间。

下图阐明了这些术语:

           arrival/wakeup                    absolute deadline
                |    start time                    |
                |        |                         |
                v        v                         v
           -----x--------xooooooooooooooooo--------x--------x---
                         |<- comp. time ->|
                |<------- relative deadline ------>|
                |<-------------- period ------------------->|

当使用sched_setattr(2)为线程设置SCHED_DEADLINE策略时,可以指定三个参数:Runtime、Deadline和Period。这些参数不一定对应于前面提到的术语:通常的做法是将Runtime设置为大于平均计算时间(或硬实时任务的最坏情况执行时间)的值,Deadline设置为相对的截止时间,Period设置为任务的周期。
因此,对于SCHED_DEADLINE调度,我们有:

           arrival/wakeup                    absolute deadline
                |    start time                    |
                |        |                         |
                v        v                         v
           -----x--------xooooooooooooooooo--------x--------x---
                         |<-- Runtime ------->|
                |<----------- Deadline ----------->|
                |<-------------- Period ------------------->|

三个deadline调度参数对应于sched_attr结构的sched_runtimesched_deadlinesched_period字段; 详见sched_setattr(2)。这些字段以纳秒为单位表示值。如果将sched_period指定为0,那么它将与sched_deadline相同。

内核要求:

           sched_runtime <= sched_deadline <= sched_period

此外,在当前的实现下,所有的参数值必须至少是1024(即,刚刚超过1微秒,这是实现的分辨率),并且小于2^63。如果其中任何一个检查失败,sched_setattr(2)将失败,并产生EINVAL错误。

CBS通过对试图超过其指定运行时的线程进行节流,保证了任务之间的不干扰。

为了确保最后期限调度的保证,内核必须防止SCHED_DEADLINE线程集在给定的约束条件下不可行的(可调度的)情况。因此,内核在设置或更改SCHED_DEADLINE策略和属性时执行导纳测试。这一入学考试计算更改是否可行;如果不是,则sched_setattr(2)失败,错误为EBUSY。

例如,总利用率必须小于或等于可用的cpu总数(但不一定足够),其中,由于每个线程在每个周期的运行时可以最大限度地运行,因此该线程的利用率是其运行时除以其周期。

为了实现当一个线程被纳入SCHED_DEADLINE策略时所做的保证,SCHED_DEADLINE线程是系统中最高优先级(用户可控)的线程;如果任何SCHED_DEADLINE线程是可运行的,它将抢占任何其他策略下调度的线程。

在SCHED_DEADLINE策略下调度的线程对fork(2)的调用失败,错误为EAGAIN,除非该线程设置了它的reset-on-fork标志(见下面)。

调用sched_yield(2)的SCHED_DEADLINE线程将生成当前作业,并等待新的时间段开始。

SCHED_OTHER: Linux默认分时调度

SCHED_OTHER只能在静态优先级0时使用(即,实时策略下的线程总是比SCHED_OTHER进程更具有优先级)。SCHED_OTHER是标准的Linux分时调度器,适用于不需要特殊实时机制的所有线程。

要运行的线程是从静态优先级0列表中根据动态优先级选择的,而动态优先级仅在该列表中确定。动态优先级基于nice值(见下面),每次线程准备运行时都会增加,但调度程序拒绝运行。这确保了所有SCHED_OTHER线程之间的公平进程。

在Linux内核源代码中,SCHED_OTHER策略实际上被命名为SCHED_NORMAL。

nice值

nice值是一个属性,可用于影响CPU调度器在调度决策中支持或不支持某个进程。它影响SCHED_OTHER和SCHED_BATCH(参见下面)进程的调度。nice值可以使用nice(2)、setpriority(2)或sched_setattr(2)修改。

根据POSIX.1, nice值是每个进程的属性;也就是说,进程中的线程应该共享一个nice值。然而,在Linux上,nice值是每个线程的属性:同一进程中的不同线程可能有不同的nice值。

nice值的范围因UNIX系统而异。在现代Linux上,范围是-20(高优先级)到+19(低优先级)。在其他一些系统上,范围是-20…20。早期Linux内核(在Linux 2.0之前)的范围是-∞…15。

nice值对SCHED_OTHER进程的相对调度的影响程度也会因UNIX系统和Linux内核版本的不同而不同。

随着内核2.6.23中CFS调度器的出现,Linux采用了一种算法,这种算法可以使nice值的相对差异产生更强的效果。在当前的实现中,两个进程的nice值的每一个单位的差异都会导致调度器对高优先级进程的偏爱程度为1.25倍。这导致非常低的nice值(+19)在系统上有任何其他更高优先级负载时,真正为进程提供很少的CPU,并使较高的nice值(-20)将大部分CPU交付给需要它的应用程序(例如,一些音频应用程序)。

在Linux上,RLIMIT_NICE资源限制可以用来定义一个限制,一个非特权进程的nice值可以被提高到这个限制;详见setrlimit(2)。

有关nice值的详细信息,请参阅下面关于autogroup特性和组调度的小节。

SCHED_BATCH:调度批处理进程

(因为Linux 2.6.16)时SCHED_BATCH只能在静态优先级0上使用。这个策略类似于SCHED_OTHER,它根据线程的动态优先级(基于nice值)调度线程。不同之处在于,此策略将导致调度器总是假定线程是cpu密集型的。因此,调度器将对唤醒行为应用一个较小的调度惩罚,因此这个线程在调度决策中略微不受欢迎。

这个策略对于非交互的工作负载非常有用,但是不希望降低它们的值,对于希望确定性调度策略而不需要交互导致额外抢占(工作负载的任务之间)的工作负载非常有用。

SCHED_IDLE:调度优先级很低的作业

(因为Linux 2.6.23。)SCHED_IDLE只能在静态优先级0时使用;过程nice值对该策略没有影响。

此策略旨在以极低的优先级运行作业(使用SCHED_OTHER或SCHED_BATCH策略时,优先级甚至低于+19)。

重置子进程的调度策略

每个线程都有一个fork上重置调度标志。设置了这个标志后,fork(2)创建的子进程不会继承特权调度策略。reset-on-fork标志可以由以下两种方式设置:

  • ORing 在调用sched_setscheduler(2)时将SCHED_RESET_ON_FORK标志写入策略参数(从Linux 2.6.32开始);或
  • 在attr中指定SCHED_FLAG_RESET_ON_FORK标志。调用sched_setattr(2)时的sched_flags

注意,这两个api使用的常量有不同的名称。fork上重置标志的状态可以类似地使用sched_getscheduler(2)和sched_getattr(2)检索。

fork上重置特性用于媒体播放应用程序,可以通过创建多个子进程来防止应用程序逃避RLIMIT_RTTIME资源限制(参见getrlimit(2))。

更准确地说,如果设置了reset-on-fork标志,则后续创建的子节点将适用以下规则:

  • 如果调用线程的调度策略为SCHED_FIFO或SCHED_RR,则在子进程中将策略重置为SCHED_OTHER。
  • 如果调用进程的nice值为负值,子进程的nice值会重置为0。

在reset-on-fork标志被启用后,只有当线程具有CAP_SYS_NICE能力时,它才能被重置。这个标志在fork(2)创建的子进程中被禁用。

特权和资源限制

在2.6.12之前的Linux内核中,只有特权线程(CAP_SYS_NICE)可以设置非零的静态优先级(即,设置实时调度策略)。非特权线程可以进行的惟一更改是设置SCHED_OTHER策略,只有当调用者的有效用户ID与其策略正在被更改的目标线程的实际或有效用户ID匹配时才能这样做(即,pid指定的线程)。

为了设置或修改SCHED_DEADLINE策略,线程必须有特权(CAP_SYS_NICE)。

从Linux 2.6.12开始,RLIMIT_RTPRIO资源限制为SCHED_RR和SCHED_FIFO策略定义了非特权线程的静态优先级的上限。调度策略和优先级的更改规则如下:

  • 如果一个非特权线程有一个非零的RLIMIT_RTPRIO软限制,那么它可以改变它的调度策略和优先级,但是优先级不能设置为高于它当前的最大优先级和它的RLIMIT_RTPRIO软限制的值。
  • 如果RLIMIT_RTPRIO软限制为0,那么唯一允许的更改是降低优先级,或切换到非实时策略。
  • 根据相同的规则,其他非特权线程也可以进行这些更改,只要进行更改的线程的有效用户ID与目标线程的真实或有效用户ID相匹配。
  • 特殊规则适用于SCHED_IDLE策略。在2.6.39之前的Linux内核中,在这个策略下运行的非特权线程不能改变它的策略,不管它的RLIMIT_RTPRIO资源限制的值是多少。从2.6.39开始,在Linux内核中,非特权线程可以切换到SCHED_BATCH或SCHED_OTHER策略,只要它的nice值在RLIMIT_NICE资源限制允许的范围内(参见getrlimit(2))。

特权线程(CAP_SYS_NICE)忽略RLIMIT_RTPRIO限制;与旧内核一样,它们可以任意更改调度策略和优先级。关于RLIMIT_RTPRIO的更多信息,请参见getrlimit(2)。

限制实时进程和截止时间进程的CPU使用

在SCHED_FIFO、SCHED_RR或SCHED_DEADLINE策略下调度的线程中,一个非阻塞的无限循环可能会永远阻塞所有其他线程访问CPU。在Linux 2.6.25之前,防止失控的实时进程冻结系统的唯一方法是(在控制台)运行一个调度在比测试的应用程序更高的静态优先级下的shell。这允许对未按预期阻塞或终止的已测试实时应用程序进行紧急终止。

从Linux 2.6.25开始,有其他技术可以处理失控的实时和截止时间处理。其中一种方法是使用RLIMIT_RTTIME资源限制来设置实时进程可能消耗的CPU时间上限。详情请参见getrlimit(2)。

从2.6.25版本开始,Linux还提供了两个/proc文件,可以用来为非实时进程预留一定数量的CPU时间。以这种方式预留CPU时间允许将一些CPU时间分配给(比如)一个可以用来终止失控进程的根shell。这两个文件都以微秒为单位指定时间值:

/proc/sys/kernel/sched_rt_period_us

该文件指定一个调度周期,相当于100%的CPU带宽。这个文件中的值范围从1到INT_MAX,给出了1微秒到35分钟的操作范围。该文件的默认值为1,000,000(1秒)。

/proc/sys/kernel/sched_rt_runtime_us

该文件中的值指定系统中所有实时和最后期限计划进程可以使用的“period”时间的多少。取值范围为-1 ~ INT_MAX-1。指定-1将使运行时间与周期相同;也就是说,没有为非实时进程预留CPU时间(这是内核2.6.25之前的Linux行为)。这个文件中的默认值是950,000(0.95秒),这意味着5%的CPU时间被预留给不运行在实时或截止时间调度策略下的进程。

响应时间

阻塞的等待I/O的高优先级线程在再次调度之前有一定的响应时间。通过使用“慢中断”中断处理程序,设备驱动程序编写器可以大大减少这个响应时间。

杂项

子进程在fork(2)上继承调度策略和参数。调度策略和参数在execve(2)之间保持不变。

为了避免分页延迟,实时进程通常需要内存锁定;这可以通过mlock(2)或mlockall(2)来完成。

autogroup特性

从Linux 2.6.38开始,内核提供了一个称为自动分组的特性,在面对多进程、cpu密集型的工作负载时(比如用大量并行构建进程(即make -j标志构建Linux内核),可以提高交互桌面的性能。

这个特性与CFS调度器一起操作,并且需要一个配置了CONFIG_SCHED_AUTOGROUP的内核。
在运行的系统上,通过文件/proc/sys/kernel/sched_autogroup_enabled启用或禁用该特性;值0禁用该特性,值1启用该特性。这个文件中的默认值是1,除非内核是用noautogroup参数引导的。

当通过setsid(2)创建一个新的会话时,一个新的autogroup被创建;例如,当启动一个新的终端窗口时,就会发生这种情况。fork(2)创建的新进程继承其父进程的自组成员关系。因此,会话中的所有进程都是同一自组的成员。当组中的最后一个进程终止时,自动组将被自动销毁。

当启用自动分组时,一个autogroup的所有成员都放在同一个内核调度器“任务组”中。CFS调度器使用一种算法来均衡任务组之间的CPU周期分布。这对于交互式桌面性能的好处可以通过下面的例子来描述。

假设有两个autogroup竞争相同的CPU(例如,假设一个单一的CPU系统或使用taskset(1)将所有进程限制在SMP系统的同一个CPU上)。第一组包含10个cpu绑定的进程,它们来自使用make -j10启动的内核构建。另一个包含一个cpu绑定的进程:视频播放器。自动分组的效果是,这两个组将各自获得一半的CPU周期。
也就是说,视频播放器将接收50%的CPU周期,而不是9%的周期,这可能会导致视频播放性能下降。SMP系统上的情况比较复杂,但总体效果是一样的:调度器任务组分配CPU周期,这样一个包含大量的CPU密集型autogroup过程最终不占用CPU周期的系统上的其他工作。

进程的autogroup(任务组)成员可以通过文件/proc/[pid]/autogroup来查看:

     $ cat /proc/1/autogroup
     /autogroup-1 nice 0

这个文件还可以用来修改分配给自动组的CPU带宽。这是通过在“nice”范围内写入一个数字来设置autogroup的nice值来完成的。取值范围为+19(低优先级)~ -20(高优先级)。(写入值超出这个范围会导致write(2)失败,错误为EINVAL。)

autogroup 的nice设置的含义与进程nice值相同,但适用于根据其他autogroup的相对nice值,将CPU周期分配到整个autogroup。对于一个autogroup中的进程,它接收到的CPU周期将是autogroup的nice值(与其他autogroup相比)和进程的nice值(与同一autogroup中的其他进程相比)的乘积。

使用cgroups(7) CPU控制器将进程放在cgroups中,而不是根CPU cgroup,可以覆盖autogroup的效果。

autogroup特性只对非实时策略(SCHED_OTHER、SCHED_BATCH和SCHED_IDLE)下调度的进程进行分组。它不会根据实时策略和截止时间策略对计划的进程进行分组。
这些过程是根据前面描述的规则安排的。

nice值和组调度

当调度非实时进程(即那些在SCHED_OTHER、SCHED_BATCH和SCHED_IDLE策略下调度的进程)时,如果内核配置了CONFIG_FAIR_GROUP_SCHED选项(这是典型的),那么CFS调度器使用一种称为“组调度”的技术。

在组调度下,线程在“任务组”中调度。任务组具有层次关系,在系统上的初始任务组之下,称为“根任务组”。
在下列情况下成立工作组:

  • 一个CPU cgroup中的所有线程组成一个任务组。该任务组的父任务组就是对应的父cgroup的任务组。
  • 如果自动分组是启用的,那么所有的线程(隐式)放置在一个autogroup(即,相同的会话,由setsid(2)创建)形成一个任务组。因此,每个新的autogroup都是一个独立的任务组。根任务组是所有这些autogroup的父组。
  • 如果启用了自动分组,那么根任务组由根CPU cgroup中所有没有隐式放置到新的autogroup中的进程组成。
  • 如果自动分组被禁用,那么根任务组由根CPU cgroup中的所有进程组成。
  • 如果组调度被禁用(即内核没有配置CONFIG_FAIR_GROUP_SCHED),那么系统上所有的进程都被放在一个单独的任务组中。

在组调度下,线程的nice值只对同一任务组中的其他线程的调度决策有影响。就UNIX系统上nice值的传统语义而言,这有一些令人惊讶的结果。特别是,如果启用了自动分组(这是各种发行版的默认值),那么在一个进程上使用setpriority(2)或nice(1)只对在同一会话(通常是:同一终端窗口)中执行的其他进程的调度有影响。

相反,对于两个(例如)在不同会话中的唯一cpu绑定进程(例如,不同的终端窗口,每个任务都被绑定到不同的autogroup),在一个会话中修改进程的nice值不会影响调度器相对于另一个会话中的进程的决策。一个可能有用的解决方法是使用如下命令来修改终端会话中所有进程的autogroup nice值:

           $ echo 10 > /proc/self/autogroup

Linux内核的实时特性

从内核版本2.6.18开始,Linux逐渐具备了实时功能,其中大部分都是从以前的实时抢占补丁集衍生出来的。在补丁完全合并到主线内核之前,必须安装它们以实现最佳的实时性能。这些补丁被命名为:

 patch-kernelversion-rtpatchversion

并可从http://www.kernel.org/pub/linux/kernel/projects/rt/下载。

在没有补丁的情况下,在它们完全包含到主线内核之前,内核配置只提供了三个抢占类CONFIG_PREEMPT_NONE、CONFIG_PREEMPT_VOLUNTARY和CONFIG_PREEMPT_DESKTOP,这三个抢占类分别没有、有些和相当程度上减少最坏情况下的调度延迟。

应用补丁或将补丁完全包含到主线内核之后,额外的配置项CONFIG_PREEMPT_RT就可用了。如果选中该选项,Linux将转换为常规的实时操作系统。然后使用FIFO和RR调度策略来运行一个具有真正实时优先级和最小最坏情况调度延迟的线程。

来源

https://www.man7.org/linux/man-pages/man7/sched.7.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值