kthread_run创建内核线程的原理

kthread_run是一个宏,用来创建一个进程,并且将其唤醒,其定义在头文件include/linux/kthread.h中.

#define kthread_run(threadfn, data, namefmt, ...)               \
({                                       \
    struct task_struct *__k                           \
        = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
    if (!IS_ERR(__k))                           \
        wake_up_process(__k);                       \
    __k;                                   \
})

    先来看看这个宏的参数,threadfn是该线程的执行函数,data是执行函数的传入参数,namefmt是该线程的printf风格的线程名,最后的...类似printf的可变参数列表.从这个宏的实现可以看出kthread_run是通过kthread_create来创建一个进程,并返回一个task_struct,然后使用wake_up_process函数将新创建的进程唤醒.下面需要关注一下kthread_create的实现,其实它也是一个宏,同样也定义在头文件include/linux/kthread.h中.

#define kthread_create(threadfn, data, namefmt, arg...) \
    kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)

    该宏的参数同kthread_run,不解释了,其实现就是调用了函数kthread_create_on_node,下面来看该函数的实现.

struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
                       void *data, int node,
                       const char namefmt[],
                       ...)
{
    DECLARE_COMPLETION_ONSTACK(done);
    struct task_struct *task;
    struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);

    if (!create)
        return ERR_PTR(-ENOMEM);
    create->threadfn = threadfn;//线程执行函数
    create->data = data;//线程执行函数的参数
    create->node = node;//NUMA系统上会用到,这里不介绍.
    create->done = &done;//completion结构,一会儿会通过它判断该线程的创建是否完成.

    spin_lock(&kthread_create_lock);
    list_add_tail(&create->list, &kthread_create_list);
    spin_unlock(&kthread_create_lock);

    wake_up_process(kthreadd_task);//唤醒kthreadd_task内核线程.

    if (unlikely(wait_for_completion_killable(&done))) {//进入睡眠,等待线程创建的完成.
        if (xchg(&create->done, NULL))
            return ERR_PTR(-EINTR);
        wait_for_completion(&done);
    }
    task = create->result;
    if (!IS_ERR(task)) {
        static const struct sched_param param = { .sched_priority = 0 };
        va_list args;

        va_start(args, namefmt);
        vsnprintf(task->comm, sizeof(task->comm), namefmt, args);//设置线程名字
        va_end(args);
    
        sched_setscheduler_nocheck(task, SCHED_NORMAL, &param);
        set_cpus_allowed_ptr(task, cpu_all_mask);
    }
    kfree(create);
    return task;
}

    该函数首先创建一个kthread_create_info结构,该结构封装了要创建的内核进程所需要的信息,然后将该结构添加到链表kthread_create_list的末尾,接着该函数调用wake_up_process函数唤醒kthreadd_task所表示的内核线程,然后调用wait_for_completion系列的函数使当前进程进入睡眠状态,在这里我们可以猜想kthreadd_task表示的内核线程应该会从链表kthread_create_list中依次的取下每个节点根据对应的kthread_create_info结构中的线程信息来创建内核线程,然后通过completion函数唤醒当前线程,这时kthread_create_info中的result应该已经被赋值为刚创建的内核线程的task_struct结构了,kthread_create_on_node函数会检查该task_struct结构,判断内核线程创建是否成功,如果成功,会进一步设置线程task的名称,以及调度器,最后返回新创建的task_struct结构体.
    刚才猜想了kthreadd_task表示的内核线程的作用是从链表kthread_create_list上依次的取下kthread_create_info结构,然后创建相应的内核线程.现在来验证这个猜想.在内核源码中搜索kthreadd_task,会在init/main.c文件中找到为kthreadd_task赋值的地方.在函数rest_init中.

