基于LInux-5.10

相关:Linux内核机制—smp_hotplug_thread:https://www.cnblogs.com/hellokitty2/p/17114737.html

一、相关数据结构

1. struct cpu_stop_done

struct cpu_stop_done {
atomic_t nr_todo; /* nr left to execute */
int ret; /* collected return value */
struct completion completion; /* fired if nr_todo reaches 0 */
};

一个辅助结构,提供与同步调用,获取返回值等功能。成员解释:

nr_todo: 表示此次queue work需要等待几个CPU完成工作
ret: 用户queue的回调函数的执行返回值,见 cpu_stopper_thread()。
completion: 当用户queue的回调函数被执行完后,若 work->done != NULL 且 done->nr_todo - 1 ==0,则 complete这个完成量,可以唤醒以同步方式调用接口而阻塞的线程。见 cpu_stopper_thread().

2. struct cpu_stop_work

struct cpu_stop_work {
struct list_head list; /* cpu_stopper->works */
cpu_stop_fn_t fn;
void *arg;
struct cpu_stop_done *done;
};

作为一个work结构挂在 cpu_stopper->works 链表上,在migration/X这个stop调度类的内核线程从这个链表上取下work,执行fn回调。成员解释:

list: 通过它挂在 cpu_stopper->works 链表上。
fn: 用户queue的回调函数,此回调函数在stop调度类上下文中运行。
arg: 用户queue的回调函数的参数。
done: 若是使用同步的接口,用户queue完回调函数后在这个结构的 completion 上 TASK_UNINTERRUPTIBLE 休眠等待。异步接口 stop_one_cpu_async 中将其初始化为NULL.

3. struct multi_stop_data

struct multi_stop_data {
cpu_stop_fn_t fn;
void *data;
unsigned int num_threads;
const struct cpumask *active_cpus; enum multi_stop_state state;
atomic_t thread_ack;
};

同时stop 2个CPU,调用两个CPU上的 migration/X 内核线程时使用。

4. struct cpu_stopper

struct cpu_stopper {
struct task_struct *thread; //指向per-cpu的"migration/X"内核线程 raw_spinlock_t lock;
bool enabled; /* is this stopper enabled? */
struct list_head works; /* list of pending works */ struct cpu_stop_work stop_work; /* for stop_cpus */
};

参数解释:

thread: 指向本CPU上的"migration/X"内核线程。
enabled: 内核启动后enable,down cpu后设置为false。
works: 用户调用接口queue过来的work挂在这个链表上。
stop_work: 主要用于在CPU hotplug down CPU 时使用,调用路径如下:

            stop_machine_from_inactive_cpu //没有调用过
set_pelt_halflife //pelt.c 设置多少个周期衰减为0.5,一般不设置
stop_machine
takedown_cpu //cpu.c stop_machine_cpuslocked(take_cpu_down, NULL, cpumask_of(cpu)); CPU hotplug down CPU 时调用
stop_machine_cpuslocked //传参cpumask=cpu_online_mask,down所有online的cpu.
stop_cpus
__stop_cpus
queue_stop_cpus_work
work = &per_cpu(cpu_stopper.stop_work, cpu);
work->fn = fn
cpu_stop_queue_work(cpu, work)

二、migration线程的创建

1. 注册后会为每个CPU都创建一个per-cpu的 migration/X 内核线程,执行函数体为。在注册过程中(此时还是内核启动阶段,非进程上下文)会调用 create 回调将自己设置为stop调度类。

static struct smp_hotplug_thread cpu_stop_threads = {
.store = &cpu_stopper.thread, //per-cpu的 "migration/X" 线程存放在 cpu_stopper.thread 中
.thread_should_run = cpu_stop_should_run, //判断 thread_fn 回调是否应该运行
.thread_fn = cpu_stopper_thread,
.thread_comm = "migration/%u",
.create = cpu_stop_create, //会设置为stop调度类
.park = cpu_stop_park, //提供了park回调但并没有提供unpark回调,park也只是一个WARN_ON(),没有实际动作。
.selfparking = true, //创建后就是没有parked的状态
}; static DEFINE_PER_CPU(struct cpu_stopper, cpu_stopper); early_initcall(cpu_stop_init);
cpu_stop_init
smpboot_register_percpu_thread(&cpu_stop_threads)

