Files
LDD-LinuxDeviceDrivers/study/kernel/01-process/05-schedule/04-stop

Linux进程调度之stop调度器类与stop_machine机制

日期 内核版本 架构 作者 GitHub CSDN
2016-06-14 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度

所属调度器类为stop_sched_class的进程是系统中优先级最高的进程, 其次才是dl_shced_class和rt_sched_class

stop_sched_class用于停止CPU, 一般在SMP系统上使用, 用以实现负载平衡和CPU热插拔. 这个类有最高的调度优先级, 如果你的系统没有定义CONFIG_SMP. 你可以试着将此类移除.

stop调度器类实现了Unix的stop_machine 特性(根据UNIX 风格,也可能是等效的其他特性)准备拼接新代码。

stop_machine 是一个通信信号 : 在SMP的情况下相当于暂时停止其他的CPU的运行, 它让一个 CPU 继续运行,而让所有其他CPU空闲. 在单CPU的情况下这个东西就相当于关中断

我的理解是如果Mulit CPU共享的东西需要修改, 且无法借助OS的lock, 关中断等策略来实现这一功能, 则需要stop_machine

#1 stop调度器类stop_sched_class

stop调度器类是优先级最高的调度器类, kernel/sched/stop_task.c,

/*
 * Simple, special scheduling class for the per-CPU stop tasks:
 */
const struct sched_class stop_sched_class = {
    .next           = &dl_sched_class,

    .enqueue_task       = enqueue_task_stop,
    .dequeue_task       = dequeue_task_stop,
    .yield_task         = yield_task_stop,

    .check_preempt_curr     = check_preempt_curr_stop,

    .pick_next_task     = pick_next_task_stop,
    .put_prev_task      = put_prev_task_stop,

#ifdef CONFIG_SMP
    .select_task_rq     = select_task_rq_stop,
    .set_cpus_allowed       = set_cpus_allowed_common,
#endif

    .set_curr_task      = set_curr_task_stop,
    .task_tick          = task_tick_stop,

    .get_rr_interval    = get_rr_interval_stop,

    .prio_changed       = prio_changed_stop,
    .switched_to        = switched_to_stop,
    .update_curr        = update_curr_stop,
};

内核提供了sched_set_stop_task函数用来将某个进程stop的调度器类设置为stop_sched_class, 该函数定义在/kernel/sched/core.c, line 849

void sched_set_stop_task(int cpu, struct task_struct *stop)
{
    struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
    struct task_struct *old_stop = cpu_rq(cpu)->stop;			/*  获取到cpu上之前的stop进程  */

    if (stop)
    {
        /*
         * Make it appear like a SCHED_FIFO task, its something
         * userspace knows about and won't get confused about.
         *
         * Also, it will make PI more or less work without too
         * much confusion -- but then, stop work should not
         * rely on PI working anyway.
         */
        sched_setscheduler_nocheck(stop, SCHED_FIFO, &param);  /*  使用SCHED_FIFO策略设置stop进程的调度信息  */

        stop->sched_class = &stop_sched_class;		/*  设置stop进程的调度器类为stop_sched_class  */
    }

    cpu_rq(cpu)->stop = stop;	/*  设置cpu的运行队列的stop进程为设置好的struct task_struct *stop */

    if (old_stop)						   /*  如果cpu的运行队列上之前有stop进程  */
    {
        /*
         * Reset it back to a normal scheduling class so that
         * it can die in pieces.
         */
        old_stop->sched_class = &rt_sched_class;	/*  恢复cpu运行队列上之前的stop进程的调度器类为rt_sched_class  */
    }
}

sched_set_stop_task把stop进程绑定为编号为cpu的处理器上的stop进程, 进程的调度策略设置为SCHED_FIFO, 但是所属的进程的调度器类设置为stop_sched_class, 这样当恢复进程的调度类时, 只需要将进程的调度器类设置为rt_sched_class即可

#2 stop_machine机制

内核中很少有地方使用了stop_sched_class, 因为这个调度器类并不像dl_shced_class, rt_sched_class和fair_sched_class一样直接调度进程

相反它用于完成stop_machine机制, 有关stop_machine机制的实现都在include/linux/stop_machine.h, line 120kernel/stop_machine.c?v=4.7, line 482

##2.1 cpu_stop_work

struct cpu_stop_work是用以完成stop_machine工作的任务实体信息, 他在SMP和非SMP结构下有不同的定义, 参见include/linux/stop_machine.h?v=4.7, line 23

#ifdef CONFIG_SMP

#ifdef CONFIG_SMP

