min_vruntime;ok = @cast(_cfs_rq, "struct cfs_rq")->nr_running - sched_nr_latency;t_curr = task_current();t_se = $p;se_curr = container_to_。从一个CFS调度案例谈Linux系统卡顿的根源( 四 )。" />

从一个CFS调度案例谈Linux系统卡顿的根源( 四 )

<------- end pick_next_entity -------\n"); printf("###################\n");}probe kernel.function("pick_next_task_fair").return{ if($return != 0) {se =t_se = container_of_entity(se);t_curr = task_current();printf("Return task: %s[%d]From current: %s[%d]\n", task_execname(t_se), task_pid(t_se), task_execname(t_curr), task_pid(t_curr)); } printf("<--------------- end pick_next_task_fair ---------------\n"); printf("###########################################################\n"); g_cfs_rq = 0;}probe kernel.function("set_last_buddy"){ se_se = $se; print_task("=== set_last_buddy", se_se, 0, 0);}probe kernel.function("__clear_buddies_last"){ se_se = $se; print_task("=== __clear_buddies_last", se_se, 0, 0);}probe kernel.function("check_preempt_wakeup"){ printf("--------------- begin check_preempt_wakeup --------------->\n"); _cfs_rq =min_vruntime = @cast(_cfs_rq, "struct cfs_rq")->min_vruntime; ok = @cast(_cfs_rq, "struct cfs_rq")->nr_running - sched_nr_latency; t_curr = task_current(); t_se = $p; se_curr = container_to_entity(t_curr); se_se = container_to_entity(t_se); vrun_curr = @cast(se_curr, "struct sched_entity")->vruntime; vrun_se = @cast(se_se, "struct sched_entity")->vruntime; printf("curr wake:[%s]woken:[%s]\t", task_execname(t_curr), task_execname(t_se)); printf("UUUUU curr:%dse:%d min:%d\t", vrun_curr, vrun_se, min_vruntime); printf("VVVVV delta:%d%d\n", vrun_curr - vrun_se, ok);}probe kernel.function("check_preempt_wakeup").return{ printf("<--------------- end check_preempt_wakeup ---------------\n"); printf("###########################################################\n");}重新做实验 , 我们跑几遍脚本 , 终于采集到一个case , 且看下面的输出:
.....--------------- begin check_preempt_wakeup --------------->curr wake:[producerAproduc]woken:[consumerAconsum]UUUUU curr:17886790442766se:17886787442766 min:20338095223270 VVVVV delta:30000001=== set_last_buddy 0xffff8800367b4a40producerAproduc 26285<--------------- end check_preempt_wakeup ---------------###########################################################--------------- begin pick_next_task_fair --------------->------- begin pick_next_entity ------->LAST:[producerAproduc] vrun:17886790442766FIRST:[consumerAconsum] vrun:17886787442766 delta: 3000000<------- end pick_next_entity -------###################Return task: consumerAconsum[26274]From current: producerAproduc[26285]<--------------- end pick_next_task_fair ---------------###########################################################--------------- begin pick_next_task_fair --------------->------- begin pick_next_entity ------->#【注意这里的case!】#【本来loop_sleep将要投入运行的 , 结果被上次被抢占从而设置为last的producerAproduc抢先!!】LAST:[producerAproduc] vrun:17886790442766FIRST:[loop_sleep] vrun:17886790410519delta: 32247<------- end pick_next_entity -------###################=== __clear_buddies_last 0xffff8800367b4a40producerAproduc 26285#【放弃loop_sleep , 选择producerAproduc】Return task: producerAproduc[26285]From current: consumerAconsum[26274]<--------------- end pick_next_task_fair ---------------###########################################################--------------- begin pick_next_task_fair --------------->------- begin pick_next_entity ------->FIRST:[loop_sleep] vrun:17886790410519delta: N/A<------- end pick_next_entity -------###################Return task: loop_sleep[4227]From current: producerAproduc[26285]<--------------- end pick_next_task_fair ---------------.......抓到的这个现场 , 意思是 , 本应该投入运行的loop_sleep进程竟然 由于CPU cache亲和的原因 被一个之前设置的last锚点进程给抢先运行了 , 虽然这个从CPU的视角来看 , 最大化了cache的利用率 , 但是我们从业务的角度来看 , 这并不合理 。
由于代码和时间戳之间紧密耦合 , 很难构造持续LAST_BUDDY抢先的场景 , 但只要发现类似上例一处此类case , 基本就说明问题了 , 一旦发生全局同步式的共振 , 某些进程耦合霸屏造成系统卡顿的场面就复现了 。
总体而言 , LAST_BUDDY feature为CFS红黑树的leftmost node引入了一个有力的竞争者 , 而leftmost node很有可能会输掉竞争而无法投入运行 , 这是不是破坏了CFS引以为傲的精妙简单的红黑树结构带来快速进程选择的收益呢?
一棵红黑树被各种启发式feature啄木鸟般啄得体无完肤!
想当初 , O ( 1 ) O(1)O(1)调度器就是因为越来越多的或补偿 , 或惩罚的启发式trick推动了新一代CFS的上位 , 期间经历了RSDL(The Rotating Staircase Deadline Schedule)等过渡 , 最终都没有CFS来的简单直接 。 如今 , CFS又开始逐渐变复杂 , 变臃肿的节奏 。 事实上 , 这把CFS调度器最终也变成了各种trade off 。
接下来 , 我来稍作评价 。
也许loop_sleep是一个急切要运行的进程 , 它可能救经理于水火 , 应用程序可以无视CPU的cache利用率 , 但是却不能无视其业务逻辑以及延迟执行的后果!