cpu offline/online时线程的绑核属性设置的相关细节

张开发
2026/4/18 12:24:52 15 分钟阅读

分享文章

cpu offline/online时线程的绑核属性设置的相关细节
一、背景在之前的博客 balance_callbacks及cpu offline的相关细节 里我们介绍了cpu offline时会进行的balance callback的行为的相关细节里面讲到了balance_push函数有进行把offline cpu上运行的任务push到别的cpu上的动作。但是任务的绑核属性的cpus_mask是如何变化的呢在这篇博客里进行实验和细节介绍。在下面第二章里我们先通过cpu online offline的实验来观察任务的cpus_mask的变化通过ko程序抓取cpus_mask的变化的调用栈然后进行调用链分析在第三章里进一步进行实验分析第二章实验里没有涉及到的offline-online部分的cpus_mask的变化的调用栈。二、cpu online-offline时的cpus_mask的变化的实验及原理我们启动一个程序设置了一些cpu作为绑定的cpu然后挑选里面的一个cpu进行cpu offline的操作就可以发现任务的绑核属性发生了变化offline的那个cpu在任务的mask列表里不在了我们编写一个ko来抓取这个任务的绑核属性的变化是如何触发的。2.1 实验ko源码下面实验ko源码是基于 2.3.1 的分析使用数据断点针对监控任务的cpus_mask来捕获调用栈有关数据断点的更多细节见 观测指定内存上是否被读写若触发条件打印调用栈#include linux/module.h /* Needed by all modules */ #include linux/kernel.h /* Needed for KERN_INFO */ #include linux/init.h /* Needed for the macros */ #include linux/kallsyms.h #include linux/perf_event.h #include linux/hw_breakpoint.h static int watchpid 0; module_param(watchpid, int, 0); struct task_struct *watchp NULL; struct perf_event * __percpu *sample_hbp; struct task_struct* get_task_struct_by_pid(int pid) { struct pid* pid_struct; struct task_struct* ptask NULL; pid_struct find_get_pid(pid); if (pid_struct) { ptask get_pid_task(pid_struct, PIDTYPE_PID); if (ptask) { put_task_struct(ptask); } put_pid(pid_struct); } return ptask; } static void sample_hbp_handler(struct perf_event *bp, struct perf_sample_data *data, struct pt_regs *regs) { printk(KERN_INFO zhaoxin:pid[%d] cpus_mask is [addr:0x%llx]changed[%*pbl]\n, watchpid, (u64)watchp-cpus_mask, cpumask_pr_args(watchp-cpus_mask)); dump_stack(); } static int __init test_offline_mask_init(void) { int ret 0; struct perf_event_attr attr; struct task_struct *task; printk(sizeof(current-cpus_mask)%d, sizeof(current)%d\n, (int)sizeof(current-cpus_mask), (int)sizeof(*current)); if (watchpid 0) { printk(KERN_INFO watchpid 0!\n); return -EINVAL; } task get_task_struct_by_pid(watchpid); if (!task) { return -ESRCH; } watchp task; printk(KERN_INFO zhaoxin:pid[%d] cpus_mask is [addr:0x%llx][%*pbl]\n, watchpid, (u64)watchp-cpus_mask, cpumask_pr_args(watchp-cpus_mask)); hw_breakpoint_init(attr); attr.bp_addr (u64)watchp-cpus_mask; attr.bp_len HW_BREAKPOINT_LEN_4; attr.bp_type HW_BREAKPOINT_W;//HW_BREAKPOINT_W; sample_hbp register_wide_hw_breakpoint(attr, sample_hbp_handler, NULL); //sample_hbp register_user_hw_breakpoint(attr, sample_hbp_handler, NULL, task); if (IS_ERR((void __force *)sample_hbp)) { printk(register_wide_hw_breakpoint error\n); ret PTR_ERR((void __force *)sample_hbp); goto label_free; } return 0; label_free: printk(KERN_INFO Breakpoint registration failed\n); return ret; } static void __exit test_offline_mask_exit(void) { unregister_wide_hw_breakpoint(sample_hbp); printk(KERN_INFO HW Breakpoint uninstalled\n); } module_init(test_offline_mask_init); module_exit(test_offline_mask_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(zhaoxin); MODULE_DESCRIPTION(testofflinemask);2.2 实验步骤及结果实验步骤还是比较简单的先启动一个任务设置绑核属性比如0-3然后执行echo 0 /sys/devices/system/cpu/cpu1/online把cpu1给下线抓取内核日志可以看到如下的调用栈如上图可以看到1任务一开始的绑核属性是0-3cpu offline后变成了0,2-32设置任务的cpus_mask的进程上下文是kworker说明是queue_work的方式来设置3work到最终调用到设置任务的cpus_mask的调用栈cpuset_hotplug_workfnupdate_tasks_cpumaskset_cpus_allowed_ptr__set_cpus_allowed_ptr__set_cpus_allowed_ptr_locked__do_set_cpus_allowedp-sched_class-set_cpus_allowed(p, ctx);.set_cpus_allowed set_cpus_allowed_common各个调度类共用在实验时发现对于cpu从offline重新变成online的cpus_mask的变化并不能抓到这是因为数据断点是借助cpu的能力而cpu已经下线了重新上线无法恢复这个数据断点的功能。而我们堆栈已经抓到可以捕获调用set_cpus_allowed_common的地方使用cfs的任务来做实验用kprobe来抓set_cpus_allowed_common的调用栈即可有关这个实验在下面第三章里涉及。2.3 原理分析2.3.1 任务的绑核属性来自于cpus_mask如下代码查看一个任务的绑核属性cat /proc/pid/status | grep -i cpu_allowed_list如下图搜索Cpus_allowd_list是在哪里进行的显示如上图是在fs/proc/array.c里进行的显示使用的是task_struct里的cpus_mask转换输出绑核属性内容。cpus_mask是cpumask_t的结构体这是一个DECLARE_BITMAP的bit数组这个bit数组由NR_CPUS的数值决定对于的系统其大小是1024字节要注意这个bitmap的size如果NR_CPUS设置得不合理会额外占用不少空间2.3.2 cpu offline后设置cpus_mask的调用栈的相关细节调用栈上面也已经给出了我们再把一些static的函数通过反汇编分析来补上cpuset_hotplug_workfncpuset_hotplug_update_taskshotplug_update_tasks_legacy/hotplug_update_tasks前者v1后者v2update_tasks_cpumask(cs, new_cpus);set_cpus_allowed_ptr(task, new_cpus);return __set_cpus_allowed_ptr(p, ac);return __set_cpus_allowed_ptr_locked(p, ctx, rq, rf);__do_set_cpus_allowed(p, ctx);p-sched_class-set_cpus_allowed(p, ctx);.set_cpus_allowed set_cpus_allowed_commonset_cpus_allowed_common设置绑核属性的公共函数各个调度类共用上面调用链里update_tasks_cpumask是一个核心环节update_tasks_cpumask的实现如下图根据输入的cs参数作为遍历的其实cgroup层级根节点依次遍历task如上图先通过css_task_iter_start从cs-css里获取到第一个遍历的起始节点再通过css_task_iter_next进行遍历每一个任务但是要提出top_cpuset里的per-cpu的内核线程css_task_iter_next的实现如下如上图遍历的时候先put上一个task再get下一个taskput减少task的引用计数get增加task的引用计数。执行set_cpus_allowed_ptr传入的第二个参数new_cpus这里new_cpus有进行cpumask的运算effective_cpus如下定义这个cpumask_var_t的参数是一个指针内存分配在栈上在cpuset_hotplug_update_tasks函数里最后我们来看一下set_cpus_allowed_common函数这是cfs调度类的设置绑核属性的函数上图里的红色框出的逻辑就是将传入的ctx里的new_mask拷贝到任务里的cpus_mask里去。上图里的红色框出的上面的和下面的逻辑也是很关键的逻辑会在之后的博客里介绍。三、cpu offline到重新online时的cpus_mask的变化的实验及原理上一章我们在用databreakpoint去检测task_struct的cpus_mask的变量的变化时并没有检测到cpu offline到重新online时的cpus_mask的变化但是这是databreakpoint在cpu重新online需要做适配逻辑的问题导致事实上它会恢复到之前cpu online时设置的情况。但是还是有一些特殊的情况我们在下面 3.1 里先介绍这个现象然后在 3.2 里通过kprobe来通过函数来检测这个变化发生时的调用栈在 3.3 里进行相关细节分析。3.1 现象描述如果任务的绑核列表里在cpu offline后仍然有可选用的核那么在cpu重新online后绑核列表会恢复如果任务的绑核列表里在cpu offline后没有可选用的核那么在cpu重新online后绑核列表不会恢复3.2 实验ko及结果3.2.1 ko源码我们先通过kprobe来抓一下cpu重新online后是谁调用了set_cpus_allowed_common另外我们也打印一下task_struct里有关绑核属性的几个变量的数值。ko源码如下#include linux/module.h #include linux/capability.h #include linux/sched.h #include linux/uaccess.h #include linux/proc_fs.h #include linux/ctype.h #include linux/seq_file.h #include linux/poll.h #include linux/types.h #include linux/ioctl.h #include linux/errno.h #include linux/stddef.h #include linux/lockdep.h #include linux/kthread.h #include linux/sched.h #include linux/delay.h #include linux/wait.h #include linux/init.h #include asm/atomic.h #include trace/events/workqueue.h #include linux/sched/clock.h #include linux/string.h #include linux/mm.h #include linux/interrupt.h #include linux/tracepoint.h #include trace/events/osmonitor.h #include trace/events/sched.h #include trace/events/irq.h #include trace/events/kmem.h #include linux/ptrace.h #include linux/uaccess.h #include asm/processor.h #include linux/sched/task_stack.h #include linux/nmi.h #include linux/version.h #include linux/sched/mm.h #include asm/irq_regs.h #include linux/kallsyms.h #include linux/kprobes.h #include linux/stop_machine.h #include linux/perf_event.h #include linux/file.h #include linux/fscache.h MODULE_LICENSE(GPL); MODULE_AUTHOR(zhaoxin); MODULE_DESCRIPTION(Module for shmem setup.); MODULE_VERSION(1.0); struct kretprobe my_kretprobe; #define STR_SIZE 128 static int watchpid 0; module_param(watchpid, int, 0); struct task_struct *watchp NULL; struct my_data { int trigger; // 0 or 1 }; struct affinity_context { const struct cpumask *new_mask; struct cpumask *user_mask; unsigned int flags; }; #define SCA_CHECK 0x01 #define SCA_MIGRATE_DISABLE 0x02 #define SCA_MIGRATE_ENABLE 0x04 #define SCA_USER 0x08 struct task_struct* get_task_struct_by_pid(int pid) { struct pid* pid_struct; struct task_struct* ptask NULL; pid_struct find_get_pid(pid); if (pid_struct) { ptask get_pid_task(pid_struct, PIDTYPE_PID); if (ptask) { put_task_struct(ptask); } put_pid(pid_struct); } return ptask; } static int kretprobe_entry_handler(struct kretprobe_instance * ri, struct pt_regs * i_p) { #ifdef __x86_64__ struct task_struct *ptask (struct task_struct *) i_p-di; struct affinity_context *ctx (struct affinity_context *) i_p-si; #elif defined(__aarch64__) struct task_struct *ptask (struct task_struct*) i_p-regs[0]; struct affinity_context *ctx (struct affinity_context*) i_p-regs[1]; #else #error #endif struct my_data *data (struct my_data *)ri-data; if (ptask ! watchp) { >3.2.2 实验结果可以从下图抓到的内核日志可以看到cpu重新online的过程调用的仍然是process_one_work所触发的cpuset_hotplug_workfn的work逻辑最终调用到set_cpus_allowed_common的函数来进行的cpu绑核属性的设置比较cpu online-offline时触发的调用栈和cpu offline-online时触发的调用栈可以看到是一样的所以有关调用栈就不需要重新在理一遍了cpuset_hotplug_workfncpuset_hotplug_update_taskshotplug_update_tasks_legacy/hotplug_update_tasks前者v1后者v2update_tasks_cpumask(cs, new_cpus);set_cpus_allowed_ptr(task, new_cpus);return __set_cpus_allowed_ptr(p, ac);return __set_cpus_allowed_ptr_locked(p, ctx, rq, rf);__do_set_cpus_allowed(p, ctx);p-sched_class-set_cpus_allowed(p, ctx);.set_cpus_allowed set_cpus_allowed_commonset_cpus_allowed_common设置绑核属性的公共函数各个调度类共用有关task_struct里的这些cpu mask相关的数值的细节介绍我们放在之后的博客里介绍。

更多文章