struct cpu_stop_work {
        struct list_head        list;           	    /* cpu_stopper->works */
        cpu_stop_fn_t           fn;			  	   /*  stop进程的工作函数  */
        void                    *arg;			       /*  stop进程工作函数的参数信息  */
        struct cpu_stop_done    *done;	 			 /*  额外女巫的完成情况, 包括返回值等信息  */
};


#else   /* CONFIG_SMP */

#include <linux/workqueue.h>

struct cpu_stop_work {
        struct work_struct      work;
        cpu_stop_fn_t           fn;
        void                    *arg;
};

SMP系统中, migration用来执行任务迁移的一组进程, 其comm字段为migration/%u, 后面标识绑定的CPU编号

static struct smp_hotplug_thread cpu_stop_threads = {
	.store                  = &cpu_stopper.thread,
	.thread_should_run      = cpu_stop_should_run,
    .thread_fn              = cpu_stopper_thread,
	.thread_comm            = "migration/%u",
	.create                 = cpu_stop_create,
	.park                   = cpu_stop_park,
	.selfparking            = true,
};

如下图所示, 我们可以显示出migration/0(当前系统中9号进程), 即第0个cpu上的任务迁移内核线程, 该线程的调度策略是SCHED_FIFO, 但是所属的调度器类为stop_sched_class.与我们之前讲解sched_set_stop_task看到的内容一致

任务迁移

##2.2 stop_one_cpu

在非SMP系统中, 使用stop_one_cpu等一组函数来停止一个CPU的工作, 其实质相当于关中断, 定义在include/linux/stop_machine.h?v=4.7

函数 描述
stop_one_cpu 停止CPU工作, 关闭中断, 并执行fn(arg)函数
stop_one_cpu_nowait_workfn 开始一个任务来完成fn(arg)的工作, 而该函数无需等待fn工作的完成
stop_one_cpu_nowait 关闭中断, 并执行fn(arg)函数, 但不等待其完成
stop_cpus 同stop_one_cpu
try_stop_cpus 同stop_cpus

下面我们列出了, stop_one_cpu函数的实现, 以供参考 定义在include/linux/stop_machine.h?v=4.7, line 49

static inline int stop_one_cpu(unsigned int cpu, cpu_stop_fn_t fn, void *arg)
{
        int ret = -ENOENT;
        preempt_disable();
        if (cpu == smp_processor_id())
                ret = fn(arg);
        preempt_enable();
        return ret;
}

在SMP系统中, 则实现了如下函数, 声明在include/linux/stop_machine.h?v=4.7, line 30, 定义在kernel/stop_machine.c?v=4.7, line 120

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);
int stop_cpus(const struct cpumask *cpumask, cpu_stop_fn_t fn, void *arg);
int try_stop_cpus(const struct cpumask *cpumask, cpu_stop_fn_t fn, void *arg);

下面是stop_one_cpu函数的smp实现

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);
        if (!cpu_stop_queue_work(cpu, &work))
                return -ENOENT;
        wait_for_completion(&done.completion);
        return done.ret;
}

##2.3 stop_machine

#if defined(CONFIG_SMP) || defined(CONFIG_HOTPLUG_CPU)

/*
声明在http://lxr.free-electrons.com/source/include/linux/stop_machine.h?v=4.7#L120
定义在http://lxr.free-electrons.com/source/kernel/stop_machine.c#L565
*/
int stop_machine(cpu_stop_fn_t fn, void *data, const struct cpumask *cpus);

int stop_machine_from_inactive_cpu(cpu_stop_fn_t fn, void *data,
                                   const struct cpumask *cpus);
#else   /* CONFIG_SMP || CONFIG_HOTPLUG_CPU */

static inline int stop_machine(cpu_stop_fn_t fn, void *data,
                                 const struct cpumask *cpus)
{
        unsigned long flags;
        int ret;
        local_irq_save(flags);
        ret = fn(data);
        local_irq_restore(flags);
        return ret;
}

static inline int stop_machine_from_inactive_cpu(cpu_stop_fn_t fn, void *data,
                                                 const struct cpumask *cpus)
{
        return stop_machine(fn, data, cpus);
}

#2.4 stop_machine机制的应用

一般来说, 内核会在如下情况下使用stop_machine技术

应用 描述
module install and remove 增加删除模块, 在不需要重启内核的情况下, 加载和删除模块
cpu hotplug CPU的热插拔, 用以执行任务迁移的工作, cpu_stop_threads, 该任务由CPU绑定的migration内核线程来完成
memory hotplug Memory的热插拔
ftrace 内核tracedebug功能, 参见kernel/trace/ftrace.c
hwlat_detector 检测系统硬件引入的latency,debug功能
Kernel Hotpatch Ksplice可以在不到一秒时间里动态地应用内核补丁, 不需要重新引导