Linux下2号进程的kthreadd--Linux进程的管理与调度(七)

本文详细解析了Linux系统中kthreadd进程的功能及其内部运作机制,包括如何管理和调度内核线程,以及创建新内核线程的具体步骤。
日期内核版本架构作者GitHub优快云
2016-05-12Linux-4.5X86 & armgatiemeLinuxDeviceDriversLinux进程管理与调度-之-进程的创建

前言


Linux下有3个特殊的进程,idle进程(PID = 0), init进程(PID = 1)和kthreadd(PID = 2)


* idle进程由系统自动创建, 运行在内核态

idle进程其pid=0,其前身是系统创建的第一个进程,也是唯一一个没有通过fork或者kernel_thread产生的进程。完成加载系统后,演变为进程调度、交换


* init进程由idle通过kernel_thread创建,在内核空间完成初始化后, 加载init程序, 并最终用户空间

由0进程创建,完成系统的初始化. 是系统中所有其它用户进程的祖先进程
Linux中的所有进程都是有init进程创建并运行的。首先Linux内核启动,然后在用户空间中启动init进程,再启动其他系统进程。在系统启动完成完成后,init将变为守护进程监视系统其他进程。


* kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

我们下面就详解分析2号进程kthreadd

2号进程


内核初始化rest_init函数中,由进程 0 (swapper 进程)创建了两个process

  • init 进程 (pid = 1, ppid = 0)

  • kthreadd (pid = 2, ppid = 0)

所有其它的内核线程的ppid 都是 2,也就是说它们都是由kthreadd thread创建的

所有的内核线程在大部分时间里都处于阻塞状态(TASK_INTERRUPTIBLE)只有在系统满足进程需要的某种资源的情况下才会运行

它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

2号进程的创建


在rest_init函数中创建2号进程的代码如下

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);

2号进程的事件循环


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;

    for (;;) {
            /*  首先将线程状态设置为 TASK_INTERRUPTIBLE, 如果当前
            没有要创建的线程则主动放弃 CPU 完成调度.此进程变为阻塞态*/
            set_current_state(TASK_INTERRUPTIBLE);
            if (list_empty(&kthread_create_list))  //  没有需要创建的内核线程
                    schedule();                                 //   什么也不做, 执行一次调度, 让出CPU

             /*  运行到此表示 kthreadd 线程被唤醒(就是我们当前)
            设置进程运行状态为 TASK_RUNNING */
            __set_current_state(TASK_RUNNING);

            spin_lock(&kthread_create_lock);                    //  加锁,
            while (!list_empty(&kthread_create_list)) {
                    struct kthread_create_info *create;

                    /*  从链表中取得 kthread_create_info 结构的地址,在上文中已经完成插入操作(将
                    kthread_create_info 结构中的 list 成员加到链表中,此时根据成员 list 的偏移
                    获得 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的核心是一for和while循环体。

在for循环中,如果发现kthread_create_list是一空链表,则调用schedule调度函数,因为此前已经将该进程的状态设置为TASK_INTERRUPTIBLE,所以schedule的调用将会使当前进程进入睡眠。

如果kthread_create_list不为空,则进入while循环,在该循环体中会遍历该kthread_create_list列表,对于该列表上的每一个entry,都会得到对应的类型为struct kthread_create_info的节点的指针create.

然后函数在kthread_create_list中删除create对应的列表entry,接下来以create指针为参数调用create_kthread(create).

create_kthread的过程如下

create_kthread完成内核线程创建


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). 
    其实就是调用首先构造一个假的上下文执行环境,最后调用 do_fork()
    返回进程 id, 创建后的线程执行 kthread 函数
    */
    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);
    }
}

在create_kthread()函数中,会调用kernel_thread来生成一个新的进程,该进程的内核函数为kthread,调用参数为

pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);

我们可以看到,创建的内核线程执行的事件kthread

此时回到 kthreadd thread,它在完成了进程的创建后继续循环,检查 kthread_create_list 链表,如果为空,则 kthreadd 内核线程昏睡过去