2. 如何被设置为stop调度类的

cpu_stop_threads.create //stop_machine.c "migration/%u" 通过此方法设置为stop调度类
cpu_stop_create
sched_set_stop_task
p->sched_class = &stop_sched_class;

3. 线程主要逻辑

可以理解为主要逻辑是在 smpboot_thread_fn() 中循环调用 thread_should_run 回调判断 thread_fn 回调是否应该运行,若是应该运行则调用 thread_fn() 回调。thread_fn() 回调里面会去遍历 cpu_stopper->works 链表,依次处
理用户在本CPU上注册的钩子函数。

三、对外接口

migration线程对外接口主要定义在 stop_machine.h 中,有如下接口,下面来看看每个接口的执行逻辑:

int stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg);
int stop_two_cpus(unsigned int cpu1, unsigned int cpu2, cpu_stop_fn_t fn, void *arg);
bool stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, struct cpu_stop_work *work_buf);
void stop_machine_park(int cpu);
void stop_machine_unpark(int cpu);
void stop_machine_yield(const struct cpumask *cpumask);
int stop_one_cpu_async(unsigned int cpu, cpu_stop_fn_t fn, void *arg, struct cpu_stop_work *work_buf, struct cpu_stop_done *done);
void cpu_stop_work_wait(struct cpu_stop_work *work_buf);

1. stop_one_cpu

(1) 函数实现

在queue work后会唤醒指定cpu上的 migration/X 线程的执行。这是一个同步调用,会等待queue的 work->func() 在migration/X线程中执行完成。

int stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg)
{
struct cpu_stop_done done;
struct cpu_stop_work work = { .fn = fn, .arg = arg, .done = &done }; cpu_stop_init_done(&done, 1); //赋值 done->nr_todo = 1
if (!cpu_stop_queue_work(cpu, &work)) //调用这个接口去queue work,返回stopper是否enabled
return -ENOENT; cond_resched(); //可抢占配置下是空函数
/* 休眠等待操作完成 */
wait_for_completion(&done.completion);
return done.ret;
} static bool cpu_stop_queue_work(unsigned int cpu, struct cpu_stop_work *work)
{
struct cpu_stopper *stopper = &per_cpu(cpu_stopper, cpu);
DEFINE_WAKE_Q(wakeq);
unsigned long flags;
bool enabled; preempt_disable();
raw_spin_lock_irqsave(&stopper->lock, flags);
/* 有个开关决定是否能queue work */
enabled = stopper->enabled;
if (enabled)
/* 将work挂在 stopper->works 链表上,然后将cpu的"migration/X"线程挂在wakeq上 */
__cpu_stop_queue_work(stopper, work, &wakeq);
else if (work->done)
/* 若 done->nr_todo-1!=0 则complete(&done->completion) */
cpu_stop_signal_done(work->done);
raw_spin_unlock_irqrestore(&stopper->lock, flags); /* 唤醒"migration/X"线程线程 */
wake_up_q(&wakeq);
preempt_enable(); return enabled;
}

(2) 主要调用路径

    set_cpus_allowed_ptr // core.c 【1】绑核
sched_setaffinity //core.c 【2】设置CPU亲和性
__set_cpus_allowed_ptr //core.c
force_compatible_cpus_allowed_ptr //core.c 没有调用位置
restrict_cpus_allowed_ptr //core.c
__set_cpus_allowed_ptr_locked //core.c p正在运行或正在被唤醒过程中则调用
stop_one_cpu(cpu_of(rq), migration_cpu_stop, &arg); do_execveat_common 【2】exec 方式创建一个进程时调用
kernel_execve
bprm_execve //exec.c
sched_exec //core.c 若dest cpu不是当前cpu且是active就调用,自己迁移自己。
stop_one_cpu(task_cpu(p), migration_cpu_stop, &arg); migrate_task_to //配置CONFIG_NUMA_BALANCING才生效(默认不生效)
stop_one_cpu(curr_cpu, migration_cpu_stop, &arg);

