1. 吞吐率和响应
吞吐:单位时间内做的有用功;
响应:低延迟。
吞吐追求的整个系统 CPU 做有用功,响应追求的是某个特定任务的延迟低;
1GHZ 的 CPU 切换线程保存恢复现场约几个微妙级别,看似消耗不了太多时间,但是由于系统的局部性原理,会保存当前线程数据的缓存,切换线程会打乱局部性引起 cache miss,而 CPU 访问 cache 速度远大于内存访问,这样综合看来上下文切换花销还是很大的。无用功占用较多 CPU;
追求吞吐量和低延迟,这两个目标是矛盾的
编译内核选项有如下
服务器版追求吞吐量,配置为不抢占;桌面版或手机更追求响应,配置为低延迟;
调度器一般讲的是最后一种,低延迟抢占;
2. Linux 任务类型
问题 2: 一个典型的系统内任务分两种:CPU 消耗型和 IO 消耗型
CPU 型的任务,通常要求高性能,但是不追求低延迟,优先级较低;
IO 型的任务 ,通常优先级更高, 追求低延迟,对 CPU 性能不敏感;
如下图,一个读 IO 循环过程,占用 CPU 时间极短(1ms),IO 占用 100ms
,若 CPU 性能降一倍,执行 CPU 占用 2ms 对整体时间影响不大(总时间 102ms);但是如果 CPU 不能及时响应,一个读写周期响应延迟 100ms,那整体时间变为约 200ms,整体性能降低一半;
所以,IO 型任务只关注响应速度,对 CPU 性能不敏感;
基于此原理 ARM 公司设计了 big.LITTLE 架构,比如在手机上有 8 个核,设计为四大核,四小核;大小核指令集完全兼容,调度器调配 IO 型的任务跑在小核上 (对 CPU 弱不敏感),CPU 型任务放在大核上跑。这样用 4 +1(四小核等效) 核的功耗实现了 8 核的性能;
调度器要在吞吐和延迟之间找到某个均衡;
3 任务调度
3.1 调度原理:
涉及两个概念,策略 和优先级;
内核所有进程优先级为 0~139 之间;
0~99:采用RT 策略,常用的有 SCHED_FIFO, SCHED_RR;
SCHED_FIFO, 属于霸占型的,高优先级执行完,才会执行低优先级;
SCHED_RR, 不同优先级与 CHED_FIFO 相同,属于霸占型,同等优先级轮转;
比如有 4 个进程:P1_FIFO_3, P2_CHED_RR_2, P3_CHED_RR_2, P4_FIFO_4
执行过程,P2/P3 轮转执行完之后,执行 P1, 最后执行 P4。
100~139:普通线程,采用非 RT 策略,对应 nice(-20, 19). 这里所有优先级都是可以抢占的,但 nice 值越低,分配 CPU 资源越多。
3.2 调度对象是 task_struct
所有调度,SCHER_FIFO/SCHER_RR 或设置 nice 都是针对 task_struct;
3.3 将进程设置为 FIFO 策略:
修改进程 25020 的策略为 FIFO, 优先级 50
sudo chrt –f –a –p 50 25020
这样进程是按 FIFO 策略调度,占有 CPU 最高为 80%(普通进程可以接近 100%),CPU 占有率降低(但不可抢占),此时 IO 延迟反而变大,鼠标操作变慢;
设置 FIFO 线程 api
内核里优先级 =99-50=49
内核态数字越低,优先级越高;用户态相反;
4. 每个 task_struct 的 nice 都可以单独设置(所有普通线程);
Nice 值设置都是指普通线程,RT 策略不支持 nice;
nice()默认是 0
调度,在不同 nice 值进程间轮转,
2.6 早期内核对进程采取奖励和惩罚算法,越睡眠越奖励,越消耗 CPU,越惩罚,动态调整 nice 值,实现极其复杂,后来升级两个补丁;
补丁 1:RT 熔断机制,设置 rt 门限值
默认 runtime 0.95s,period 1s,1s 内 RT 最多跑了 0.95s 自动熔断;
$cd /proc/sys/kernel
sudo sh -c 'echo 800000 > sched_rt_runtime_us'
设置 RR/FIFO 策略熔断最高位 800ms,RR/FIFO 策略进程最多占用 CPU 80%。(默认是 95%)
补丁 2:普通进程调度算法 CFS(complete fare schedule)
虚拟运行时间,
vtime = ptime * 1024/weight
ptime:物理时间
weight: 权重参数
nice=0,虚拟时间等于物理时间;
nice 值越小,对应 weight 分母越大,vtime 增长越慢,实际对应 ptime 占用越多;
vtime 机制很好的平衡了 I / O 型,CPU 型任务;
I/ O 型喜欢睡眠,ptime 比较小,所以 vtime 自然较小,会偏向于挂在树左边;
同理,优先级越高的 CPU 型,其 weight 值越大,vtime 也会越小,亦偏向挂在树左边;
即 CFS 用很简单的方式实现了历史上复杂的睡眠补偿,消耗惩罚,动态调整等功能;
修改进程的 nice 值:
sudo renice -n -5 -g 24856 //24856 进程的所有线程 nice 都设置为 -5
综上,linux 调度算法过程:
1. 首先执行 SCHECH_RR/SCHECH_FIFO 进程,待他们执行到睡眠或者熔断,CPU 切换到普通线程;
2. 普通线程按 CFS 算法调度,在普通线程间轮转;
线程调度优先与线程是否在内核态无关,只由优先级和策略决定。用户态内核态只涉及权限问题;
关于 CFS 实现细节,参考多年前的一篇文章
http://blog.chinaunix.net/uid-24708340-id-3787960.html