4. kthread_create()源码分析
内核线程的创建并非一蹴而就,其背后涉及一套精密的协作机制。本章将深入剖析 kthread_create() 函数的源码执行路径,揭示 kthreadd 内核线程作为“线程保姆”的核心职责,并详细解读 wait_for_completion() 同步原语如何确保创建过程的原子性与正确性。
4.1 kthread_create() 核心路径解析
kthread_create() 是一个宏定义,最终会调用 __kthread_create_on_node() 函数。其核心思想是:创建一个特殊的任务(task),该任务会立即进入睡眠状态,等待 kthreadd 线程将其唤醒并执行真正的线程函数。
4.1.1 核心数据结构:struct kthread_create_info
在创建过程中,内核使用 struct kthread_create_info 结构体来传递参数和同步状态。该结构体是 kthreadd 与创建者线程之间的通信契约。
struct kthread_create_info {
/* 线程函数的参数 */
int (*threadfn)(void *data);
void *data;
/* 存放创建好的 task_struct 指针 */
struct task_struct *result;
/* 完成变量,用于同步 */
struct completion done;
/* 创建完成后的状态 */
int status;
};
4.1.2 创建流程步骤
- 分配与初始化: 调用
kmalloc()分配kthread_create_info结构体,并填充线程函数threadfn和参数data。 - 创建内核线程: 调用
kthreadd()或kernel_thread()创建一个新的内核线程。这个新线程的入口函数是kthread(),而不是用户指定的threadfn。 - 挂起新线程: 新创建的线程在
kthread()函数中立即执行__kthread_create_on_node()中的wait_for_completion(&create->done),使自己进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE)。 - 唤醒 kthreadd: 创建者线程将
kthread_create_info结构体加入全局链表,并唤醒kthreadd内核线程。 - kthreadd 处理:
kthreadd被唤醒后,遍历链表,对每个待创建的线程调用create_kthread()。 - 完成创建:
create_kthread()调用kernel_thread()创建真正的线程,并设置其入口为kthread()。随后,kthreadd通过complete()唤醒之前挂起的线程,使其继续执行。
kthreadd 在后台完成,提高了系统的响应性。
4.2 kthreadd 内核线程的职责
kthreadd(PID=2)是所有内核线程的“守护进程”。它运行在核心态,负责监听创建请求并执行实际的线程创建工作。没有 kthreadd,内核线程的创建将无法完成。
4.2.1 kthreadd 的核心工作循环
kthreadd 的主函数是一个无限循环,其核心逻辑如下:
for (;;) {
/* 1. 将当前线程状态设置为 TASK_INTERRUPTIBLE */
set_current_state(TASK_INTERRUPTIBLE);
/* 2. 检查全局链表 kthread_create_list 是否为空 */
if (list_empty(&kthread_create_list))
schedule(); /* 如果没有待创建任务,则睡眠 */
/* 3. 被唤醒后,将状态设为 TASK_RUNNING */
__set_current_state(TASK_RUNNING);
/* 4. 遍历链表,处理所有待创建的线程 */
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);
/* 5. 调用 create_kthread() 完成实际创建 */
create_kthread(create);
}
}
4.2.2 kthreadd 的职责清单
| 职责 | 说明 | 关键函数 |
|---|---|---|
| 监听创建请求 | 持续检查 kthread_create_list 链表,等待新的创建请求。 |
schedule() |
| 执行线程创建 | 调用 kernel_thread() 创建新的内核线程,并设置其入口函数为 kthread()。 |
create_kthread() |
| 同步与通知 | 通过 complete() 唤醒等待中的创建者线程,告知创建完成。 |
complete() |
| 资源管理 | 负责释放 kthread_create_info 结构体,避免内存泄漏。 |
kfree() |
kthreadd 是一个极其重要的内核线程,如果它被意外杀死或阻塞,整个系统的内核线程创建功能将瘫痪,导致系统无法正常工作。因此,kthreadd 的代码路径必须保持高效和稳定。
4.3 wait_for_completion() 同步机制
wait_for_completion() 是 Linux 内核中一种轻量级的同步原语,用于等待一个事件的发生。在 kthread_create() 的上下文中,它被用来确保创建者线程等待 kthreadd 完成新线程的初始化工作。
4.3.1 核心数据结构:struct completion
struct completion {
unsigned int done; /* 完成状态:0 表示未完成,非0表示已完成 */
wait_queue_head_t wait; /* 等待队列,存放所有等待该完成的进程 */
};
4.3.2 工作流程
- 初始化: 通过
init_completion()或DECLARE_COMPLETION()将done字段初始化为 0。 - 等待: 调用
wait_for_completion()的进程会检查done是否为 0。如果为 0,则将自己加入等待队列,并进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE)。 - 完成: 另一个进程(通常是
kthreadd)调用complete(),将done加 1,并唤醒等待队列中的所有进程。 - 继续: 被唤醒的进程检查
done是否为非 0,如果是,则退出等待,继续执行。
4.3.3 在 kthread_create 中的具体应用
在 __kthread_create_on_node() 函数中,创建者线程会执行以下代码:
/* 创建者线程 */
struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);
init_completion(&create->done);
create->threadfn = threadfn;
create->data = data;
/* 将 create 加入全局链表,并唤醒 kthreadd */
spin_lock(&kthread_create_lock);
list_add_tail(&create->list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task);
/* 等待 kthreadd 完成创建 */
wait_for_completion(&create->done);
/* 此时 create->result 中存放了新线程的 task_struct 指针 */
return create->result;
而 kthreadd 在 create_kthread() 中完成创建后,会调用:
/* kthreadd 线程 */
complete(&create->done);
4.4 总结与对比
| 组件 | 角色 | 关键函数/机制 | 同步方式 |
|---|---|---|---|
| kthread_create() | 创建者接口 | __kthread_create_on_node() |
通过 wait_for_completion() 等待 |
| kthreadd | 线程守护进程 | create_kthread() |
通过 complete() 通知 |
| wait_for_completion() | 同步原语 | complete() / wait_for_completion() |
基于 done 计数器和等待队列 |
通过本章的分析,我们可以看到 kthread_create() 的创建过程是一个精心设计的协作流程。它利用 kthreadd 作为中央管理者,通过 wait_for_completion() 同步机制,确保了内核线程创建的原子性和可靠性。这种设计不仅提高了系统的响应性,还为后续的调度策略选型提供了稳定的基础。