Sched ext回调2——enable(linux 6.15.7)

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

相关推荐
步步为营DotNet3 小时前
深度剖析.NET中IHostedService:后台服务管理的关键组件
服务器·网络·.net
一叶星殇3 小时前
.NET WebAPI:用 Nginx 还是 IIS 更好
运维·nginx·.net
LUCIFER4 小时前
[驱动进阶——MIPI摄像头驱动(五)]rk3588+OV13855摄像头驱动加载过程详细解析第四部分——ISP驱动
linux·驱动开发
暮云星影4 小时前
四、linux系统 应用开发:UI开发环境配置概述 (一)
linux·ui·arm
a程序小傲5 小时前
得物Java面试被问:RocketMQ的消息轨迹追踪实现
java·linux·spring·面试·职场和发展·rocketmq·java-rocketmq
Ghost Face...5 小时前
i386 CPU页式存储管理深度解析
java·linux·服务器
LEEE@FPGA5 小时前
zynq 是不是有了设备树,再linux中不需要编写驱动也能控制
linux·运维·单片机
Trank-Lw5 小时前
Docker Devcontainer 管理命令
运维·docker·容器
RisunJan5 小时前
Linux命令-less(分页查看器)
linux·运维
赛希咨询5 小时前
人工智能自动化如何提高研究生产力
运维·人工智能·自动化