目录

一、Linux的2号进程

二、kthreadd进程的创建


三、kthreadd进程执行体

四、create\_kthread函数

五、小结

          • -

一、Linux的2号进程

说起Linux进程,学习Linux系统的大部分人都知道1号进程为init进程,人们就是这样只记得第一,却很少人记得第二。(经典问题:第一个宇航员是加加林,第二呢? 世界最高峰为珠穆朗玛峰,第二、第三、第四呢?…经典翻车问题)。下面直接看看2号进程:

UID PID PPID C STIME TTY TIME CMD

root 1 0 0 11月21 ? 00:00:17 /sbin/init splash

root 2 0 0 11月21 ? 00:00:00 [kthreadd]

root 3 2 0 11月21 ? 00:00:00 [rcu\_gp]

root 4 2 0 11月21 ? 00:00:00 [rcu\_par\_gp]

root 6 2 0 11月21 ? 00:00:00 [kworker/0:0H-kb]

root 10 2 0 11月21 ? 00:00:15 [ksoftirqd/0]

root 24 2 0 11月21 ? 00:00:00 [khugepaged]

root 71 2 0 11月21 ? 00:00:00 [kblockd]

root 78 2 0 11月21 ? 00:00:00 [watchdogd]

root 138 2 0 11月21 ? 00:00:00 [kworker/u257:0]

root 151 2 0 11月21 ? 00:00:00 [charger\_manager]

root 228 2 0 11月21 ? 00:00:01 [irq/16-vmwgfx]

root 229 2 0 11月21 ? 00:00:00 [scsi\_tmf\_7]

root 230 2 0 11月21 ? 00:00:00 [ttm\_swap]

是的,kthreadd就是Linux的2号进程,这个进程在Linux内核中非常的重要,他是其他内核线程的父进程或者祖先进程(这个可以通过上面的PPID为2的进程可以看出,这些重要线程包括kworker、kblockd、khugepaged…),下面便慢慢来介绍下kthreadd进程。

二、kthreadd进程的创建

kthreadd进程是在内核初始化start\_kernel()的最后rest\_init()函数中,由0号进程(swapper进程)创建了两个进程:

  • init进程(PID = 1, PPID = 0)
  • kthreadd进程(PID = 2, PPID = 0)

内核中的其他线程PPID都是2, 说明这些线程都是由kthreadd进程创建的,因此可以说kthreadd进程负责内核线程的创建、维护等工作,是其他线程的基础。实际上也确实如此,kthreadd就是专门负责内核线程管理工作的。

static noinline void \_\_ref rest\_init(void)

{

struct task\_struct *tsk;

int pid;

… …

rcu\_scheduler\_starting();

pid = kernel\_thread(kernel\_init, NULL, CLONE\_FS);

… …

pid = kernel\_thread(kthreadd, NULL, CLONE\_FS | CLONE\_FILES);

… …

complete(&kthreadd\_done);

}

由于Linux-2.6.12版本中rest\_init函数中只创建了init进程,而kthreadd进程的创建我没有找到,因此这里引用了Linux-4.19版本中的rest\_init函数部分代码。这个版本中清楚的显示了创建init进程和kthreadd进程。

创建完毕后,Linux系统需要借助ktheadd进程实现腾飞,因此在这里等待kthreadd进程创建完毕。

三、kthreadd进程执行体

在创建线程时,是需要传递线程执行函数的,从rest\_init()中使用kernel\_thread创建线程可知kthreadd线程执行体是kthreadd()函数。

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

    /* Setup a clean context for our children to inherit. */
    set_task_comm(tsk, "kthreadd");
    ignore_signals(tsk);
    set_cpus_allowed_ptr(tsk, cpu_all_mask);/*允许kthreadd在任意cpu上执行*/
    set_mems_allowed(node_states[N_MEMORY]);

    current->flags |= PF_NOFREEZE;
    cgroup_init_kthreadd();

    for (;;) {
        set_current_state(TASK_INTERRUPTIBLE);/*将当前状态设置为可中断*/
        if (list_empty(&kthread_create_list))/*如果没有线程需要创建,则主动出让cpu*/
            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;
}