static noinline void __init_refok rest_init(void)
{
    int pid;

    rcu_scheduler_starting();
    
    kernel_thread(kernel_init, NULL, CLONE_FS);
    numa_default_policy();
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
    rcu_read_lock();
    kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
    rcu_read_unlock();
    complete(&kthreadd_done);
    
    ......
}

    rest_init算是kernel再启动函数start_kernel中执行的最后一个函数,kthreadd_task表示的内核线程就是在这里创建的,这行代码kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns)是通过pid来查找其对应的task_struct的结构体,再看pid是上面的这行代码产生的pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES),kernel_thread函数是通过do_fork来创建线程的,然后返回该线程的pid,从这里可以看出kthreadd_task表示的内核线程的执行函数是kthreadd,下面来看这个函数的实现.

int kthreadd(void *unused)
{
    struct task_struct *tsk = current;

    ......

    for (;;) {
        set_current_state(TASK_INTERRUPTIBLE);
        if (list_empty(&kthread_create_list))
            schedule();
        __set_current_state(TASK_RUNNING);

        spin_lock(&kthread_create_lock);
        while (!list_empty(&kthread_create_list)) {
            struct kthread_create_info *create;

            create = list_entry(kthread_create_list.next, struct kthread_create_info, list);
            list_del_init(&create->list);
            spin_unlock(&kthread_create_lock);

            create_kthread(create);

            spin_lock(&kthread_create_lock);
        }
        spin_unlock(&kthread_create_lock);
    }

    return 0;
}

    可以看出这个函数的主体是一个for循环,重点是其中的那个while循环,很明显是在遍历链表kthread_create_list,获取将其上面的每个节点所表示的kthread_create_info结构,然后将该结构传递给函数create_kthread,顾名思义,该函数就是来创建线程的,下面看看create_kthread的代码.

static void create_kthread(struct kthread_create_info *create)
{
    int pid;

    ......

    pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
    if (pid < 0) {//线程如果创建失败,下面的代码是错误处理,不是重点
        /* If user was SIGKILLed, I release the structure. */
        struct completion *done = xchg(&create->done, NULL);

        if (!done) {
            kfree(create);
            return;
        }
        create->result = ERR_PTR(pid);
        complete(done);
    }
}

该函数调用了kernel_thread函数创建了一个以kthread函数为线程执行函数,以kthread_create_info结构为执行函数参数的内核线程,其实这个新创建的线程就是最终需要创建的那个内核线程,那为什么执行函数是kthread,而不是kthread_create_info结构中指定的线程执行函数.看完kthread函数的实现就知道了.

static int kthread(void *_create)
{
    struct kthread_create_info *create = _create;
    int (*threadfn)(void *data) = create->threadfn;//获取线程执行函数赋值给函数指针threadfn
    void *data = create->data;//线程执行函数的参数
    struct completion *done;
    struct kthread self;
    int ret;

    ......
    
    done = xchg(&create->done, NULL);可以理解为将create->done赋值给done
    if (!done) {
        kfree(create);
        do_exit(-EINTR);
    }
    
    __set_current_state(TASK_UNINTERRUPTIBLE);
    create->result = current;//将该线程的task_struct结构赋值给create->result
    complete(done);//调用complete函数唤醒调用kthread_create_on_node的那个线程,通知它创建的线程已经创建完毕.
    schedule();//调用schedule交出处理器,给刚唤醒的调用kthread_create_on_node的线程以运行的机会,使其继续执行kthread_create_on_node函数剩下的代码.
    
    //再次调度到该线程,从这里开始执行.
    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {//判断如果该线程没有被停止掉.
        ......
        ret = threadfn(data);//我们通过kthread_create_on_node函数传递进来的线程执行函数最终是在这个位置被执行的.
    }

    do_exit(ret);
}
    可以看出,如果在kthread函数调用schedule交出处理器和再次调度到该线程之间的这段时间里,在别的地方调用了kthread_stop函数停止了该函数的话,那么线程执行函数是得不到执行的,该线程就直接退出了.
    以上就是通过kthread_run和kthread_create创建线程的整个过程.可以看出,它们并不直接创建线程,而是将要创建的线程的相关信息打包到kthread_create_info结构中,然后委托给内核线程kthreadd来做的,从函数kthreadd也可以看出,该线程的主要工作就是遍历链表kthread_create_list创建线程.
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值