可以看到,设置任务CPU亲和性进行绑核的时候,若此时正在运行或正在被唤醒过程中则进行调用进行主动迁移。或当exec执行一个新任务时会主动迁移自己。这是同步迁移,当函数返回后迁移已经完成了。

2. stop_two_cpus

(1) 函数实现

int stop_two_cpus(unsigned int cpu1, unsigned int cpu2, cpu_stop_fn_t fn, void *arg)
{
struct cpu_stop_done done;
struct cpu_stop_work work1, work2;
struct multi_stop_data msdata; msdata = (struct multi_stop_data){
.fn = fn,
.data = arg,
.num_threads = 2,
.active_cpus = cpumask_of(cpu1),
}; /* 初始化两个work */
work1 = work2 = (struct cpu_stop_work){
.fn = multi_cpu_stop,
.arg = &msdata,
.done = &done
}; cpu_stop_init_done(&done, 2);
set_state(&msdata, MULTI_STOP_PREPARE); /* 保证cpu1的数值比cpu2大,可能为了防死锁 */
if (cpu1 > cpu2)
swap(cpu1, cpu2);
/* 向两个cpu上queue work,然后唤醒两个cpu上的migration/X线程执行 */
if (cpu_stop_queue_two_works(cpu1, &work1, cpu2, &work2))
return -ENOENT; wait_for_completion(&done.completion); return done.ret;
}

(2) 主要调用路径

check_for_migration //eas_plus.c
task_check_for_rotation //rotate.c
task_rotate_work_func //rotate.c
task_numa_migrate //fair.c 若使能 CONFIG_NUMA_BALANCING 才存在,默认不存在
migrate_swap
stop_two_cpus(arg.dst_cpu, arg.src_cpu, migrate_swap_stop, &arg)

可以看到,在rotate机制中,要在大核和小核之间交换两个 misfit 任务,使每个 misfit 任务的运行时间均等分布,以便可以用于多核并行线程以减少执行时间时会用到。

3. stop_one_cpu_nowait

(1) 函数实现

此函数只是queue一个work,但是当前进程不必等待任务执行完成,相当于异步迁移,调用函数执行完时迁移可能并没有完成。此时调用者需要保证 @work_buf 当前未被使用,并且在stopper开始执行 @fn 之前保持不变。

bool stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, struct cpu_stop_work *work_buf)
{
*work_buf = (struct cpu_stop_work){ .fn = fn, .arg = arg, };
/* 仅仅是queue一个work,并不等待work执行完成 */
return cpu_stop_queue_work(cpu, work_buf);
}

(2) 主要调用路径

newidle_balance //fair.c 【1】 new idle balnace调用路径
trace_android_rvh_sched_newidle_balance
mtk_sched_newidle_balance //mtk/fair.c
scheduler_tick //core.c 【2】 tick中的主动迁移
trace_android_vh_scheduler_tick
check_for_migration //eas_plus.c
migrate_running_task //mtk/fair.c 判断是主动迁移则调用
stop_one_cpu_nowait(cpu_of(target), mtk_active_load_balance_cpu_stop, p, &target->active_balance_work); newidle_balance //fair.c 【7】一个CPU新进入idle调用
nohz_newidle_balance
run_rebalance_domains 【8】如下,SCHED_SOFTIRQ 的软中断回调函数
nohz_idle_balance //fair.c 为所有tick停止的cpu调用
_nohz_idle_balance //fair.c 为所有idle cpu调用,传参 (rq, CPU_IDLE)
scheduler_tick 【6】 tick中触发
trigger_load_balance //fair.c
scheduler_tick //core.c 【5】tick中触发
trigger_load_balance //fair.c
nohz_balancer_kick //fair.c
newidle_balance //fair.c 【4】一个CPU新进入idle调用
nohz_newidle_balance //fair.c
kick_ilb //fair.c kick一个cpu做nohz balance
smp_call_function_single_async
nohz_csd_func //core.c 作为 rq->nohz_csd 的回调函数
run_rebalance_domains //fair.c SCHED_SOFTIRQ 的软中断回调函数,传参 (rq, CPU_IDLE/CPU_NOT_IDLE)
rebalance_domains //fair.c
newidle_balance //fair.c 【3】一个CPU新进入idle调用
load_balance //fair.c
stop_one_cpu_nowait(cpu_of(busiest), active_load_balance_cpu_stop, busiest, &busiest->active_balance_work);

