利用 sched_ext 作為框架撰寫一個將所有任務全部塞給指定 CPU core 執行的排程器!參考 scx_central
與 scx_simple
。
在初始化函式 randmigrate_init
當中只需要做兩件事,讓該排程器排程所有任務以及建立一個 FALLBACK_DSQ 。
int BPF_STRUCT_OPS_SLEEPABLE(randmigrate_init)
{
int ret;
__COMPAT_scx_bpf_switch_all();
ret = scx_bpf_create_dsq(FALLBACK_DSQ_ID, -1);
return ret;
}
挑選 CPU 處則是任意挑選即可,此處不是最後真正任務被分配到的 CPU 。
s32 BPF_STRUCT_OPS(randmigrate_select_cpu, struct task_struct *p,
s32 prev_cpu, u64 wake_flags)
{
s32 cpu = bpf_get_prandom_u32() % nr_cpus;
return cpu;
}
select_cpu 當中沒有利用到 scx_bpf_dispatch()
也就意味著 ops.enqueue()
一定會被呼叫,我們實作自己的 enqueue()
,概念和 select_cpu()
類似,若任務是 kthread 的任務則分配到 SCX_LOCAL
當中,若不是則放到 FALLBACK_DSQ_ID
當中並喚醒挑選到的 CPU 。
void BPF_STRUCT_OPS(randmigrate_enqueue, struct task_struct *p, u64 enq_flags)
{
__sync_fetch_and_add(&nr_total, 1);
if ((p->flags & PF_KTHREAD) && p->nr_cpus_allowed == 1) {
__sync_fetch_and_add(&nr_locals, 1);
scx_bpf_dispatch(p, SCX_DSQ_LOCAL, SCX_SLICE_INF, enq_flags | SCX_ENQ_PREEMPT);
return;
}
s32 cpu = bpf_get_prandom_u32() % nr_cpus;
__sync_fetch_and_add(&nr_queued, 1);
scx_bpf_dispatch(p, FALLBACK_DSQ_ID , SCX_SLICE_INF, enq_flags);
if (!scx_bpf_task_running(p))
scx_bpf_kick_cpu(cpu, SCX_KICK_PREEMPT);
}
dispatch()
則是在 CPU 發現自己的 local dsq 當中沒有任務可執行時會呼叫的函式,我們實作的原理也很簡單,若非我們指定要轟炸的 CPU ,則不做任何事讓它回到 idle state 。若是的話則從 FALLBACK_DSQ_ID
當中拿任務出來。
void BPF_STRUCT_OPS(randmigrate_dispatch, s32 cpu, struct task_struct *prev)
{
if (cpu == central_cpu) {
__sync_fetch_and_add(&nr_dispatches, 1);
if (scx_bpf_consume(FALLBACK_DSQ_ID))
return;
s32 wake_cpu = bpf_get_prandom_u32() % nr_cpus;
scx_bpf_kick_cpu(wake_cpu, SCX_KICK_PREEMPT);
}
}
到此處為止就大功告成了!
編譯後將排程器掛載至我們的系統當中,此時應該可以發現你的電腦反應變得很慢,因為只有一顆 CPU 在做事。利用 htop
命令可以觀察到類似以下結果
只有一顆 CPU core 也就是 5 在工作而已,若是此時還利用 stress-ng
等工具產生大量 workload ,可以看到以下有趣的結果
$ stress-ng --cpu 12 --timeout 60s --metrics-brief
整台電腦或者 server 會陷入嚴重卡頓的狀況,若使用 ssh 連線也會中斷,想停下 stress-ng
命令也需要等待很長的時間,非常有趣的過程!
原始程式碼在我的 github repo 的 scx_randmigrate 分支底下,按照原本 scx
的安裝方式即可在 build/scheds/c/scx_randmigrate
處使用此排程器!