那么我们现在回想我们的操作
我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,于是这里有三个线程

  1. kthreadd已经光荣完成使命(接手执行真正的创建工作),睡眠

  2. 唤醒kthreadd的线程由于新创建的线程还没有创建完毕而继续睡眠 (在 kthread_create函数中)

  3. 新创建的线程已经正在运行kthread,但是由于还有其它工作没有做所以还没有最终创建完成.

新创建的内核线程kthread函数


static int kthread(void *_create)
{
    /* Copy data: it's on kthread's stack 
     create 指向 kthread_create_info 中的 kthread_create_info */
    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.flags = 0;
    self.data = data;
    init_completion(&self.exited);
    init_completion(&self.parked);
    current->vfork_done = &self.exited;

    /* If user was SIGKILLed, I release the structure. */
    done = xchg(&create->done, NULL);
    if (!done) {
            kfree(create);
            do_exit(-EINTR);
    }
    /* OK, tell user we're spawned, wait for stop or wakeup
     设置运行状态为 TASK_UNINTERRUPTIBLE  */
    __set_current_state(TASK_UNINTERRUPTIBLE);

     /*  current 表示当前新创建的 thread 的 task_struct 结构  */
    create->result = current;
    complete(done);
    /*  至此线程创建完毕 ,  执行任务切换,让出 CPU  */
    schedule();

    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
            __kthread_parkme(&self);
            ret = threadfn(data);
    }
    /* we can't just return, we must preserve "self" on stack */
    do_exit(ret);
}

线程创建完毕:

创建新 thread 的进程恢复运行 kthread_create() 并且返回新创建线程的任务描述符
新创建的线程由于执行了 schedule() 调度,此时并没有执行.

直到我们使用wake_up_process(p);唤醒新创建的线程

线程被唤醒后, 会接着执行threadfn(data)

    ret = -EINTR;

    if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
            __kthread_parkme(&self);
            ret = threadfn(data);
    }
    /* we can't just return, we must preserve "self" on stack */
    do_exit(ret);

总结


kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理,它的任务就是管理和调度其他内核线程kernel_thread, 会循环执行一个kthreadd的函数,该函数的作用就是运行kthread_create_list全局链表中维护的kthread, 当我们调用kernel_thread创建的内核线程会被加入到此链表中,因此所有的内核线程都是直接或者间接的以kthreadd为父进程

我们在内核中通过kernel_create或者其他方式创建一个内核线程, 然后kthreadd内核线程被唤醒, 来执行内核线程创建的真正工作,新的线程将执行kthread函数, 完成创建工作,创建完毕后让出CPU,因此新的内核线程不会立刻运行.需要手工 wake up, 被唤醒后将执行自己的真正工作函数

  • 任何一个内核线程入口都是 kthread()

  • 通过 kthread_create() 创建的内核线程不会立刻运行.需要手工 wake up.

  • 通过 kthread_create() 创建的内核线程有可能不会执行相应线程函数threadfn而直接退出