看来 active_load_balance_cpu_stop() 是CFS调度中主动迁移的执行函数。

4. stop_one_cpu_async

(1) 函数实现

int stop_one_cpu_async(unsigned int cpu, cpu_stop_fn_t fn, void *arg,
struct cpu_stop_work *work_buf, struct cpu_stop_done *done)
{
cpu_stop_init_done(done, 1); work_buf->done = done;
work_buf->fn = fn;
work_buf->arg = arg; if (cpu_stop_queue_work(cpu, work_buf))
return 0; /* 区别在这里,将done赋值为NULL */
work_buf->done = NULL; return -ENOENT;
}

(2) 调用路径

do_core_ctl //core_ctl.c 【1】内核core ctl接口
try_to_pause //core_ctl.c
eas_ioctl_impl //perf_ioctl.c 【2】对上层的接口
core_ctl_force_pause_cpu //core_ctl.c
sched_pause_cpu //core_pause.c
pause_cpus //cpu.c
__pause_drain_rq //cpu.c 对参数cpus中的每个cpu都调用
sched_cpu_drain_rq //core.c
stop_one_cpu_async(cpu, drain_rq_cpu_stop, NULL, rq_drain, rq_drain_done);
__wait_drain_rq(cpus) //cpu.c 对cpus中的每个cpu都调用。同在 pause_cpus 下调用,等待上面的 stop_one_cpu_async() 注册的回调执行完
sched_cpu_drain_rq_wait(cpu) //core.c
if (work->done)
cpu_stop_work_wait(rq_drain);

可以看到主要是在CPU isolate路径中调用,把能迁移走的所有调度类的任务迁移走,把不能迁移走的(只绑定这个单个CPU的内核线程)deactive掉。实测绑定单核的非内核线程,无论isolate还是offline都会将其cpu mask复位为所有CPU,
并迁移到其它CPU上继续运行。

5. cpu_stop_work_wait

等待由 stop_one_cpu_async() 发起的回调。

(1) 函数实现

void cpu_stop_work_wait(struct cpu_stop_work *work_buf)
{
struct cpu_stop_done *done = work_buf->done; wait_for_completion(&done->completion);
work_buf->done = NULL;
}

(2) 调用路径

见上面 stop_one_cpu_async() 的调用路径。

6. stop_machine_park

(1) 函数实现

void stop_machine_park(int cpu)
{
struct cpu_stopper *stopper = &per_cpu(cpu_stopper, cpu);
/*
* 无锁执行。 cpu_stopper_thread() 中将持有 stopper->lock
* 并在停放它之前flush pending的works,直到可以将排队新works。
*/
stopper->enabled = false;
/* kthread->flags |= KTHREAD_SHOULD_PARK, 然后在loop
* 里将自己设置为 TASK_PARKED 然后切走。
* 若thread不是当前正在执行的任务,则会先唤醒,然后等待其进
* 入TASK_PARKED状态。
*/
kthread_park(stopper->thread);
}

(2) 调用路径

cpuhp_hp_states[CPUHP_TEARDOWN_CPU].teardown.single 回调
takedown_cpu //cpu.c 也将下面函数作为回调在migration/X内核线程上下文执行
take_cpu_down //cpu.c 先回调一些hotplug回调,这些回调是在migration/X内核线程上下文执行的,函数最后才park
stop_machine_park(cpu);

可以看到在CPU hotplug down CPU 的时候,会将很多hotplug状态对应的回调放到migration/X内核线程上下文中去执行,执行完后就把migration/X内核线程给park了。

7. stop_machine_unpark

(1) 函数实现

