3 Resetting scheduling policy for child processes
每个线程都有一个reset-on-fork调度标识。当设置该标识后,使用fork(2)创建的子进程不会继承特权调度策略。可以通过如下方式设置reset-on-fork:
- 在调用sched_setscheduler(2)时,将SCHED_RESET_ON_FORK 标识作为policy参数
- 在调用sched_setattr(2)时,将SCHED_FLAG_RESET_ON_FORK 设置为attr.sched_flags
注意上面两个函数的常量名称不一样。使用sched_getscheduler(2)和sched_getattr(2)获取reset-on-fork状态的用法与上面类似。
reset-on-fork特性用于媒体播放的应用,可以防止应用在创建多个子进程时规避RLIMIT_RTTIME设置的资源限制。
更精确地讲,如果设置了reset-on-fork,后续创建地子进程会遵循下面规则:
- 如果正在运行的线程使用了SCHED_FIFO或SCHED_RR调度策略,子进程地策略或被设置为SCHED_OTHER;
- 如果正在运行的进程的nice值为负值,子进程的nice值会被设置为0。
在设置reset-on-fork之后,只有线程拥有CAP_SYS_NICE的capability时才能重置reset-on-fork。使用fork(2)创建的子进程会disable reset-on-fork标识。
4 Privileges and resource limits
在Linux 2.6.12之前,只有拥有特权(CAP_SYS_NICE)的线程才能设置非0的静态优先级(即设置实时调度策略)。后续版本对如下实现进行了修改:非特权的线程仅在调用者的effective user ID(EID)与目标线程的real或effective user ID相同的情况下才能且仅能设置SCHED_OTHER策略。
为了设置或修改SCHED_DEADLINE策略。线程必须是特权(CAP_SYS_NICE)的。
从Linux 2.6.12开始,RLIMIT_RTPRIO(可以使用ulimit -e设置)资源限制定义了非特权线程设置SCHED_RR 和SCHED_FIFIO策略的静态优先级的上限。修改调度策略和优先级的规则如下:
- 如果非特权线程有一个非0的RLIMIT_RTPRIO 软限制(soft limit),则该线程对调度策略和优先级的修改限制为:优先级的不能高于当前优先级且不能高于RLIMIT_RTPRIO。
- 如果RLIMIT_RTPRIO 为0,则仅允许降低优先级,或切换到非实时策略。
- 遵从上述规则的前提下,只要执行修改的线程的effective user ID等于目标线程的effective user ID就可以执行相应的修改。
- SCHED_IDLE策略有特殊的约束。在Linux 2.6.39之前,在该策略下创建的非特权线程无法修改该策略(与RLIMIT_RTPRIO 资源限制无关)。从Linux 2.6.39开始,只要nice值在RLIMIT_RTPRIO 资源限制所允许的范围内,非特权线程可以切换到SCHED_BATCH或SCHED_OTHER策略。
特权(CAP_SYS_NICE)线程会忽略RLIMIT_RTPRIO限制。在一些老的内核中,特权线程可以任意修改策略和优先级。参见 getrlimit(2)获取更多信息。
5 Limiting the CPU usage of real-time and deadline processes(限制实时进程或deadline进程)
SCHED_FIFO, SCHED_RR或SCHED_DEADLINE策略下调度的线程中的非阻塞无限循环处理可能会阻塞其他线程获取CPU。在Linux 2.6.25之前,阻止实时进程冻结系统的唯一方式是通过shell启动一个静态优先级更高的程序,如通过这种方式来停止实施程序,并释放CPU资源。
从Linux 2.6.25开始,引进了其他技术手段来处理实时(SCHED_FIFO,SCHED_RR)和deadline(SCHED_DEADLINE)进程。一种方式是通过RLIMIT_RTTIME 来限制实时进程可能使用到的CPU的上限。参见 getrlimit(2)获取更多信息。
从Linux 2.6.25开始,Linux提供了2个/proc文件来为非实时进程保留CPU时间。保留的CPU也可以为shell预留资源来停止正在允许的进程。两个文件中的值对应的单位为微秒:
- /proc/sys/kernel/sched_rt_period_us
该文件中的值指定了等同于100% CPU的调度周期。取值范围为1到INT_MAX,即1微秒到35分钟。默认值为1000,000(1秒)。定义了一个CPU使用周期,周期越短,可以越快开始下一个周期
- /proc/sys/kernel/sched_rt_runtime_us
该文件中的值指定了实时和deadline调度的进程可以使用的"period"。取值范围为-1到INT_MAX-1,设置为-1标识运行时间等同于周期,即没有给非实时进程预留任何CPU。默认值为950,000(0.95秒),表示给非实时或deadline调度策略保留5%的CPU。该参数需要结合sched_rt_period_us使用
6 Response time
一个阻塞的高优先级的线程(在调度前)等待I/O时会有一个确定的响应时间。设备驱动作者可以使用"slow interrupt"中断句柄来减少响应时间
7 Miscellaneous
子进程会通过fork(2)继承调度策略和参数。可以使用execve(2)来保存调度策略和参数。
实时进程通常会使用memory locking特性来防止内存页的延迟。可以使用mlock(2) 或mlockall(2)设置memory locking。
8 The autogroup feature
从Linux 2.6.38开始,内核提供了一种被称为autogrouping的特性来为多进程和CPU密集型负载(如Linux内核中的大量并行进程)提升交互式桌面性能。
该特性结合CFS调度策略,需要内核设置CONFIG_SCHED_AUTOGROUP。在一个运行的系统中,该特性可以通过文件/proc/sys/kernel/sched_autogroup_enabled使能或去使能,值0表示去使能,1表示使能。默认值为1(除非内核使用noautogroup参数启动内核)。
当通过setsid(2) (setsid会将一个进程脱离父进程)创建一个新的会话时会创建一个新的autogroup,这种情况可能发生在一个新的终端窗口启动时。使用fork(2)创建的进程会继承父辈的autogroup成员。因此,一个会话中的所有进程都属于同一个autogroup。当最后一个进程结束后,autogroup会被自动销毁。
当使能autogrouping时,一个autogroup中的所有成员都属于同一个内核调度器"任务组"。CFS调度器使用了在任务组间均衡分配CPU时钟周期的算法。可以使用下面例子进行展示提升交互式桌面性能的好处。
假设有2个竞争相同CPU的autogroup(即,单核系统或使用taskset设置所有SMP系统的进程使用相同的CPU),第一个group包含10个用于构建内核的CPU密集型进程make -j10CPU;另外一个包含一个CPU密集型的视频播放器进程。autogrouping的影响为:每个group各自分配到一半的CPU时钟周期,即视频播放器会分配到50%的CPU时钟周期,而非9%的时钟周期(该情况下可能会导致降低视频播放质量)。在SMP系统上会更加复杂,但整体的表现是一样的:调度器会在任务组之间分配CPU时钟周期,包含大量CPU密集型进程的autogroup并不会以牺牲系统上的其他任务为代价占用CPU周期。
进程的autogroup成员可以通过/proc/[pid]/autogroup查看:(下面进程隶属于autogroup-1,autogroup-1的nice值为0)
cat /proc/1/autogroup
/autogroup-1 nice 0
该文件可以通过为autogroup设置nice值来修改分配给一个autogroup的CPU带宽(bandwidth,即可使用的CPU时间),nice值范围为+19(低优先级)到-20(高优先级),设置越界的值会导致write(2)返回EINVAL错误。
autogroup的nice值的意义与进程的nice值意义相同,区别是前者为将autogroup作为一个整体,并基于相对其他autogroups设置的nice值来分配CPU时钟周期。对于一个autogroup内的进程,其CPU时钟周期为autogroup(相对于其他autogroups)的nice值和进程的nice值(相对于其他进程)的产物(即首先根据autogroup的nice值计算该autogroup所占用的CPU,然后根据进程的nice值计算该进程所占用的(属于其autogroup的)CPU)。
可以使用cgroups(7) CPU控制器来设置(非root CPU cgroup的)cgroups中的进程所占用的CPU,该设置会覆盖掉autogrouping。
所有的cgroup都由可选择的内核配置CONFIG_CGROUPS控制。在Linux 3.2引入了CPU带宽控制(bandwidth control)。cgroup对CFS的扩展有如下三种:
CONFIG_CGROUP_SCHED :运行任务以组的方式(在组之间)公平使用CPU
CONFIG_RT_GROUP_SCHED :用于支持实时任务组(SCHED_FIFO和SCHED_RR)
CONFIG_FAIR_GROUP_SCHED :用于支持CFS任务组(SCHED_NORMAL和SCHED_BATCH)
可以用如下方式查看是否启用了cgroup
zgrep -i cgroup /boot/config-4.4.0-148-generic
CONFIG_CGROUPS=y
# CONFIG_CGROUP_DEBUG is not set
CONFIG_CGROUP_FREEZER=y
CONFIG_CGROUP_PIDS=y
...
autogroup特性仅用于非实时调度策略(SCHED_OTHER, SCHED_BATCH和SCHED_IDLE)。它不会为实时和deadline策略分组。
9 The nice value and group scheduling
当调度非实时进程时,CFS调度器会使用一种称为"group scheduling"的技术(如果内核设置了CONFIG_FAIR_GROUP_SCHED 选项)
在group scheduling下,进程以"任务组"方式进行调度。任务组间有继承关系,会继承系统上被称为"root任务组"的初始化任务组。任务组遵循以下条件(按顺序):
- CPU cgroup中的所有线程为一个任务组。该任务组的父辈为对应的父cgroup
- 如果使能了autogrouping,则一个autogroup(即,使用setsid(2)创建的相同的会话)中的所有线程为一个任务组。每个新的autogrouping为独立的任务组。root任务组为所有任务组的父辈。
- 如果使能了autogrouping,那么包含所有root CPU cgroup进程的root任务组不会(隐式地)放到一个新的autogroup中。
- 如果使能了autogrouping,那么root任务组包含所有root CPU croup中的进程。
- 如果去使能autogrouping(即内核不配置CONFIG_FAIR_GROUP_SCHED),那么系统中所有的进程都会被放到一个任务组中
在group调度下,线程的nice值仅会影响到相同任务组的其他线程的调度。这会在一些使用传统nice语义的UNIX系统上会导致惊人的后果。实践中,如果使能了autogrouping,则会使用setpriority(2)或nice(1)来影响相同会话(通常为相同的终端窗口)中的一个进程相对于其他进程的调度。
相反的,对于不同会话(如,不同的终端窗口,这些任务都绑定到不同的autogroups)中绑定了唯一的CPU的2个进程,修改一个会话中的进程的nice值不会影响其他会话中的进程的调度。使用如下命令可以修改一个终端会话中所有进程对于的autogroup nice值。
echo 10 > /proc/self/autogroup
autogroup和进程都有一个nice值,autogroup的nice值用于在autogroup之间分配CPU;autogroup内的进程的nice值用于在进程间分配autogroup的CPU。cgroup的配置会覆盖autogroup
10Note
- 使用ps -eLfc可以在CLS一栏中查看进程的调度策略
TS SCHED_OTHER
FF SCHED_FIFO
RR SCHED_RR
B SCHED_BATCH
ISO SCHED_ISO
IDL SCHED_IDLE
/*
* Scheduling policies
*/
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
/* SCHED_ISO: reserved but not implemented yet */
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
- 可以在/proc/sched_debug中查看各个cpu core的调度情况,其中包含每个cgroup 服务的调度情况
core0上的调度情况:
cpu#0
.nr_running : 1
.load : 1024
.nr_switches : 89142
.nr_load_updates : 6244
.nr_uninterruptible : 0
.next_balance : 4294.892296
.curr->pid : 144
.clock : 2098472.597440
.clock_task : 2098472.597440
.cpu_load[0] : 1024
.cpu_load[1] : 992
.cpu_load[2] : 781
.cpu_load[3] : 499
.cpu_load[4] : 284
.avg_idle : 1000000
.max_idle_balance_cost : 500000
cfs_rq[0]:
.exec_clock : 0.000000
.MIN_vruntime : 0.000001
.min_vruntime : 45681.026639
.max_vruntime : 0.000001
.spread : 0.000000
.spread0 : 0.000000
.nr_spread_over : 0
.nr_running : 1
.load : 1024
.runnable_weight : 1024
.load_avg : 1098
.runnable_load_avg : 1024
.util_avg : 700
.util_est_enqueued : 1
.removed.load_avg : 0
.removed.util_avg : 0
.removed.runnable_sum : 0
rt_rq[0]:
.rt_nr_running : 0
.rt_nr_migratory : 0
.rt_throttled : 0
.rt_time : 0.000000
.rt_runtime : 950.000000
dl_rq[0]:
.dl_nr_running : 0
.dl_nr_migratory : 0
.dl_bw->bw : 996147
.dl_bw->total_bw : 0
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-----------------------------------------------------------------------------------------------------------
S init 1 7976.707447 1242 120 0.000000 1218.279487 0.000000
S kthreadd 2 45649.208333 59 120 0.000000 15.441751 0.000000
I rcu_gp 3 11.439403 2 100 0.000000 0.117660 0.000000
I rcu_par_gp 4 13.464421 2 100 0.000000 0.083686 0.000000
I kworker/0:0 5 1482.793978 151 120 0.000000 16.063414 0.000000
I kworker/0:0H 6 1147.932334 5 100 0.000000 0.479405 0.000000
I kworker/u4:0 7 45652.573929 606 120 0.000000 43.541697 0.000000
I mm_percpu_wq 8 19.192839 2 100 0.000000 0.114189 0.000000
S ksoftirqd/0 9 45438.277316 7130 120 0.000000 335.912627 0.000000
I rcu_sched 10 45675.055893 1546 120 0.000000 144.671547 0.000000
I rcu_bh 11 25.312191 2 120 0.000000 0.067661 0.000000
S migration/0 12 0.000000 13 0 0.000000 1.012660 0.000000
S cpuhp/0 13 850.366609 7 120 0.000000 0.372269 0.000000
S kdevtmpfs 19 1069.226685 129 120 0.000000 24.582750 0.000000
I kworker/0:1 20 45652.629141 6989 120 0.000000 918.071436 0.000000
S oom_reaper 21 101.580722 2 120 0.000000 0.059196 0.000000
I writeback 22 104.611617 2 100 0.000000 0.092006 0.000000
S kcompactd0 23 108.681666 2 120 0.000000 0.097510 0.000000
I crypto 24 112.684782 2 100 0.000000 0.090752 0.000000
I kblockd 25 116.718371 2 100 0.000000 0.091908 0.000000
S watchdogd 26 0.000000 2 0 0.000000 0.068764 0.000000
I cfg80211 27 154.714368 2 100 0.000000 0.149890 0.000000
S kswapd0 28 339.596327 3 120 0.000000 0.190751 0.000000
S spi0 53 2736.699133 3135 120 0.000000 116.784401 0.000000
S spi1 54 45618.244734 24854 120 0.000000 750.808049 0.000000
Sirq/41-400a4000 55 0.000000 2 49 0.000000 0.146301 0.000000
I kworker/u4:1 56 45653.187332 471 120 0.000000 39.596196 0.000000
I kworker/0:1H 61 4574.014004 33 100 0.000000 4.199412 0.000000
S jffs2_gcd_mtd6 66 44383.950302 26457 130 0.000000 4454.380844 0.000000
S amstat 70 45650.755687 473 120 0.000000 349.526673 0.000000
S syslogd 81 45649.511432 368 120 0.000000 195.174340 0.000000
S klogd 85 2669.549176 158 120 0.000000 57.621830 0.000000
S sh 121 45657.586207 717 120 0.000000 291.763896 0.000000
I kworker/u4:2 143 45675.101064 20 120 0.000000 1.418929 0.000000
>R cat 144 45681.026639 3 120 0.000000 17.887341 0.000000
core1上的调度情况:
cpu#1
.nr_running : 0
.load : 0
.nr_switches : 55
.nr_load_updates : 34
.nr_uninterruptible : 0
.next_balance : 4294.892296
.curr->pid : 0
.clock : 2814.614141
.clock_task : 2814.614141
.cpu_load[0] : 0
.cpu_load[1] : 0
.cpu_load[2] : 0
.cpu_load[3] : 0
.cpu_load[4] : 0
.avg_idle : 1000000
.max_idle_balance_cost : 500000
cfs_rq[1]:
.exec_clock : 0.000000
.MIN_vruntime : 0.000001
.min_vruntime : -1.047815
.max_vruntime : 0.000001
.spread : 0.000000
.spread0 : -45682.074454
.nr_spread_over : 0
.nr_running : 0
.load : 0
.runnable_weight : 0
.load_avg : 0
.runnable_load_avg : 0
.util_avg : 0
.util_est_enqueued : 0
.removed.load_avg : 0
.removed.util_avg : 0
.removed.runnable_sum : 0
rt_rq[1]:
.rt_nr_running : 0
.rt_nr_migratory : 0
.rt_throttled : 0
.rt_time : 0.000000
.rt_runtime : 950.000000
dl_rq[1]:
.dl_nr_running : 0
.dl_nr_migratory : 0
.dl_bw->bw : 996147
.dl_bw->total_bw : 0
runnable tasks:
S task PID tree-key switches prio wait-time sum-exec sum-sleep
-----------------------------------------------------------------------------------------------------------
S cpuhp/1 14 -3.296980 6 120 0.000000 0.803230 0.000000
S migration/1 15 0.000000 13 0 0.000000 1.065445 0.000000
S ksoftirqd/1 16 -3.989746 3 120 0.000000 0.119203 0.000000
I kworker/1:0 17 -2.493688 13 120 0.000000 0.633875 0.000000
I kworker/1:0H 18 -1.047815 4 100 0.000000 0.189170 0.000000
- 可以在/proc/$pid/sched中查看特定进程的调度情况
# cat /proc/1/sched
init (1, #threads: 1)
-------------------------------------------------------------------
se.exec_start : 8840.289712
se.vruntime : 7976.707447
se.sum_exec_runtime : 1218.279487
se.nr_migrations : 0
nr_switches : 1242
nr_voluntary_switches : 959
nr_involuntary_switches : 283
se.load.weight : 1024
se.runnable_weight : 1024
se.avg.load_sum : 127
se.avg.runnable_load_sum : 127
se.avg.util_sum : 97280
se.avg.load_avg : 0
se.avg.runnable_load_avg : 0
se.avg.util_avg : 0
se.avg.last_update_time : 8840289280
se.avg.util_est.ewma : 17
se.avg.util_est.enqueued : 1
policy : 0
prio : 120
clock-delta : 725
- 一个线程可以通过系统分配的时间片在各个CPU core上允许,但一个线程不能同时在多个core上运行。如果一个系统有N个core,那么可以同时在这些core上允许N个线程。将CPU core上线程切换到另外一个线程时,会涉及到上下文切换,上下文切换时需要保存PCB中的CPU寄存器信息,进程状态以及内存管理等信息,在进程恢复时还原这些信息。