<think> 嗯,用户这次的问题非常精准——直接聚焦在守护进程和内核线程的本质区别上,而且提供了详实的参考文献。看来是个有一定Linux基础的嵌入式开发者,可能在实际开发中遇到了调度或资源管理的困惑。 从用户引用的内核资料来看,ta显然已经理解了内核线程的创建机制(kthreadd、0/1/2线程等),但需要更明确的操作系统层级划分概念。我需要从三个核心维度展开对比: 第一是权限层级差异。引用[4]明确指出“内核线程永远运行于内核态”,这直接决定了它们的指令执行权限级别。而守护进程虽然权限较高,但本质上还是用户态进程,系统调用时仍要经历从用户态到内核态的切换。这点在嵌入式实时系统中尤其关键,因为上下文切换会带来不可预测的延迟。 第二是内存管理机制。根据引用[5]的描述,内核线程“唯一使用的资源就是内核栈”,这意味着它们共享内核的地址空间。反观守护进程,每个都有独立的4GB用户地址空间(32位系统),虽然物理内存可能通过mmap共享,但虚拟地址空间是完全隔离的。这个区别在嵌入式设备有限的内存资源下会产生显著影响。 第三是调度可见性。内核线程的调度对用户完全透明(引用[5]),而守护进程在ps命令中可见(通常带方括的是内核线程)。不过有趣的是,像udev这样的守护进程会伪装成内核线程的命名风格,这点需要特别提醒用户注意识别。 用户可能没意识到但实际很重要的点是异常处理机制。当内核线程崩溃时(比如驱动模块错误),往往直接引发内核oops;而守护进程崩溃通常只会影响自身服务。这也是为什么大多数硬件驱动都以内核线程形式存在——它们需要直接操作物理内存和寄存器,这些操作在用户态会引发段错误。 最后补充个实用技巧:通过`ps -e -To pid,spid,cmd`可以清晰看到内核线程的PPID都是2kthreadd的子进程),而守护进程的父进程是systemd/sysvinit。这个诊断方法应该能解决用户实际调试中的困惑。 </think> 以下是守护进程(用户空间)Linux内核线程(kthread)的本质区别解析: --- ### **1. 运行层级权限** | **维度** | 守护进程 | 内核线程 (kthread) | |-------------------|-----------------------------------|---------------------------------| | **运行空间** | 用户空间(需通过系统调用访问内核)| **内核空间**(直接操作硬件)[^4] | | **权限级别** | 普通用户或root权限 | **内核特权级**(无权限隔离)[^4] | | **内存访问** | 用户态内存空间 | **直接访问内核地址空间**[^5] | > **本质区别**:内核线程运行在Ring 0特权级,无需系统调用即可操作硬件;守护进程需通过内核接口间接访问资源。 --- ### **2. 创建管理机制** | **维度** | 守护进程 | 内核线程 (kthread) | |-------------------|-----------------------------------|---------------------------------| | **创建者** | 用户进程(如`systemd`) | **kthreadd内核线程**(PID=2)[^1] | | **创建方式** | `fork()` + `setsid()` | `kthread_create()`[^2] | | **调度实体** | 进程(含虚拟内存、文件描述符等) | **轻量级线程**(仅内核栈+寄存器)[^5] | ```c // 内核线程创建核心逻辑 struct task_struct *kthread_create(work_func_t func, ...) { ... wake_up_process(task); // 立即唤醒线程[^2] } ``` --- ### **3. 功能设计约束** | **维度** | 守护进程 | 内核线程 (kthread) | |-------------------|-----------------------------------|---------------------------------| | **核心任务** | 网络服务/日志采集等应用层功能 | **内核核心功能**(如内存回收、中断处理)[^5] | | **资源开销** | 较高(独立地址空间) | **极低**(共享内核内存)[^5] | | **阻塞影响** | 仅自身阻塞 | 可能阻塞整个内核(需谨慎设计)[^4] | | **可见性** | 用户`ps`命令可见 | 需`ps -ef`或`/proc`查看(多数带`[]`标识) | > **典型内核线程**: > - `kswapd`:内存回收 [^5] > - `kworker`:异步任务处理 > - `rcu_sched`:RCU垃圾回收 --- ### **4. 生命周期对比** | **阶段** | 守护进程 | 内核线程 (kthread) | |-------------------|-----------------------------------|---------------------------------| | **启动** | 系统初始化时由用户空间启动 | 内核启动时由`kthreadd`创建[^1] | | **退出** | 显式退出或被`SIGTERM`终止 | 调用`kthread_stop()`或随内核关闭 | | **异常处理** | 崩溃后可重启 | 崩溃导致**内核panic**(严重性高)[^4] | --- ### **关键区别总结** $$\text{本质差异} = \begin{cases} \text{权限级别:内核线程 \in Ring\ 0} \\ \text{内存空间:内核线程 \subset 内核地址空间} \\ \text{创建者:内核线程 \equiv kthreadd的子任务} \end{cases}$$ > 内核线程是内核功能的延伸(如`kswapd`管理虚拟内存[^5]),而守护进程是**用户态的服务代理**(如`sshd`处理远程登录)。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值