回顾下上篇文章:感兴趣可以看上面 《linux系列》专辑
调度器设计的两个目标:
1、吞吐
2、响应
调度器只能满足一个目标,提高吞吐就会降低响应,提高响应就会降低吞吐。
对吞吐的影响不只是上下文切换的耗时,还有cache miss的影响。
进程分两种类型:
1、IO消耗型
2、CPU消耗型
IO消耗型一般和用户体验相关,这种进程特别关注响应,一般CPU占用率很低。
linux操作系统一般来说是抢占式、非实时操作系统。
但是并不表示linux不能做实时系统, 打上RT-Preempt Patch内核补丁,linux就可以改为实时操作系统了。
linux抢占模型:
1、No Forced Preemption (Server) 不强制抢占 适合偏吞吐模式 适合服务器选用,操作系统基本没有抢占调度,吞吐性能越好
2、Voluntary Kernel Preemption (Desktop) 内核不可抢占
3、Preemptible Kernel (Low-Latency Desktop) 可抢占式内核 偏响应模式 一般桌面版选用,内核可以抢占,响应越好
4、Fully Preemptible Kernel (Real-Time) 实时系统 (需要rt补丁)
所以有些linux厂商会将发行的系统分服务器版和桌面版,就是因为linux内核选择了不同的抢占模型。
这里因为分析linux内核的调度策略,所以我们只看第3种 偏响应的抢占模型。
linux2.6内核时期
优先级数组和bitmaps
内核中将优先级划分为0~139,优先级值越小优先级越高。
在内核中0~99属于RT型进程(实时),100~139为非RT普通进程。
多个进程可以设置同样的优先级。
注意:这里的优先级指的是nice值,但是内核中的nice值和top命令在用户空间中看到的(NI)不一样!
调度:
每次调度时,按优先级从高到低(从0到139)找最先有就绪态的进程/线程,哪个优先级有任务就先调度哪个优先级。
实时调度策略:0~99
SCHED_FIFO
按优先级,最高优先级先跑,跑到让出CPU(比如睡眠),低优先级再跑;同等优先级,按先后顺序跑,先进先出。
比如:A、B两个进程优先级都是0,按A、B进入就绪态的时间先后(假设A先就绪,B再就绪),先跑A进程,A进程跑完再跑B。假设后来有个C进程,优先级是1,那么等A、B都跑完,再跑C。
SCHED_RR
按优先级,高优先级先跑,低优先级再跑。同等优先级,大家轮流跑。
RR就是RoundRobin(轮询),比如A、B优先级相同,假设A先就绪,B后就绪,那么先跑A一段时间,再跑B一段时间,然后再跑A一段时间,再跑B…轮流跑,直到进程/线程都跑完。
这时候你可能有个疑问:假如有个进程是RT型进程,调度策略被设置了SCHED_FIFO,如果这个进程有个死循环的bug,那么CPU是不是一直会被这个进程占用?
答案是不会。
linux以前有一个sched_rt补丁,限制RT进程在指定时间周期内,最多可以占用CPU时间片比例。
// sched_rt_period_us 在指定的时间周期 us[root@dev 7]# cat /proc/sys/kernel/sched_rt_period_us1000000// sched_rt_runtime_us 在上述周期内,最多可以运行 xxx us[root@dev 7]# cat /proc/sys/kernel/sched_rt_runtime_us950000
由于各类软件总有各种各样的bug,所以操作系统在设计时,通常把运行的第三方程序看作是个渣渣。linux会用各种操作保证第三方软件的各种异常情况下,操作系统能够稳定的运行。
就像之前提到的,进程死亡(比如bug崩溃)进入僵尸态时,操作系统会将进程下所有资源释放。
对于普通进程(优先级100~139 即 nice值 -20 ~ 19)
普通进程
对于普通进程,体现的是人民的善良性,优先级高不会对优先级低的绝对优势。普通优先级的进程/线程是轮流在cpu上执行。
那么优先级又有什么用处呢?
不同优先级,优先级高的可以分配到更多cpu时间片;高优先级在就绪态时可以抢占低优先级的进程/线程,先执行高优先级。
但是我们之前说过,对于IO消耗型的任务,期望CPU可以尽快响应自己。linux会优先照顾喜欢睡眠的进程,linux在运行时会对进程统计,区分IO消耗型和CPU消耗型,对IO消耗型(经常在睡眠态的进程)奖励,对CPU消耗型惩罚。越喜欢睡,优先级越高;越喜欢跑,优先级越低。让CPU消耗型的进程和IO消耗型进程在抢占时,IO消耗型更容易抢到CPU。linux会在运行时动态的调整进程的优先级。
规则:
1、不同优先级轮转运行
2、按nice优先级抢占。就绪态时,高优先级可以抢占低优先级
3、动态奖励和惩罚
后来的调度策略,linux针对 普通进程 提出了一个CFS调度策略(完全公平调度)。
CFS:完全公平调度
CFS的目标是:追求虚拟时间的最终相等
CFS是基于红黑树实现的调度算法,算法简单到小学生都能理解。
首先我们回顾下什么是红黑树:
红黑树:一种自平衡二叉树,左边结点小于右边结点,右边结点小于最右边结点。
CFS红黑树结构
这个红黑树上的结点值记录的是进程的 “virtual runtime”
virtual runtime = 进程实际运行时间 / 进程权重 * 1024
我们可以忽略掉1024系数
https://github.com/torvalds/linux/blob/a5ad5742f671de906adbf29fbedf0a04705cebad/kernel/sched/core.c#L8026
// 优先级对应的权重const int sched_prio_to_weight[40] = { /* -20 */ 88761, 71755, 56483, 46273, 36291, /* -15 */ 29154, 23254, 18705, 14949, 11916, /* -10 */ 9548, 7620, 6100, 4904, 3906, /* -5 */ 3121, 2501, 1991, 1586, 1277, /* 0 */ 1024, 820, 655, 526, 423, /* 5 */ 335, 272, 215, 172, 137, /* 10 */ 110, 87, 70, 56, 45, /* 15 */ 36, 29, 23, 18, 15, };
优先级0的时候,权重就是1024。
linux调度普通进程时,优先选择virtual runtime值最小的进程,每次调度完之后更新红黑树上进程的virtual runtime值,因为红黑树的特性,进程在红黑树上的位置会从左移到右,所以整个红黑树会不停的向右滚动。
拿出我们小学的数学知识,相同的分母,分子越小,值越小。所以,同样的进程权重,进程实际运行时间越短,进程的virtual runtime越小,在内核调度时越容易被优先调度。
这算法思想,真是小学生都能懂。
一个进程又喜欢睡(经常在睡眠态),又权重越大(nice值越低),就会一直在CFS红黑树的左边。这种进程最容易被调度器调度。但是只要遇到RT进程,也得乖乖让路。
做个实验:
在linux系统中,每一个进程/线程的调度策略和优先级都可以不一样。
调度的最小单位 task_struct 中定义了 policy (调度策略)、 prio/static_prio/normal_prio/rt_priority(优先级),没错是我。
代码不贴了,测试系统是4核CPU,测试代码中启动4个线程,每个线程都是死循环。
后台运行两次:
[root@dev shell]$ ./a.out &[1] 2722[root@dev shell]$ thread pid:2722, tid:2722 pthread_self:140062450566976thread pid:2722, tid:2723 pthread_self:140062442186496thread pid:2722, tid:2724 pthread_self:140062433793792thread pid:2722, tid:2725 pthread_self:140062425401088thread pid:2722, tid:2726 pthread_self:140062417008384[root@dev shell]$ ./a.out &[2] 2731[root@dev shell]$ thread pid:2731, tid:2731 pthread_self:140036305585984thread pid:2731, tid:2733 pthread_self:140036288812800thread pid:2731, tid:2732 pthread_self:140036297205504thread pid:2731, tid:2735 pthread_self:140036272027392thread pid:2731, tid:2734 pthread_self:140036280420096
这时候我们可以看到cpu占用率
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2722 root 20 0 39288 384 300 S 210.3 0.0 4:14.87 a.out 2731 root 20 0 39288 380 300 S 191.7 0.0 2:34.70 a.out
linux启动一个进程的时候,默认为普通进程,nice值为0,按照上面权重和优先级的映射关系,所以分母为1024。
按照我们之前说的nice值越低(对应权重越大),根据完全公平调度的设计目标,追求虚拟时间的最终相等。
当进程2722的分母是进程2731的三倍时,同比例的分子应该也是三倍,这样才能实现虚拟时间的最终相等。
所以我们设置进程2722下所有线程的nice值为 -5 时,它的分母为3121,按道理2722进程的运行时间是2731进程的3倍,即CPU占用率为3倍时,才可以实现虚拟时间的最终相等。
[root@dev dev]# renice -n -5 -g 27222722 (进程组 ID) 旧优先级为 0,新优先级为 -5 PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 2722 root 15 -5 39288 384 300 S 309.9 0.0 3:06.42 a.out 2731 root 20 0 39288 380 300 S 87.4 0.0 1:55.07 a.out
// 设置nice值[root@dev dev]# renice -h用法: renice [-n] [-p|--pid] ... renice [-n] -g|--pgrp ... renice [-n] -u|--user ...选项: -g, --pgrp 将参数解释为进程组 ID -n, --priority 指定 nice 增加值 -p, --pid 将参数解释为进程 ID (默认) -u, --user 将参数解释为用户名或用户 ID -h, --help 显示帮助文本并退出 -V, --version 显示版本信息并退出renice -n -g
内核api:支持程序代码内人为干涉调度策略
系统调用 | 描述 |
nice() | 设置进程的nice值 |
sched_setscheduler() | 设置进程的调度策略,即设置进程采取何种调度算法 |
sched_getscheduler() | 获取进程的调度算法 |
sched_setparam() | 设置进程的实时优先级 |
sched_getparam() | 获取进程的实时优先级 |
sched_get_priority_max() | 获取实时优先级的最大值,由于用户权限的问题,非root用户并不能设置实时优先级为99 |
sched_get_priority_min() | 获取实时优先级的最小值,理由与上面类似 |
sched_rr_get_interval() | 获取进程的时间片 |
sched_setaffinity() | 设置进程的处理亲和力,其实就是保存在task_struct中的cpu_allowed这个掩码标志。该掩码的每一位对应一个系统中可用的处理器,默认所有位都被设置,即该进程可以在系统中所有处理器上执行。 用户可以通过此函数设置不同的掩码,使得进程只能在系统中某一个或某几个处理器上运行。 |
sched_getaffinity() | 获取进程的处理亲和力 |
sched_yield() | 暂时让出处理器 |
// 查看或设置一个进程调度策略和优先级[root@dev shell]# chrt -hShow or change the real-time scheduling attributes of a process.Set policy: chrt [options] [...] chrt [options] --pid Get policy: chrt [options] -p Policy options: -b, --batch set policy to SCHED_BATCH -d, --deadline set policy to SCHED_DEADLINE -f, --fifo set policy to SCHED_FIFO -i, --idle set policy to SCHED_IDLE -o, --other set policy to SCHED_OTHER -r, --rr set policy to SCHED_RR (default)Scheduling options: -R, --reset-on-fork set SCHED_RESET_ON_FORK for FIFO or RR -T, --sched-runtime runtime parameter for DEADLINE -P, --sched-period period parameter for DEADLINE -D, --sched-deadline deadline parameter for DEADLINEOther options: -a, --all-tasks operate on all the tasks (threads) for a given pid -m, --max show min and max valid priorities -p, --pid operate on existing given pid -v, --verbose display status information -h, --help 显示此帮助并退出 -V, --version 输出版本信息并退出
查看进程的调度策略和优先级[root@dev shell]# chrt --pid 7pid 7's current scheduling policy: SCHED_FIFOpid 7 的当前调度优先级:99
注意,设置进程调度策略为RT进程后,进程会受到系统对RT进程CPU占用率的限制,“限制RT进程在指定时间周期内,最多可以占用CPU时间片比例”。
而且,RT进程由于优先级高于普通进程,会将系统中普通进程抢占,会导致系统反应变慢。