2.9.6 迁移线程
每个处理器有一个迁移线程,线程名称是“migration/<cpu_id>”,属于停机调度类,可以抢占所有其他进程,其他进程不可以抢占它。迁移线程有两个作用。
(1)调度器发出迁移请求,迁移线程处理迁移请求,把进程迁移到目标处理器。
(2)执行主动负载均衡。
如图2.47所示,每个处理器有一个停机工作管理器,成员thread指向迁移线程的进程描述符,成员works是停机工作队列的头节点,每个节点是一个停机工作,数据类型是结构体cpu_stop_work。内核提供了两个添加停机工作的函数。
图2.47 停机工作管理器的数据结构
(1)stop_one_cpu用来向指定处理器添加停机工作,并且等待停机工作完成。
int stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg);
(2)stop_one_cpu_nowait用来向指定处理器添加停机工作,但是不等待停机工作完成。
bool stop_one_cpu_nowait(unsigned int cpu, cpu_stop_fn_t fn, void *arg, struct cpu_ stop_work *work_buf);
如图2.48所示,迁移线程的线程函数是smpboot_thread_fn,如果当前处理器的停机工作队列不是空的,重复执行下面的步骤。
图2.48 迁移线程的执行流程
(1)从停机工作队列中取一个工作。
(2)执行工作函数。
(3)如果发起请求的进程正在等待,那么发送处理完成的通知。
如图2.49所示,调用系统调用sched_setaffinity以设置进程的处理器亲和性时,如果进程正在执行或者被唤醒,假设进程在处理器n上,调度器就会向处理器n的迁移线程发出迁移请求:“向处理器n的停机工作队列添加一个工作,工作函数是migration_cpu_stop”,然后唤醒处理器n的迁移线程,等待迁移线程处理完迁移请求。
图2.49 设置处理器亲和性时发出迁移请求
函数migration_cpu_stop负责把进程从当前处理器迁移到目标处理器,参数的类型是结构体migration_arg,成员task是需要迁移的进程,成员dest_cpu是目标处理器,其代码如下:
kernel/sched/core.c
1 static int migration_cpu_stop(void *data)
2 {
3 struct migration_arg *arg = data;
4 struct task_struct *p = arg->task;
5 struct rq *rq = this_rq();
6 struct rq_flags rf;
7
8 local_irq_disable();
9 sched_ttwu_pending();
10
11 raw_spin_lock(&p->pi_lock);
12 rq_lock(rq, &rf);
13 if (task_rq(p) == rq) {
14 if (task_on_rq_queued(p))
15 rq = __migrate_task(rq, &rf, p, arg->dest_cpu);
16 else
17 p->wake_cpu = arg->dest_cpu;
18 }
19 rq_unlock(rq, &rf);
20 raw_spin_unlock(&p->pi_lock);
21
22 local_irq_enable();
23 return 0;
24 }
第13行代码,检查进程p是否在当前处理器上。
第14行和第15行代码,如果进程p在当前处理器的运行队列中,那么把进程p迁移到目标处理器,从当前处理器的运行队中列删除,添加到目标处理器的运行队列中。
第16行和第17行代码,如果进程p正在睡眠,那么使用进程描述符的成员wake_cpu记录目标处理器,等到唤醒进程p的时候迁移到目标处理器。
公平调度类执行处理器负载均衡失败的时候,为最忙处理器设置主动负载均衡标志,唤醒最忙处理器的迁移线程。函数active_load_balance_cpu_stop负责执行主动负载均衡,执行流程如图2.50所示,先判断运行队列是否设置了主动负载均衡标志,如果设置了,那么从当前处理器的运行队列中选择一个公平调度类的进程,清除运行队列的主动负载均衡标志,把进程迁移到目标处理器。
图2.50 主动负载均衡的执行流程