struct sched_ext_ops代表了一个调度器,里面定义了很多回调函数,本文分析enable这个hook,sched ext的部分hook如下:

enable 函数仅在 task 被sched ext调度器接管时才会触发;而 init_task 函数则无论 task 是否被 sched ext 调度器接管,都会被触发。
一、以全量接管模式注册sched ext调度器,接管task触发enable
SCX_OPS_ATTACH(...,numa_aware_ops, ...) ← numa_aware_ops是sched ext调度器
├─ bpf_map__attach_struct_ops
| ├─ bpf_link_create
| | └─ BPF_CALL_3(bpf_sys_bpf, int, cmd, union bpf_attr *, attr, u32, attr_size)
↓ ↓ (进入内核态)
bpf_sys_bpf
├─ __sys_bpf(BPF_LINK_CREATE,...)
| ├─ link_create
| | ├─ bpf_struct_ops_link_create
| | | └─ st_map->st_ops_desc->st_ops->reg(st_map->kvalue.data, &link->link)
↓ ↓ ( 即 bpf_scx_reg )
bpf_scx_reg
├─ scx_ops_enable
| ├─ scx_ops_bypass(true)
| ├─ scx_ops_init_task_enabled = true
| |
| |------- scx_task_iter_start(&sti);
| |代 while ((p = scx_task_iter_next_locked(&sti))) {
| |码 ...
| |段 scx_ops_init_task ← 对scx_tasks中的每个task执行init_task
| | ------ }
| |
| | ------- scx_task_iter_start(&sti);
| | 代 while ((p = scx_task_iter_next_locked(&sti))) {
| | 码 ...
| | 段 __setscheduler_class ← 全量接管模式fair_sched_class的task,返回ext class
| | check_class_changing ← 对scx_tasks中的每个task执行switching_to_scx
| | ------ } └─ p->sched_class->switching_to ← 即 switching_to_scx
| | └─ scx_ops_enable_task
| | └─ SCX_CALL_OP_TASK(SCX_KF_REST, enable, rq, p)
| ├─ scx_ops_bypass(false)
sched_ext 调度器(struct sched_ext_ops)默认接管系统所有 task(全量接管模式,scx_switching_all = true)。
struct sched_ext_ops->flags 中设置 SCX_OPS_SWITCH_PARTIAL 标记,则启用部分接管模式,只接管用户指定sched ext调度策略的task,其余 task 仍由内核默认调度器管理。
全量接管系统内已存在的task,见scx_ops_enable,代码片段如下:
cpp
percpu_down_write(&scx_fork_rwsem);
scx_task_iter_start(&sti);
while ((p = scx_task_iter_next_locked(&sti))) {
const struct sched_class *old_class = p->sched_class;
const struct sched_class *new_class =
__setscheduler_class(p->policy, p->prio);
struct sched_enq_and_set_ctx ctx;
if (old_class != new_class && p->se.sched_delayed)
dequeue_task(task_rq(p), p, DEQUEUE_SLEEP | DEQUEUE_DELAYED);
sched_deq_and_put_task(p, DEQUEUE_SAVE | DEQUEUE_MOVE, &ctx);
p->scx.slice = SCX_SLICE_DFL;
p->sched_class = new_class;
check_class_changing(task_rq(p), p, old_class);
sched_enq_and_set_task(&ctx);
check_class_changed(task_rq(p), p, old_class, p->prio);
}
scx_task_iter_stop(&sti);
percpu_up_write(&scx_fork_rwsem);
在__setscheduler_class函数中,task调度类(sched class)为fair_sched_class时返回ext_sched_class。
cpp
const struct sched_class *__setscheduler_class(int policy, int prio)
{
if (dl_prio(prio))
return &dl_sched_class;
if (rt_prio(prio))
return &rt_sched_class;
#ifdef CONFIG_SCHED_CLASS_EXT
if (task_should_scx(policy))
return &ext_sched_class;
#endif
return &fair_sched_class;
}
bool task_should_scx(int policy)
{
if (!scx_enabled() ||
unlikely(scx_ops_enable_state() == SCX_OPS_DISABLING))
return false;
if (READ_ONCE(scx_switching_all))
return true;
return policy == SCHED_EXT;
}
接着,scx_ops_enable -> check_class_changing检测到task的调度类发生了变化(fair_sched_class变成了ext_sched_class),执行新调度类的switching_to钩子,即switching_to_scx。后面如何触发enable函数的,请参考前面的调用栈。
二、fork task时,接管task触发enable
SYSCALL_DEFINE0(fork)
├─ kernel_clone
| ├─ copy_process()
| | ├─ dup_task_struct
| | | ├─ arch_dup_task_struct
| | | | └─ memcpy(dst, src, arch_task_struct_size) ← 继承父task名
| | | |
| | ├─ strscpy_pad(p->comm, args->name, sizeof(p->comm)) ← 内核线程设置task
| | ├─ sched_fork()
| | | └─ scx_pre_fork()
| | ├─ ...
| | |
| | ├─ sched_cgroup_fork()
| | | ├─ 设置 p->sched_task_group ← 设置cgroup
| | | ├─ __set_task_cpu()
| | | ├─ p->sched_class->task_fork() ← sched ext没有实现这个hook
| | | └─ scx_fork()
| | | └─ scx_ops_init_task() ← scx_ops_init_task_enabled为true时,有条件触发
| | └─ BPF_STRUCT_OPS(init_task)
| ├─ sched_post_fork
| | └─ scx_post_fork
| | ├─ scx_ops_enable_task
| | | └─ SCX_CALL_OP_TASK(SCX_KF_REST, enable, rq, p)
| └─ list_add_tail(&p->scx.tasks_node, &scx_tasks)
|
├─ wake_up_new_task
相关代码如下:
cpp
void scx_post_fork(struct task_struct *p)
{
if (scx_ops_init_task_enabled) { 《==================== 条件1
scx_set_task_state(p, SCX_TASK_READY);
if (p->sched_class == &ext_sched_class) { 《==================== 条件2
struct rq_flags rf;
struct rq *rq;
rq = task_rq_lock(p, &rf);
scx_ops_enable_task(p);
task_rq_unlock(rq, p, &rf);
}
}
spin_lock_irq(&scx_tasks_lock);
list_add_tail(&p->scx.tasks_node, &scx_tasks);
spin_unlock_irq(&scx_tasks_lock);
percpu_up_read(&scx_fork_rwsem);
}
从上面代码可以看出,对于fork task触发enable的路径,需要满足2个条件:
1)scx_ops_init_task_enabled = true
执行SCX_OPS_ATTACH注册sched ext调度器时,scx_ops_init_task函数将scx_ops_init_task_enabled设置成true。
2)task的调度器类为ext_sched_class,接task被sched ext接管
fork函数没有任何参数,是没法将新task的调度类(sched class)指定为ext_sched_class的,所以新task如何满足条件2从而触发enable函数的呢?fork触发enable,是针对下面情况的。
如果父进程设置了ext_sched_class,fork新task时,fork -> copy_process -> dup_task_struct会复制父进程struct task_struct内容到新task中,因struct task_struct -> sched_class指向调度类,所以父task、新task的调度类是一样的。
三、通过系统调用,将task设置成sched ext策略,接管task触发enable
SYSCALL_DEFINE3(sched_setscheduler,......
SYSCALL_DEFINE2(sched_setparam ,......
SYSCALL_DEFINE3(sched_setattr, ......
以上系统调用都会执行到: __sched_setscheduler -> check_class_changing -> switching_to_scx -> scx_ops_enable_task。
以sched_setscheduler为例:
SYSCALL_DEFINE3(sched_setscheduler, ...
└─ do_sched_setscheduler
└─ sched_setscheduler
└─ _sched_setscheduler
└─ __sched_setscheduler
├─ __setscheduler_class ← 全量接管模式fair_sched_class -> sched ext class
├─ check_class_changing
├─ p->sched_class->switching_to ← 即 switching_to_scx
├─ scx_ops_enable_task
| └─ SCX_CALL_OP_TASK(SCX_KF_REST, enable, rq, p)
四、enable与其他hook的时序
1,对于fork出的新task,从(一)调用栈可以知道,先init_task,再enable
2,对于已存在的task,从Sched ext回调1------init_task,可以知道一定先init_task,然后才enable
总结,按时序 init_task -> enable -> enqueue