void stop_machine_unpark(int cpu)
{
struct cpu_stopper *stopper = &per_cpu(cpu_stopper, cpu); stopper->enabled = true;
/*
* 将此线程重新bind到cpu上,然后 从 kthread->flags 中清除
* KTHREAD_SHOULD_PARK 标志,然后唤醒处于 TASK_PARKED 状态
* 的 migration/X 线程。
* 唤醒后便会去 cpu_stopper.works 链表上取任务执行了。
*/
kthread_unpark(stopper->thread);
}

(2) 调用路径

rest_init //main.c
cpu_startup_entry //idle.c
cpuhp_online_idle //cpu.c 在开始进入idle loop之前对stopper thread进行unpark
stop_machine_unpark(smp_processor_id());

(3) 在 offline/online 一个CPU的过程中会调用stop_machine的 park 和 unpark 回调(isolate cpu不会调用),实验:

//执行:
/sys/devices/system/cpu # echo 0 > ./cpu5/online
/sys/devices/system/cpu # echo 1 > ./cpu5/online //对应栈回溯
[ 4682.695877] Hello: stop_machine_park: cpu=5
[ 4682.695930] Call trace:
[ 4682.695970] dump_backtrace.cfi_jt+0x0/0x8
[ 4682.695993] dump_stack_lvl+0xc4/0x140
[ 4682.696016] take_cpu_down+0x150/0x16c
[ 4682.696040] multi_cpu_stop+0x11c/0x1f8
[ 4682.696055] cpu_stopper_thread+0x138/0x41c
[ 4682.696074] smpboot_thread_fn+0x180/0x594
[ 4682.696091] kthread+0x150/0x200
[ 4682.696107] ret_from_fork+0x10/0x30 [ 4684.881993] Hello: stop_machine_unpark: cpu=5
[ 4684.882038] Call trace:
[ 4684.882074] dump_backtrace.cfi_jt+0x0/0x8
[ 4684.882097] dump_stack_lvl+0xc4/0x140
[ 4684.882123] cpu_startup_entry+0x90/0xb4
[ 4684.882144] secondary_start_kernel+0x204/0x27c

四、总结

stop_machine.c 中实现的per-cpu的 migration/X 内核线程是stop调度类的,线程函数体是 smpboot_thread_fn(),其被唤醒后主要是调用 thread_should_run 回调判断是否需要调用 thread_fn 回调,若是需要则调用。

migration/X 内核线程对外是以queue work的方式向用户提供接口的,用户可以通过向其queue work的方式使work func回调运行在stop调度类上下文中。在任务迁移(绑核、负载均衡)、cpu hotplug等相关机制中使用较多。

负载均衡中的任务迁移注册的回调函数是 active_load_balance_cpu_stop().

当一个CPU被isolate或offline时,会将其工作队列上的任务移到其它CPU上,对于只绑定单个CPU的内核线程,会对其deactive而不会迁移到其它CPU,对于只绑定单个CPU的用户线程,会将其cpumask reset到所有CPU上并迁移走。

参考:
How migration thread works inside of Linux Kernel: https://www.systutorials.com/migration-thread-works-inside-linux-kernel/

