调度器28—杂项汇总
一、获取绑核信息
1. 通过 /proc/<pid>/status 获取
# cat /proc/<pid>/status | grep Cpus_allowed Cpus_allowed: ff Cpus_allowed_list: 0-7
调用路径和函数:
struct pid_entry tgid_base_stuff[] //fs/proc/base.c ONE("status", S_IRUGO, proc_pid_status), struct pid_entry tid_base_stuff[] //fs/proc/base.c ONE("status", S_IRUGO, proc_pid_status), proc_pid_status //fs/proc/array.c task_cpus_allowed //fs/proc/array.c static void task_cpus_allowed(struct seq_file *m, struct task_struct *task) { //取的是 task->cpus_ptr 而不是 task->cpus_mask seq_printf(m, "Cpus_allowed:\t%*pb\n", cpumask_pr_args(task->cpus_ptr)); seq_printf(m, "Cpus_allowed_list:\t%*pbl\n", cpumask_pr_args(task->cpus_ptr)); }
注:
(1) 这个cat出来的cpu会受到cgroup cpuset的限制,比如echo pid到只有cpu0-3的background分组的cpuset中时,Cpus_allowed_list就是0-3。
(2) cgroup 中 cpuset 的绑核优先级高于per-任务的绑核。比如将任务p taskset -p 7f pid 绑定到CPU0-6,但是一旦将任务echo到 top-app 分组中,任务的cpu亲和性又是0xff了。但是将任务先放到background分组中,此时cpu亲和性是0x0f,此时将任务 taskset -p 7f pid 绑定到CPU0-6,任务的cpu亲和还是0x0f。
2. 通过 sched_getaffinity() 系统调用获取
//系统调用 int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); //内核函数,其中 mask 为返回给用户空间的参数 long sched_getaffinity(pid_t pid, struct cpumask *mask) { struct task_struct *p = find_process_by_pid(pid); //这里使用的是 p->cpus_mask,而且是与 cpu_active_mask 相与后的结果 cpumask_and(mask, &p->cpus_mask, cpu_active_mask); }
3. 两种获取方式的差别
sched_getaffinity() 系统调用返回的是 p->cpus_mask & cpu_active_mask 的结果,注意与上后,此结果就受到offline和isolate cpu核的影响了。sched_setaffinity() 系统调用最终设置的是 p->cpus_mask。而 cat /proc/<pid>/status 获取的直接是 task->cpus_ptr 的值,不会受到 cpu_active_mask 影响。
4. p->cpus_ptr 指针和 p->cpus_mask 变量的区别
sched_setaffinity() 系统调用最终调用到调度类的 .set_cpus_allowed 回调,5大调度类的此回调都指向 set_cpus_allowed_common(),它里面设置的是 p->cpus_mask。在 kernel/sched 下检索,发现 cpus_ptr 在 fair.c、deadline.c、rt.c、core.c 中使用,而 cpus_mask 只在 core.c 中使用。在 core.c 中可以看到 p->cpus_mask 只是在 sched_setaffinity()/sched_getaffinity() 系统调用执行路径中使用到。
默认其实二者是同一个东西,如下:
//fork --> copy_process --> dup_task_struct static struct task_struct *dup_task_struct(struct task_struct *orig, int node) { ... if (orig->cpus_ptr == &orig->cpus_mask) tsk->cpus_ptr = &tsk->cpus_mask; ... } //追溯到最初首个任务: //init/init_task.c struct task_struct init_task = { ... .cpus_ptr = &init_task.cpus_mask, .cpus_mask = CPU_MASK_ALL, .nr_cpus_allowed= NR_CPUS, //这个最初可能是32 ... }
p->cpus_ptr 默认是指向 p->cpus_mask 的,但是在某些特殊情况下也可能不等于,比如要放开后台线程的cpu分组,会设置 p->cpus_ptr 指向其它mask.如下:
p->cpus_ptr = new_mask;
p->nr_cpus_allowed = cpumask_weight(new_mask);
二、调度中的锁
1. __task_rq_lock 注释翻译
(1) 串行化规则:
锁定顺序:
p->pi_lock rq->lock hrtimer_cpu_base->lock (hrtimer_start() 带宽控制使用) rq1->lock rq2->lock 条件: rq1 < rq2
(2) 常规状态:
正常情况下调度状态由 rq->lock 序列化。 __schedule() 获取本地 CPU 的 rq->lock,它可以选择从运行队列中删除任务,并始终查看本地 rq 数据结构以找到最符合要求的任务来运行。
任务入队也在 rq->lock 的保护下,任务可能取自另一个 CPU。 来自另一个 LLC 域的唤醒可能会使用 IPI 将入队传输到本地 CPU,以避免在周围反弹(bouncing)运行队列的状态 [参见 ttwu_queue_wakelist()]
任务唤醒,特别是涉及迁移的唤醒,为避免不得不使用两个 rq->locks,非常复杂。
(3) 特殊状态:
系统调用和任何外部操作都将使用 task_rq_lock() 获取 p->pi_lock 和 rq->lock。 因此,它们更改的状态在持有任一锁时都是稳定的:
- sched_setaffinity()/set_cpus_allowed_ptr(): p->cpus_ptr, p->nr_cpus_allowed - set_user_nice(): p->se.load, p->*prio - __sched_setscheduler(): p->sched_class, p->policy, p->*prio, p->se.load, p->rt_priority, p->dl.dl_{runtime, deadline, period, flags, bw, density} - sched_setnuma(): p->numa_preferred_nid - sched_move_task()/cpu_cgroup_fork(): p->sched_task_group - uclamp_update_active(): p->uclamp*
(4) p->state <- TASK_*:
使用 set_current_state(),__set_current_state() 或 et_special_state() 无锁地更改,查看它们各自的注释,或通过 try_to_wake_up()。 后者使用 p->pi_lock 针对并发自身进行序列化。
(5) p->on_rq <- { 0, 1 = TASK_ON_RQ_QUEUED, 2 = TASK_ON_RQ_MIGRATING }:
由 activate_task() 设置并由 deactivate_task() 在 rq->lock 下清除。 非零表示任务是可运行的,特殊的 ON_RQ_MIGRATING 状态用于迁移而不持有两个 rq->locks。它表示 task_cpu() 不稳定,参见 task_rq_lock()。
(6) p->on_cpu <- { 0, 1 }:
由 prepare_task() 设置并由 finish_task() 清除,这样它将在 p 被调度之前设置并在 p 被切换走之后清除,两者都在 rq->lock 的保护下。 非零表示任务正在其 CPU 上运行。[ 精明的读者会观察到,一个 CPU 上的两个任务可能同时具有 ->on_cpu = 1。]
(7) task_cpu(p):由 set_task_cpu() 改变,规则为:
- 不要在阻塞的任务上调用 set_task_cpu(): 我们不关心我们没有在上面运行的CPU,这简化了热插拔,阻塞任务的 CPU 分配不需要是有效的。 - 用于 try_to_wake_up(),在 p->pi_lock 下调用: 这允许 try_to_wake_up() 只使用一个 rq->lock,查看它的注释。 - 用于在 rq->lock 下调用的迁移: [参见 task_rq_lock() 中的 task_on_rq_migrating()] move_queued_task() detach_task() - 用于在 double_rq_lock() 下调用的迁移: __migrate_swap_task() push_rt_task() / pull_rt_task() push_dl_task() / pull_dl_task() dl_task_offline_migration()
三、调度类在5.10上放弃使用next指针
改为使用链接器将调度类链接在一起了,低优先级的调度类在前,高优先的调度类在后。
//fair.c 里面已经没有 next 指针了 const struct sched_class fair_sched_class __section("__fair_sched_class") = { ... } #define __section(section) __attribute__((__section__(section))) //include/asm-generic/vmlinux.lds.h #define SCHED_DATA \ STRUCT_ALIGN(); \ __begin_sched_classes = .; \ *(__idle_sched_class) \ *(__fair_sched_class) \ *(__rt_sched_class) \ *(__dl_sched_class) \ *(__stop_sched_class) \ __end_sched_classes = .; //使用下面宏访问: //kernel/sched/sched.h /* Defined in include/asm-generic/vmlinux.lds.h */ extern struct sched_class __begin_sched_classes[]; extern struct sched_class __end_sched_classes[]; #define sched_class_highest (__end_sched_classes - 1) #define sched_class_lowest (__begin_sched_classes - 1) #define for_class_range(class, _from, _to) \ for (class = (_from); class != (_to); class--) //[ ) #define for_each_class(class) \ for_class_range(class, sched_class_highest, sched_class_lowest)
四、cpumask_var_t 和 struct cpumask
typedef struct cpumask { unsigned long bits[1]; } cpumask_t; typedef struct cpumask cpumask_var_t[1];
cpumask_var_t 类型为 "struct cpumask [1]",是一个数组长度为1的数组名,也可以认为是指向struct cpumask类型的常指针,除了做左值外等效于"struct cpumask*"。
举例:
#include <stdio.h> typedef struct cpumask { unsigned long bits[1]; } cpumask_t; typedef struct cpumask cpumask_var_t[1]; cpumask_var_t mt = {0xef}; //定义数组并赋值 void print_mask_bit(struct cpumask *mask) { printf("mask: 0x%lx\n", mask->bits[0]); } void main() { print_mask_bit(mt); //注意传参 } /* $ gcc main.c -o pp $ ./pp mask: 0xef */
五、Cache优化
1. cache预取
prefetch_curr_exec_start
prefetch(&curr->exec_start);
posted on 2022-01-22 16:02 Hello-World3 阅读(1024) 评论(0) 编辑 收藏 举报