从上述代码中可以看出:kthreadd进程的任务就是等待创建线程,如果任务队列为空,则线程主动让出cpu(调用schedule后会让出cpu,本线程会睡眠):如果不为空,则依次从任务队列中取出任务,然后创建相应的线程。如此往复,直到永远…

四、create\_kthread函数

在create\_kthread函数中会通过调用kernel\_thread函数来创建新进程,且新进程的执行函数为kthread(所有经过kthreadd进程创建的进程执行体都为kthead, 看名字有点晕哈…)。

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

#ifdef CONFIG_NUMA
    current->pref_node_fork = create->node;
#endif
    /* We want our own signal handler (we take no signals by default). */
    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接口刚才在rest\_init接口中遇到过,内核就是通过kernel\_thread接口创建的init进程和kthreadd进程。这里再次使用它创建新线程,新的线程执行体统一为kthead。下面我们看看kthread函数的内容:

static int kthread(void *_create)
{
    /* Copy data: it's on kthread's stack */
    struct kthread_create_info *create = _create;
    int (*threadfn)(void *data) = create->threadfn;
    void *data = create->data;
    struct completion *done;
    struct kthread *self;
    int ret;

    self = kzalloc(sizeof(*self), GFP_KERNEL);
    set_kthread_struct(self);

    /* If user was SIGKILLed, I release the structure. */
    done = xchg(&create->done, NULL);
    if (!done) {
        kfree(create);
        do_exit(-EINTR);
    }

    if (!self) {
        create->result = ERR_PTR(-ENOMEM);
        complete(done);
        do_exit(-ENOMEM);
    }

    self->data = data;
    init_completion(&self->exited);
    init_completion(&self->parked);
    current->vfork_done = &self->exited;

    /* OK, tell user we're spawned, wait for stop or wakeup */
    __set_current_state(TASK_UNINTERRUPTIBLE);
    create->result = current;
    complete(done);
    schedule();/*睡眠,一直。直到被唤醒*/

    ret = -EINTR;
    if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {/*唤醒后如果此线程不需要stop*/
        cgroup_kthread_ready();
        __kthread_parkme(self);
        ret = threadfn(data);/*执行指定的函数体*/
    }
    do_exit(ret);
}

从kthread函数可以看出,新线程创建成功后,会一直睡眠(使用schedule主动让出CPU并睡眠),直到有人唤醒它(wake\_up\_process);线程被唤醒后,并且不需要stop, 则执行指定的函数体( threadfn(data) )。

五、小结

我使用一幅图来简单的描述下内核中kthreadd的工作流程:

上图中显示了内核创建线程的基本流程:

①某一个线程A(左上那个圈)调用kthread\_create函数来创建新线程,调用后阻塞;kthread\_create会将任务封装后添加到kthreadd监控的工作队列中;

②kthreadd进程检测到工作队列中有任务,则结束休眠状态,通过调用create\_kthread函数创建线程,最后调用到kernel\_thread --> do\_fork来创建线程,且新线程执行体为kthead

③新线程创建成功后,执行kthead,kthreadd线程则继续睡眠等待创建新进程;

④线程A调用kthread\_create返回后,在合适的时候通过wake\_up\_process(pid)来唤醒新创建的线程

⑤新创建的线程在kthead执行体中被唤醒,检测到是否需要stop,在不需要stop时,执行用户指定的线程执行体。(线程执行体发生了变化:先执行默认的kthead,然后才是用户指定的threadfn,当然也可能直接执行do\_exit退出线程)

标签: Linux, 进程, 线程, 内核, create, kthread, kthreadd

相关文章推荐

添加新评论,含*的栏目为必填