调度器43—migration 内核线程的更多相关文章

  1. Golang调度器GMP原理与调度全分析(转 侵 删)

    该文章主要详细具体的介绍Goroutine调度器过程及原理,包括如下几个章节. 第一章 Golang调度器的由来 第二章 Goroutine调度器的GMP模型及设计思想 第三章 Goroutine调度 ...

  2. golang中GPM模型原理与调度器设计策略

    一.GMP模型原理first: 1. 全局队列:存放待运行的G2. P的本地队列:同全局队列类似,存放待运行的G,存储的数量有限:256个,当创建新的G'时,G'优先加入到P的本地队列,如果队列已满, ...

  3. linux常见进程与内核线程

    发现大量jdb2进程占用io资源.jdb2进程是一个文件系统的写journal的进程 kthreadd:这种内核线程只有一个,它的作用是管理调度其它的内核线程.它在内核初始化的时候被创建,会循环运行一 ...

  4. 常见linux内核线程说明

    ps进程名有方括号的是内核级的进程,执行辅助功能(比如将缓存写入到磁盘):所有其他进程都是使用者进程.您会注意到,就算是在您新安装的(最小化的)系统中,也会有很多进程在运行. 在文档kernel-pe ...

  5. 转: 调整 Linux I/O 调度器优化系统性能

    转自:https://www.ibm.com/developerworks/cn/linux/l-lo-io-scheduler-optimize-performance/index.html 调整 ...

  6. CFS调度器

    一.前言 随着内核版本的演进,其源代码的膨胀速度也在递增,这让Linux的学习曲线变得越来越陡峭了.这对初识内核的同学而言当然不是什么好事情,满腔热情很容易被当头浇灭.我有一个循序渐进的方法,那就是先 ...

  7. go调度: 第二部分-go调度器

    前言 这个博客是三部分中提供go调度器的语义和机制的部分. 博客三部分的顺序: 1) go调度: 第一部分-操作系统调度 2) go调度: 第二部分-go调度器 3) go调度: 第三部分-并发 介绍 ...

  8. [翻译] 深入浅出Go语言调度器:第一部分 - 系统调度器

    目录 译者序 序 介绍 系统调度器 执行指令 Figure 1 Listing 1 Listing 2 Listing 3 线程状态 任务侧重 上下文切换 少即是多 寻找平衡 缓存行 Figure 2 ...

  9. 【freertos】005-启动调度器分析

    前言 本节主要讲解启动调度器. 这些都是与硬件相关,所以会分两条线走:posix和cortex m3. 原文:李柱明博客:https://www.cnblogs.com/lizhuming/p/160 ...

  10. RxSwift 中的调度器

    与 ReactiveCocoa 相比,Rx 的一大优势就是更丰富的并发模型.提到并发,就不得不提多线程.在 RxSwift 中,与线程对应的概念就是调度器,本文就调度器做些介绍,包括并发调度器.串行调 ...

随机推荐

  1. 学习Java Day17

    今天继续加强了一下类的联系,并学习了如何生成随机数

  2. RestTemplate的调用方式、服务消费者

    二:RestTemplate 通过RestTemplate可以实现不同微服务之间的调用 RestTemplate是spring框架提供的一种基于RESTful的服务组件,底层对HTTP请求及其相应进行 ...

  3. Autoit 制作上传工具完美版

    一. 制作上传器 在ui自动化过程中经常遇到需要上传的动作,我们可以使用input标签来送值,但这样不太稳定,所以建议使用autoit制作出来的exe工具. 下面就教大家如何制作上传器,如何使用吧! ...

  4. P2617 Dynamic Rankings 解题报告

    link 整体二分是一种东西,比如上面这道题. 先考虑一个不带修版本的,也就是经典问题区间 kth,显然我们可以主席树但是我知道你很想用主席树但是你先别用不用主席树,用一种离线的算法,叫整体二分. 首 ...

  5. iview表单验证

    iview表单验证的步骤 第一步:给 Form 设置属性 rules :rules="规则设置" 第二步:同时给需要验证的每个 FormItem 设置属性 prop 指向对应字段即 ...

  6. PYTHON编写程序练习-打印99乘法表

    使用for循环嵌套的知识点编写 for i in range(1,10):   #第一层循环,循环乘数 for j in range(1,i+1):   #第二层循环,循环被乘数 print(f&qu ...

  7. spring 事务不生效

    1.方法自身(this)调用问题,导致事务失效 非事务方法seckillVoucher()中调用的自身类的事务方法createVoucherOrder(). 解决办法: ps:要加aspj依赖,同时在 ...

  8. Redis Stream类型的使用详解

    目录 一.背景 二.redis中Stream类型的特点 三.Stream的结构 四.Stream的命令 1.XADD 往Stream末尾添加消息 1.命令格式 2.举例 2.XRANGE查看Strea ...

  9. Mysql数据库基础第七章:流程控制结构

    Mysql数据库基础系列 软件下载地址 提取码:7v7u 数据下载地址 提取码:e6p9 mysql数据库基础第一章:(一)数据库基本概念 mysql数据库基础第一章:(二)mysql环境搭建 mys ...

  10. Git安装,配置、基本使用

    p.p1 { margin: 0; font: 12px ".PingFang SC" } p.p2 { margin: 0; text-align: justify; font: ...