优雅选择调度策略/优先级
- 本文主要介绍综合考虑内核中断,内核线程以及用户线程的情况下,用户线程如何选择调度策略以及优先级。
问题引入
- 调试板子时,运行程序,概率性遇到问题
mmc1: Timeout waiting for hardware interrupt。搞得一头雾水。 - 后来东搞西搞,发现将程序中的线程绑定到 cpu1 上,反复测试后,都不会出现问题。
思考原因
-
程序默认将线程绑定到 cpu0 上,且使用的是 SCHED_FIFO 策略,会不会是抢占了 cpu0,导致中断或者内核线程(默认策略为 SCHED_NORMAL)没有及时得到运行。但是,转念一想,内核一般都有负载均衡策略,就算 cpu0 被占用,也有其他的 cpu 可以使用。奈何也想不出其他原因,就打算先从中断入手。
-
cat /proc/interrupts查看中断情况,输出如下:CPU0 CPU1 CPU2 CPU3 19: 26131 12743 12604 20136 GIC-0 27 Level arch_timer 21: 32332 18752 16822 20502 GIC-0 56 Level rk_timer 27: 2 0 0 0 GIC-0 36 Level ff3f0000.i2c 35: 0 0 0 0 GIC-0 71 Level rockchip_thermal 36: 199319 0 0 0 GIC-0 145 Level rockchip_thermal 37: 0 0 0 0 GIC-0 90 Level rkcifhw 38: 77 0 0 0 GIC-0 94 Level rga 39: 0 0 0 0 GIC-0 91 Level ffb00f00.iommu, ffb00000.vop 59: 34562 0 0 0 GIC-0 127 Level eth0 61: 79287 0 0 0 GIC-0 110 Level dw-mci -
发现大部分中断都在 cpu0 上,这就让人疑惑了,不是有负载均衡嘛,怎么在其他 cpu 上没有。然后查看中断号的 cpu 亲和性,
cat /proc/irq/59/smp_affinity输出为f,cat /proc/irq/59/smp_affinity_list输出为0-3,即中断可以在所有 cpu 上处理。 -
到这,可以提出猜想:即使有负载均衡策略而且中断号的 cpu 亲和性设置为全部 cpu, 但某些中断号以及内核线程就是只能在 cpu0 上处理,用户程序长时间占用 cpu0,就可能造成内核无法及时相应。
验证猜想
-
验证方法:运行以下代码,然后将
affinity改为1后,再次运行,如果前者引起系统卡顿,而后者没有,则可以验证猜想。 -
验证代码:
int main(void) { int affinity = 0; cpu_set_t mask; CPU_ZERO(&mask); CPU_SET(affinity, &mask); pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask); sched_param sp{ 0 }; sp.sched_priority = 1; pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp); while (1); } -
现象对比:
内核 cpu gcc 版本 绑定 cpu0 绑定 cpu1 Linux 5.15.0-86-generic Ubuntu x86_64 AMD Ryzen 7 3800X 11.3 系统一切正常 系统一切正常 Linux 5.10.160 SMP aarch64 RK3588 10.3 网络卡顿,ping卡住
通过串口输入crtl+c卡顿
top ps 等命令卡住系统一切正常 Linux 4.19.111 SMP PREEMPT armv7l RV1126 8.3 网络卡顿,ping卡住
通过串口输入crtl+c卡顿
top ps 等命令卡住系统一切正常
结论
- 该问题看来只会在某些平台上出现,使用存在该问题的平台时,可以参考下面的建议。
- 用户空间线程最好使用默认的调度策略,如有需要使用类似
SCHED_FIFO的实时调度策略,则一定要设置其 cpu 亲和性,确保其在 cpu0 以外的 cpu 上运行。 - 用户空间线程如果需要设置 cpu 亲和性,则应避免在 cpu0 上绑定过多的线程。
- 对于核心够用的 cpu,完全可以把所有线程绑定在除 cpu0 以外的 cpu 上,把 cpu0 留给系统使用。