[TOC]

kernel/kthread.c 内核线程(Kernel Threads) 内核后台任务的创建与管理

历史与背景

这项技术是为了解决什么特定问题而诞生的?

kernel/kthread.c 提供的内核线程(kthread)机制是为了解决内核自身需要执行长时间运行的、可阻塞的后台任务这一根本需求而诞生的。

在内核中,许多任务不能在任何用户进程的上下文中执行,也不能在中断上下文中完成。

  • 中断上下文的局限性:中断处理程序(包括softirq/tasklet)必须快速执行且绝对不能睡眠(阻塞)。然而,很多内核任务需要进行I/O操作、获取信号量或等待定时器,这些都会导致睡眠。
  • 用户进程上下文的不可靠性:内核的后台任务(如刷新脏页到磁盘、管理内存)必须在系统的整个生命周期中持续运行。如果将这些任务依附于某个用户进程,那么当该进程退出或被杀死时,这些关键的内核任务也将终止,这是不可接受的。

kthread就是为了提供一个纯粹的、独立的内核执行上下文而设计的。它是一个在内核空间运行、没有用户地址空间(mm_struct)的特殊“进程”。这使得内核可以创建自己的“守护进程(daemons)”,这些守护进程可以被调度、可以睡眠、可以执行任何需要在进程上下文中完成的操作,同时其生命周期完全由内核控制。

它的发展经历了哪些重要的里程碑或版本迭代?

  • 早期的kernel_thread():在kthread API出现之前,内核使用一个名为kernel_thread()的函数来创建内核线程。这个接口功能较为原始,需要手动进行许多设置。
  • 标准化的kthread_* API:一个重要的里程碑是引入了一套以kthread_为前缀的、更加健壮和易用的API。这包括:
    • kthread_create(): 只创建线程,不运行。
    • kthread_run(): 创建并立即运行线程。
    • kthread_stop(): 一个设计精良的、同步的线程停止机制,它会向线程发出停止请求,并等待其真正退出。
      这套API极大地简化了内核线程的生命周期管理,并提供了一个安全的退出范式。
  • 与Freezer的集成:为了支持系统挂起/休眠(Suspend/Hibernate),内核线程需要能够被“冻结(freeze)”。kthread框架与内核的freezer机制紧密集成,允许kthreads在系统进入休眠状态时安全地暂停。
  • Per-CPU线程的简化:为在每个CPU上都运行一个实例的常见模式提供了辅助函数,简化了这类线程的创建和管理。

目前该技术的社区活跃度和主流应用情况如何?

kthread是内核最基础、最稳定的基础设施之一。其核心API很少变动,社区的活跃度主要体现在内核的各个子系统如何使用它来构建自己的后台任务。
它是现代Linux内核中所有后台守护任务的实现基础,应用极其广泛,一些著名的例子包括:

  • kswapd:内存管理的核心守护进程,负责在内存不足时换出页面。
  • kworker:这是工作队列(workqueue)的执行实体。所有的工作队列任务最终都是在kworker这些内核线程中执行的。
  • 文件系统日志线程:如ext4的jbd2线程,负责将文件系统日志提交到磁盘。
  • CPU调度相关线程:如用于CPU间任务迁移的migration线程,以及用于负载均衡的调度器线程。

核心原理与设计

它的核心工作原理是什么?

kthread的核心原理是利用clone()系统调用的底层机制,创建一个不与用户空间关联的、只在内核态运行的新任务(task_struct)。

  1. 创建过程
    • 当驱动程序调用kthread_create()时,内核会准备一个kthread_create_info结构体,其中包含了用户提供的线程函数、参数等信息。
    • 然后,它会调用kernel_clone()(这是_do_fork的一个封装)。在调用kernel_clone()时,会传入CLONE_KERNEL等特殊标志,但不会传入CLONE_VM
    • 这意味着新创建的任务会获得一个独立的task_struct,但它不会复制父进程的用户地址空间,而是将其mmactive_mm字段指向NULL,表示它没有自己的用户空间。
  2. 启动与执行
    • 新创建的kthread并不会直接从用户提供的函数开始执行,而是从一个通用的包装函数kthread()开始。
    • kthread()函数负责进行一些初始化,如屏蔽所有信号,然后才调用用户传入的真正线程函数。
  3. 线程主循环
    • 一个设计良好的kthread,其主函数通常是一个while循环。循环的条件是!kthread_should_stop()
    • 在循环内部,线程执行其具体任务,并在任务间歇调用schedule()msleep()或在等待队列上等待,以让出CPU。
  4. 停止机制 (kthread_stop)
    • 当其他代码调用kthread_stop()时,它会首先设置kthread_should_stop()将返回true的标志。
    • 然后,它会唤醒正在睡眠的目标kthread。
    • 被唤醒的kthread在下一次循环检查时,发现kthread_should_stop()为真,于是跳出循环,执行清理工作并返回。
    • kthread_stop()函数会一直等待,直到目标kthread完全退出,这是一个同步的、确保资源被完全释放的操作。

它的主要优势体现在哪些方面?

  • 拥有进程上下文:这是kthread最核心的优势。因为它是一个完整的、可调度的任务,所以它可以睡眠(阻塞)。这使得它可以执行等待I/O、获取mutex等操作。
  • 独立的生命周期:kthread的生命周期完全由内核代码控制,独立于任何用户进程,保证了内核关键任务的持续运行。
  • 可调度性:作为标准的task_struct,kthread可以被赋予不同的调度策略和优先级,允许内核对后台任务进行精细的性能控制。
  • 安全的退出机制kthread_stop()提供了一个清晰、无竞态的范式来安全地终止线程。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 开销相对较大:与workqueuetasklet相比,创建一个完整的task_struct来代表一个kthread,其内存和调度器开销更大。因此,它不适合用于执行大量、短小的“一次性”任务。
  • 编程模型更复杂:开发者需要自己管理线程的主循环、睡眠/唤醒逻辑以及对kthread_should_stop()的检查。而workqueue模型则简单得多,只需提供一个函数即可。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

kthread是那些需要长期运行、周期性执行或需要在后台等待事件的内核任务的首选。

  • 周期性轮询:一个驱动需要每隔500毫秒检查一次硬件状态。可以创建一个kthread,其主循环中包含一个msleep(500)
  • 内核守护进程:如上文提到的kswapd,它大部分时间都在等待队列上睡眠,只有在内存压力达到阈值时才被唤醒执行工作。
  • 阻塞式I/O处理:一个内核任务需要将大量数据写入磁盘。它可以创建一个kthread来执行这个写操作,因为写磁盘是一个阻塞操作。

是否有不推荐使用该技术的场景?为什么?

  • 短小的、一次性的延迟任务:如果只是想把一个简短的任务从中断上下文推迟到进程上下文执行,应该使用**workqueue**。例如,在中断处理程序中检测到一个设备状态改变,需要执行一些不紧急的后续处理。为这种任务创建一个完整的kthread然后销毁它,效率太低。
  • 高性能、高频率的延迟任务(不睡眠):如果延迟的任务非常短小、执行频率非常高,且不需要睡眠,那么tasklet是比kthread更轻量、更高效的选择。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 内核线程 (Kthread) 工作队列 (Workqueue) 任务队列 (Tasklet) / 软中断 (Softirq)
执行上下文 进程上下文 进程上下文 中断上下文 (软中断)
能否睡眠 可以 可以 绝对不能
生命周期 长周期。通常用于创建守护进程,与系统共存。 短周期。为“一次性”的延迟任务设计。 极短周期。为中断的下半部设计。
创建开销 。需要创建完整的task_struct 。只需创建一个work_struct并将其排入共享的kworker线程。 极低/静态
编程模型 复杂。开发者需手动管理线程主循环和退出逻辑。 简单。只需提供一个函数,然后调用queue_work() 简单。只需提供一个函数,然后调用tasklet_schedule()
主要用途 内核守护进程、周期性任务、需要独立调度实体的后台工作。 将工作从中断上下文或其他不允许睡眠的地方推迟执行。这是最常用的延迟执行机制。 中断处理的下半部,用于高性能、不睡眠的延迟任务。

include/linux/kthread.h

kthread_init_work 初始化工作线程 将工作线程的结构体初始化为零,并设置函数指针

1
2
3
4
5
6
#define kthread_init_work(work, fn)					\
do { \
memset((work), 0, sizeof(struct kthread_work)); \
INIT_LIST_HEAD(&(work)->node); \
(work)->func = (fn); \
} while (0)

kthread_init_worker 初始化工作线程

1
2
3
4
5
6
#define kthread_init_worker(worker)					\
do { \
static struct lock_class_key __key; \
__kthread_init_worker((worker), "("#worker")->lock", &__key); \
} while (0)

kthread_create_worker 创建工作线程

1
2
3
4
5
6
7
__printf(3, 4)
struct kthread_worker *kthread_create_worker_on_node(unsigned int flags,
int node,
const char namefmt[], ...);

#define kthread_create_worker(flags, namefmt, ...) \
kthread_create_worker_on_node(flags, NUMA_NO_NODE, namefmt, ## __VA_ARGS__);

kthread_run_worker 创建并唤醒 kthread worker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* kthread_run_worker - 创建并唤醒 kthread worker。
* @flags:修改 worker 默认行为的标志
* @namefmt:线程的 printf 样式名称。
*
* 描述:kthread_create_worker() 后跟
* wake_up_process() 的 返回 kthread_worker 或 ERR_PTR (-ENOMEM)。
*/
#define kthread_run_worker(flags, namefmt, ...) \
({ \
struct kthread_worker *__kw \
= kthread_create_worker(flags, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__kw)) \
wake_up_process(__kw->task); \
__kw; \
})

kernel/kthread.c

to_kthread 检查内核线程,返回kthread结构体指针

  • 检查传入的任务是否是一个内核线程(kernel thread),并返回与该任务关联的 kthread 结构体指针
1
2
3
4
5
static inline struct kthread *to_kthread(struct task_struct *k)
{
WARN_ON(!(k->flags & PF_KTHREAD));
return k->worker_private;
}

set_kthread_struct 设置线程结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
bool set_kthread_struct(struct task_struct *p)
{
struct kthread *kthread;

if (WARN_ON_ONCE(to_kthread(p))) //检查任务结构体 p 是否已经关联了一个 kthread
return false;

kthread = kzalloc(sizeof(*kthread), GFP_KERNEL); //分配内存,并初始化为零
if (!kthread)
return false;
/* parked:通常用于表示线程是否已进入“停放”状态(parked)。
在某些场景下,线程可能需要暂时停止执行,等待某个条件满足后再继续运行。 */
init_completion(&kthread->exited);
/* exited:用于表示线程是否已退出(exited)。
当一个线程完成其任务并退出时,可以通过触发 exited 来通知其他线程,确保资源的正确释放或后续操作的执行。 */
init_completion(&kthread->parked);
INIT_LIST_HEAD(&kthread->hotplug_node);
/* vfork_done 是 task_struct 中的一个字段,通常用于同步 vfork 系统调用的完成状态。
在内核线程的上下文中,这里将其重用为一个同步点,表示线程的退出状态。 */
p->vfork_done = &kthread->exited;

kthread->task = p;
kthread->node = tsk_fork_get_node(current); //获取当前线程的 NUMA 节点
p->worker_private = kthread;
return true;
}

__kthread_init_worker 初始化工作线程

1
2
3
4
5
6
7
8
9
10
11
void __kthread_init_worker(struct kthread_worker *worker,
const char *name,
struct lock_class_key *key)
{
memset(worker, 0, sizeof(struct kthread_worker));
raw_spin_lock_init(&worker->lock);
lockdep_set_class_and_name(&worker->lock, key, name);
INIT_LIST_HEAD(&worker->work_list);
INIT_LIST_HEAD(&worker->delayed_work_list);
}
EXPORT_SYMBOL_GPL(__kthread_init_worker);

__kthread_create_on_node 创建内核线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/*
* __printf(4, 0): GCC属性,告诉编译器检查printf风格的格式化字符串。
* 第4个参数是格式化字符串(namefmt),可变参数从第5个开始(va_list)。
*
* 这个函数是一个底层的内核线程创建接口。
*/
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
va_list args)
{
/* 在当前函数的栈上声明并初始化一个completion对象,用于同步。*/
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
/* 为创建请求动态分配一个kthread_create_info结构体。*/
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对象 */

/* 使用传入的格式化字符串和可变参数列表,动态生成完整的线程名。*/
create->full_name = kvasprintf(GFP_KERNEL, namefmt, args);
if (!create->full_name) {
task = ERR_PTR(-ENOMEM);
goto free_create; /* 如果生成线程名失败,跳转到清理步骤。*/
}

/* 获取kthread_create_lock自旋锁,保护全局链表。*/
spin_lock(&kthread_create_lock);
/* 将创建请求添加到kthread_create_list链表的尾部。*/
list_add_tail(&create->list, &kthread_create_list);
/* 释放自旋锁。*/
spin_unlock(&kthread_create_lock);

/* 唤醒kthreadd守护线程,让它来处理链表中的请求。
* 将kthreadd_task入队rq运行队列,设置kthreadd_task的状态为TASK_RUNNING。
*/
/* [main.c](/init/main.c:724)
pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
*/
wake_up_process(kthreadd_task);
/*
* 注释:在一个可被杀死的(killable)状态下等待completion。因为当
* kthreadd尝试为新内核线程分配内存时,我(当前线程)
* 可能会被OOM killer(内存不足杀手)选中。
*/
/* completion中执行了调度器 之后将会执行kthread的任务函数*/
if (unlikely(wait_for_completion_killable(&done))) {
/*
* 如果wait被一个致命信号中断(函数返回非零值),
* 需要处理一个复杂的竞争条件。
*/
/*
* 注释:如果我在kthreadd(或新线程)调用complete()之前被一个致命
* 信号杀死,就把这个结构体的清理工作留给那个线程。
*/
/* 原子地交换create->done为NULL。如果交换前的旧值不为NULL,
* 说明我们赢得了竞争,kthreadd还没有处理它。*/
if (xchg(&create->done, NULL))
return ERR_PTR(-EINTR); /* 返回“被中断”错误。*/

/*
* 如果xchg返回NULL,说明kthreadd已经拿走了done指针,
* 即将或已经调用complete()。我们输掉了竞争,但必须等待它完成。
*/
/*
* 注释:kthreadd(或新线程)很快就会调用complete()。
*/
wait_for_completion(&done);
}

/* 到达这里时,kthreadd已经处理完请求,结果保存在create->result中。*/
task = create->result;

free_create:
/* 释放用于传递信息的kthread_create_info结构体和线程名。*/
kfree(create->full_name);
kfree(create);

/* 返回新创建的task_struct指针或错误指针。*/
return task;
}

kthread_create_on_node 创建内核线程,调用传入的线程函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* kthread_create_on_node - 创建一个内核线程。
* @threadfn: 要运行的函数,直到 signal_pending(current)。
* @data: 用于 @threadfn 的数据指针。
* @node: 为线程分配任务和线程结构的节点。
* @namefmt: 线程的 printf 风格名称。
*
* 描述: 此辅助函数用于创建并命名一个内核线程。
* 线程将被停止:使用 wake_up_process() 来启动它。
* 另请参阅 kthread_run()。新线程具有 SCHED_NORMAL 策略,并与所有 CPU 相关联。
*
* 如果线程将绑定到特定的 CPU,请在 @node 中提供其节点,
* 以获得 kthread 堆栈的 NUMA 亲和性,否则提供 NUMA_NO_NODE。
* 当被唤醒时,线程将使用 @data 作为参数运行 @threadfn()。
* 如果线程是独立线程且不会调用 kthread_stop(),@threadfn() 可以直接返回,
* 或者当 'kthread_should_stop()' 为真时返回(这意味着已调用 kthread_stop())。
* 返回值应为零。
* 或负错误编号;它将被传递给 kthread_stop()。
*
* 返回一个 task_struct 或 ERR_PTR(-ENOMEM) 或 ERR_PTR(-EINTR)。
*/
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
...)
{
struct task_struct *task;
va_list args;

va_start(args, namefmt);
task = __kthread_create_on_node(threadfn, data, node, namefmt, args);
va_end(args);

return task;
}
EXPORT_SYMBOL(kthread_create_on_node);

kthread_worker_fn 处理kthread_worker的kthread函数

  • 它的核心作用是:在一个无限循环中,不断地从其私有的工作链表(工人->工作列表)中取出待处理的工作项(线程工作),并串行地执行它们。当没有工作时,它会进入可中断睡眠,直到被kthread_queue_work唤醒。
    它实现了一个单线程的、串行化的、先进先出(FIFO)的异步任务处理器。
    其工作原理是一个**“睡眠-唤醒-处理”**的循环模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/**
* kthread_worker_fn - 处理kthread_worker的kthread函数
* @worker_ptr: 指向已初始化的kthread_worker的指针
*
* 这个函数实现了kthread工作者的主循环。它会处理work_list直到
* 被kthread_stop()停止。当队列为空时,它会睡眠。
*
* 工作项在完成时不应持有任何锁、禁用抢占或中断。在一个工作项完成
* 和下一个开始之前,定义了一个用于冻结的安全点。
*
* 另外,工作项不能同时被多个worker处理,另见kthread_queue_work()。
*/
int kthread_worker_fn(void *worker_ptr)
{
/* worker: 指向描述此工作者自身状态的kthread_worker结构体。*/
struct kthread_worker *worker = worker_ptr;
/* work: 指向当前正在处理的kthread_work工作项。*/
struct kthread_work *work;

/*
* FIXME: 当所有kthread worker用户都使用kthread_create_worker*()函数创建后,
* 更新这个检查并移除赋值操作。
* 原理:这是一个兼容性/健壮性检查。worker->task应该在创建时就已设置,
* 这里再次检查并赋值,以确保双向链接是正确的。
*/
WARN_ON(worker->task && worker->task != current);
worker->task = current;

/* 如果worker被标记为可冻结的(KFT_FREEZABLE)。*/
if (worker->flags & KTW_FREEZABLE)
/* 调用set_freezable(),将PF_FREEZE标志加入自己的flags,
* 以便在系统休眠时能响应冻结请求。*/
set_freezable();

repeat: /* 主循环的入口点。*/
/*
* 将当前任务状态设置为可中断睡眠。这是一个乐观的假设,即接下来可能无事可做。
* 它与kthread_stop中的wake_up_process配对,形成一个内存屏障,
* 确保对kthread_should_stop()的检查是最新的。
*/
set_current_state(TASK_INTERRUPTIBLE);

/* 检查是否有外部请求要求本线程停止并退出。*/
if (kthread_should_stop()) {
/* 如果需要停止,则将状态恢复为运行中,以执行清理。*/
__set_current_state(TASK_RUNNING);
/* 获取锁,以安全地修改worker->task指针。*/
raw_spin_lock_irq(&worker->lock);
/* 将worker中的task指针清空,表示此worker不再与任何线程关联。*/
worker->task = NULL;
raw_spin_unlock_irq(&worker->lock);
/* 从线程主函数返回0,这将最终导致线程退出。*/
return 0;
}

/* 将work指针初始化为NULL。*/
work = NULL;
/* 获取保护worker->work_list的自旋锁。*/
raw_spin_lock_irq(&worker->lock);
/* 检查工作链表是否不为空。*/
if (!list_empty(&worker->work_list)) {
/* 如果有工作,则从链表头部获取第一个工作项。*/
work = list_first_entry(&worker->work_list,
struct kthread_work, node);
/* 将该工作项从链表中移除。*/
list_del_init(&work->node);
}
/* 将当前正在处理的工作项指针(可能是NULL)存入worker->current_work。*/
worker->current_work = work;
/* 释放自旋锁。*/
raw_spin_unlock_irq(&worker->lock);

/* 检查是否成功获取到了一个工作项。*/
if (work) {
/* 获取工作项的回调函数指针。*/
kthread_work_func_t func = work->func;
/* 将自己的状态恢复为运行中,因为即将开始工作。*/
__set_current_state(TASK_RUNNING);
/* 记录一个追踪事件,标记工作开始执行。*/
trace_sched_kthread_work_execute_start(work);
/*
* 调用工作项的回调函数,执行真正的异步任务。
* C语言中,work->func(work)等价于(*work->func)(work)。
*/
work->func(work);
/*
* 在此之后,避免解引用work。追踪事件只关心它的地址。
* 因为回调函数func可能会释放work结构体本身。
*/
trace_sched_kthread_work_execute_end(work, func);
} else if (!freezing(current)) {
/* 如果没有获取到工作,并且当前没有被冻结,则调用调度器。
* 因为状态在循环开始时已设为TASK_INTERRUPTIBLE,
* 所以这里会使线程进入睡眠。*/
schedule();
} else {
/*
* 处理当前任务保持在TASK_INTERRUPTIBLE状态的情况。
* try_to_freeze()期望当前任务是TASK_RUNNING。
*/
/* 如果没有工作但正在被冻gel,则需要先恢复为RUNNING状态,
* 以便try_to_freeze()能正确处理。*/
__set_current_state(TASK_RUNNING);
}

/* 调用try_to_freeze(),这是一个标准的冻结点。*/
try_to_freeze();
/* 调用条件重新调度,在完成一轮工作后,给其他高优先级任务一个运行的机会。*/
cond_resched();
/* 跳转到主循环的起点。*/
goto repeat;
}
/* 将函数符号以GPL兼容的方式导出。*/
EXPORT_SYMBOL_GPL(kthread_worker_fn);

kthread_create_worker_on_node 创建内核线程工作,调用内核工作函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static __printf(3, 0) struct kthread_worker *
__kthread_create_worker_on_node(unsigned int flags, int node,
const char namefmt[], va_list args)
{
struct kthread_worker *worker;
struct task_struct *task;

worker = kzalloc(sizeof(*worker), GFP_KERNEL);
if (!worker)
return ERR_PTR(-ENOMEM);

kthread_init_worker(worker);

task = __kthread_create_on_node(kthread_worker_fn, worker,
node, namefmt, args);
if (IS_ERR(task))
goto fail_task;

worker->flags = flags;
worker->task = task;

return worker;

fail_task:
kfree(worker);
return ERR_CAST(task);
}

/**
* kthread_create_worker_on_node - 创建一个 kthread worker
* @flags:修改 worker 默认行为的标志
* @node:线程的任务结构在此节点上分配
* @namefmt:kthread worker(任务)的 printf 样式名称。
*
* 成功时返回指向已分配 worker 的指针 ERR_PTR(-ENOMEM)
* 当无法分配所需的结构时,以及 ERR_PTR (-EINTR)
* 当呼叫者被致命信号杀死时。
*/
struct kthread_worker *
kthread_create_worker_on_node(unsigned int flags, int node, const char namefmt[], ...)
{
struct kthread_worker *worker;
va_list args;

va_start(args, namefmt);
worker = __kthread_create_worker_on_node(flags, node, namefmt, args);
va_end(args);

return worker;
}
EXPORT_SYMBOL(kthread_create_worker_on_node);

kthread_bind 将内核线程绑定到特定的CPU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
/*
* 这是一个静态的内部函数,是所有kthread绑定操作的最终执行者。
* @p: 指向目标kthread的task_struct。
* @mask: 指向描述了允许运行的CPU集合的cpumask。
* @state: 调用者期望p所处的非运行状态掩码。
*/
static void __kthread_bind_mask(struct task_struct *p, const struct cpumask *mask, unsigned int state)
{
/* flags: 用于保存spin_lock_irqsave获取锁前的中断状态。*/
unsigned long flags;

/*
* 调用wait_task_inactive,同步等待,直到任务p进入一个非运行状态
* (其task->state匹配state掩码)。如果等待失败(例如,任务意外运行了),
* 这是一个内核逻辑错误。
*/
if (!wait_task_inactive(p, state)) {
/* 触发一次性内核警告。*/
WARN_ON(1);
return;
}

/*
* 注释:这是安全的,因为任务是非活动的。
* 获取任务的pi_lock,以保护对调度相关字段的修改。
*/
raw_spin_lock_irqsave(&p->pi_lock, flags);
/* 调用底层函数,实际地修改任务的CPU亲和性掩码。*/
do_set_cpus_allowed(p, mask);
/*
* 设置PF_NO_SETAFFINITY标志,以禁止用户空间通过sched_setaffinity
* 系统调用来修改这个内核线程的亲和性。
*/
p->flags |= PF_NO_SETAFFINITY;
/* 释放锁,并恢复中断状态。*/
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
}
/*
* 这是一个静态的内部辅助函数,用于将绑定到单个CPU的操作,
* 转换为基于掩码的绑定操作。
* @p: 目标任务。
* @cpu: 要绑定的单个CPU的编号。
* @state: 期望p所处的状态。
*/
static void __kthread_bind(struct task_struct *p, unsigned int cpu, unsigned int state)
{
/*
* 调用cpumask_of(cpu)宏,这个宏会返回一个临时的、只包含
* 指定cpu比特位的cpumask。然后调用底层的__kthread_bind_mask执行操作。
*/
__kthread_bind_mask(p, cpumask_of(cpu), state);
}
/*
* 将一个kthread绑定到一个cpumask。
* @p: 目标任务。
* @mask: 目标cpumask。
*/
void kthread_bind_mask(struct task_struct *p, const struct cpumask *mask)
{
/* 获取与p关联的kthread结构体。*/
struct kthread *kthread = to_kthread(p);
/*
* 调用底层的__kthread_bind_mask,并硬编码期望状态为TASK_UNINTERRUPTIBLE。
* 这是因为标准的kthread创建流程保证了新线程在被绑定时,
* 正处于此状态,等待被唤醒。
*/
__kthread_bind_mask(p, mask, TASK_UNINTERRUPTIBLE);
/*
* 这是一个重要的运行时检查。它断言在调用绑定函数时,该kthread
* 的started标志尚未被设置。这强制要求绑定操作必须在线程
* 真正开始执行其主逻辑(即kthread_stop可以对其生效)之前完成。
*/
WARN_ON_ONCE(kthread->started);
}

/**
* kthread_bind - 将一个刚创建的kthread绑定到一个cpu。
* @p: 由kthread_create()创建的线程。
* @cpu: @k将要运行于其上的cpu(可能不在线,但必须是可能的cpu)。
*
* 描述: 这个函数等价于set_cpus_allowed(),不同之处在于@cpu不需要在线,
* 并且该线程必须是停止的(即,刚从kthread_create()返回)。
*/
void kthread_bind(struct task_struct *p, unsigned int cpu)
{
/* 获取与p关联的kthread结构体。*/
struct kthread *kthread = to_kthread(p);
/* 调用内部的、绑定到单个CPU的辅助函数。*/
__kthread_bind(p, cpu, TASK_UNINTERRUPTIBLE);
/* 同样,执行对started标志的运行时检查。*/
WARN_ON_ONCE(kthread->started);
}
/* 将kthread_bind函数符号导出,供内核其他部分使用。*/
EXPORT_SYMBOL(kthread_bind);

kthread_create_on_cpu 创建一个绑定到特定CPU的内核线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* kthread_create_on_cpu - 创建一个绑定到特定CPU的kthread
* @threadfn: 要运行的函数,直到signal_pending(current)为真。
* @data: 传递给@threadfn的数据指针。
* @cpu: 线程应该被绑定到的那个CPU的编号。
* @namefmt: 用于线程的printf风格的名称。格式被限制为"name.*%u",
* 代码会自动填入CPU编号。
*
* 描述: 这个辅助函数创建并命名一个内核线程。
*/
struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
void *data, unsigned int cpu,
const char *namefmt)
{
/* p: 用于存储新创建任务的task_struct指针。*/
struct task_struct *p;

/*
* 调用更底层的、基于NUMA节点的创建函数。
* - threadfn, data, namefmt: 直接透传。
* - cpu_to_node(cpu): 将指定的CPU编号转换为其所属的NUMA节点编号,
* 作为一个内存分配的亲和性提示。
* - cpu: 作为可变参数,用于在namefmt中格式化线程名,如"kworker/%u"。
*/
p = kthread_create_on_node(threadfn, data, cpu_to_node(cpu), namefmt,
cpu);
/* 检查kthread_create_on_node是否返回了一个错误。IS_ERR会判断指针是否编码了错误。*/
if (IS_ERR(p))
/* 如果创建失败,则直接返回这个错误指针。*/
return p;

/*
* 调用kthread_bind,这是实现硬亲和性的关键。
* 它会修改任务p的cpus_allowed掩码,使其只能在指定的cpu上运行。
*/
kthread_bind(p, cpu);
/*
* 注释:当解除线程停泊时,CPU热插拔需要再次进行绑定。
*
* 原理:将目标CPU编号明确地存储在kthread的私有数据结构中。
* 这个值在处理CPU热插拔事件时至关重要,当一个离线的CPU重新
* 上线,内核需要根据这个存储的值,来为该CPU上的per-cpu线程
* 恢复正确的绑定关系。
*/
to_kthread(p)->cpu = cpu;

/* 返回成功创建的任务的task_struct指针。*/
return p;
}
/*
* 将kthread_create_on_cpu函数符号导出,使得可加载内核模块(LKM)也能够调用它。
*/
EXPORT_SYMBOL(kthread_create_on_cpu);

create_kthread 负责执行实际的线程创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* 这是一个静态函数,由kthreadd调用,负责执行实际的线程创建。
* @create: 指向包含所有创建信息的kthread_create_info结构体的指针。
*/
static void create_kthread(struct kthread_create_info *create)
{
int pid;

/* 如果内核配置了NUMA支持。*/
#ifdef CONFIG_NUMA
/*
* 设置当前任务(kthreadd)的'pref_node_fork'字段。
* 这会给接下来的kernel_thread调用一个提示,让它优先在
* create->node指定的NUMA节点上为新线程分配内存。
*/
current->pref_node_fork = create->node;
#endif
/*
* 注释:我们需要自己的信号处理器(我们默认不接收任何信号)。
* 调用底层的kernel_thread函数来创建一个新线程。
* - 第一个参数kthread:是所有新内核线程的通用入口包装函数。
* - 第二个参数create:将整个信息结构体传递给新线程。
* - 第三个参数create->full_name:设置新线程的名称。
* - 第四个参数flags:指示新线程与kthreadd共享文件系统和文件描述符,
* 并在退出时向父进程发送SIGCHLD信号。
*/
pid = kernel_thread(kthread, create, create->full_name,
CLONE_FS | CLONE_FILES | SIGCHLD);

/* 如果pid小于0,表示线程创建失败。*/
if (pid < 0) {
/*
* 注释:当调用者被致命信号杀死时,释放这个结构体。
* 这是一个复杂的错误处理和同步路径。
*/
/*
* 原子地将create->done与NULL进行交换,并获取其旧值。
* 这是为了处理调用者和kthreadd之间的竞争条件。
*/
struct completion *done = xchg(&create->done, NULL);

/* 释放之前为线程名动态分配的内存。*/
kfree(create->full_name);

/* 如果done为NULL,说明调用者已被信号唤醒并放弃了等待。
* 我们只需要清理create结构体即可。*/
if (!done) {
kfree(create);
return;
}

/* 如果done不为NULL,说明我们拥有唤醒调用者的责任。*/
/* 将错误码存入结果字段。*/
create->result = ERR_PTR(pid);
/* 调用complete(),唤醒正在睡眠的原始调用者,并传递结果。*/
complete(done);
}
}

kthreadd 所有内核线程的管理器线程的主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/*
* kthreadd是所有内核线程的管理器线程的主函数。
* @unused: 此参数未使用。
*/
int kthreadd(void *unused)
{
/* 静态常量,定义该线程的名字。*/
static const char comm[TASK_COMM_LEN] = "kthreadd";
/* 获取当前任务(即kthreadd自身)的task_struct指针。*/
struct task_struct *tsk = current;

/* 注释:为我们的子线程(其他kthreads)设置一个干净的上下文,以便它们继承。*/
/* 设置任务名。*/
set_task_comm(tsk, comm);
/* 设置信号处理动作为忽略所有信号,防止被意外终止。*/
ignore_signals(tsk);
/* 设置CPU亲和性,允许其在指定的管理型CPU上运行。*/
set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD));
/* 设置内存亲和性,允许其在所有内存节点上分配。*/
set_mems_allowed(node_states[N_MEMORY]);

/* 设置PF_NOFREEZE标志,防止在系统休眠(suspend)时被冻结。*/
current->flags |= PF_NOFREEZE;
/* 初始化与cgroup相关的kthreadd特定数据。*/
cgroup_init_kthreadd();

/* 进入无限循环,作为守护线程持续运行。*/
for (;;) {
/* 将当前状态设置为可中断睡眠。*/
set_current_state(TASK_INTERRUPTIBLE);
/* 如果创建请求链表为空,则调用调度器,放弃CPU并睡眠。*/
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);
/* 暂时释放锁,因为create_kthread可能耗时较长或引起调度。*/
spin_unlock(&kthread_create_lock);

/* 调用核心函数来执行实际的线程创建工作。*/
create_kthread(create);

/* 再次获取锁,以准备处理下一个请求。*/
spin_lock(&kthread_create_lock);
}
/* 释放锁。*/
spin_unlock(&kthread_create_lock);
}

/* 理论上,此函数永远不会返回。返回0只是为了满足函数签名。*/
return 0;
}

__kthread_parkme 为内核线程提供一个标准的“停泊(Parking)”机制

  • 它的核心作用是为内核线程提供一个标准的“停泊(Parking)”机制。 当其他内核代码(通常是该线程的创建者或管理者)请求一个内核线程暂停其工作时,该线程会在__kthread_parkme这里进入睡眠,直到被明确地“唤醒(unpark)”。..
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
* 这是一个静态函数,是内核线程的通用停泊实现。
* @self: 指向与当前任务关联的kthread结构体。
*/
static void __kthread_parkme(struct kthread *self)
{
/*
* 进入一个无限循环,以处理可能的多次“停泊-唤醒”周期。
*/
for (;;) {
/*
* 注释:TASK_PARKED是一个特殊状态;我们必须与可能挂起的唤醒操作
* 进行序列化,以避免对task->state的存储-存储冲突。
*
* 注释:这样的冲突可能会导致任务状态从TASK_PARKED改变,
* 从而使我们在kthread_park()中的wait_task_inactive()失败。
*
* 原理:set_special_state是一个受锁保护的状态设置函数,它能原子地
* 设置状态,避免与并发的try_to_wake_up()产生竞争。
*/
set_special_state(TASK_PARKED);

/*
* 检查KTHREAD_SHOULD_PARK标志位。这个标志由外部调用kthread_park()的
* 线程设置。如果该标志未被设置,说明我们不需要停泊。
*/
if (!test_bit(KTHREAD_SHOULD_PARK, &self->flags))
break; /* 跳出循环,继续执行。*/

/*
* 注释:线程将要调用schedule(),不要抢占它,否则kthread_park()的
* 调用者可能会在wait_task_inactive()中花费更多时间。
*
* 原理:在通知调用者“我已停泊”和实际睡眠之间,禁用抢占,可以确保
* 这个过程不会被意外延迟,从而让调用者能更快地确认停泊已完成。
*/
preempt_disable();
/*
* 调用complete(),通知并唤醒正在kthread_park()中等待
* self->parked这个completion对象的那个线程。
*/
complete(&self->parked);
/*
* 调用调度器,主动放弃CPU,进入睡眠。因为抢占是禁用的,
* 所以使用schedule_preempt_disabled()。
*/
schedule_preempt_disabled();
/*
* 当被kthread_unpark()唤醒后,从这里继续执行,并重新使能抢占。
*/
preempt_enable();
}
/*
* 当跳出循环后,将当前任务的状态明确地设置回TASK_RUNNING,
* 为接下来执行实际工作做准备。
*/
__set_current_state(TASK_RUNNING);
}

kthread_park 停泊一个由kthread_create()创建的线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* kthread_park - 停泊一个由kthread_create()创建的线程。
* @k: 由kthread_create()创建的线程的task_struct指针。
*
* 为@k设置kthread_should_park()使其返回true,唤醒它,并等待它返回。
* 这个函数也可以在kthread_create()之后,代替wake_up_process()被调用:
* 此时,该线程会在不调用其threadfn()的情况下,直接进入停泊状态。
*
* 返回值: 如果线程被成功停泊,则返回0;如果线程已退出,则返回-ENOSYS。
* 如果由kthread自己调用,则只设置停泊标志位。
*/
int kthread_park(struct task_struct *k)
{
/* kthread: 指向与任务k关联的kthread私有数据结构体。*/
struct kthread *kthread = to_kthread(k);

/* 这是一个健壮性检查。如果目标线程k已经处于退出流程中(PF_EXITING),
* 则不能再对其进行停泊。打印一次性警告并返回错误。*/
if (WARN_ON(k->flags & PF_EXITING))
return -ENOSYS;

/* 检查KTHREAD_SHOULD_PARK标志是否已经被设置。如果是,说明已经有
* 另一个调用者请求停泊该线程,本次操作为重复请求。打印警告并返回错误。*/
if (WARN_ON_ONCE(test_bit(KTHREAD_SHOULD_PARK, &kthread->flags)))
return -EBUSY;

/* 原子地设置KTHREAD_SHOULD_PARK标志位。这是向目标线程k发出的“请停泊”的请求。*/
set_bit(KTHREAD_SHOULD_PARK, &kthread->flags);

/* 检查调用者是否在试图停泊自己。*/
if (k != current) {
/* 如果是停泊其他线程,则必须唤醒它,以确保它能看到新设置的标志位。*/
wake_up_process(k);
/*
* 注释:等待__kthread_parkme()调用complete(),这意味着我们
* *将要*进入TASK_PARKED状态,并且即将调用schedule()。
*
* 原理:这是第一重等待。它等待目标线程k在__kthread_parkme中
* 执行完complete(&kthread->parked),以此确认k已响应请求。
*/
wait_for_completion(&kthread->parked);
/*
* 注释:现在等待那个schedule()调用完成,并且任务被调度出去。
*
* 原理:这是第二重等待。调用wait_task_inactive来最终确认
* 目标线程k确实已经因为调用schedule()而离开了运行队列,
* 进入了稳定的TASK_PARKED状态。
*/
WARN_ON_ONCE(!wait_task_inactive(k, TASK_PARKED));
}

/* 返回0表示请求已成功提交(或在自我停泊的情况下),或者已确认停泊。*/
return 0;
}
/*
* 将kthread_park函数符号以GPL兼容的方式导出。
*/
EXPORT_SYMBOL_GPL(kthread_park);

kthread_unpark 清除目标内核线程k的“应该停泊”标志,并将其从特殊的TASK_PARKED睡眠状态中唤醒,使其能够继续执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* kthread_unpark - 解除一个由kthread_create()创建的线程的停泊状态。
* @k: 由kthread_create()创建的线程的task_struct指针。
*
* 为@k设置kthread_should_park()使其返回false,唤醒它,并等待它返回。
* 如果该线程被标记为per-cpu线程,那么它会再次被绑定到CPU上。
*/
void kthread_unpark(struct task_struct *k)
{
/* kthread: 指向与任务k关联的kthread私有数据结构体。*/
struct kthread *kthread = to_kthread(k);

/*
* 使用test_bit检查KTHREAD_SHOULD_PARK标志是否被设置。
* 如果没有被设置,说明线程当前不处于被请求停泊的状态,无需操作。
*/
if (!test_bit(KTHREAD_SHOULD_PARK, &kthread->flags))
return;
/*
* 注释:新创建的kthread在CPU离线时被停泊了。
* 绑定关系已丢失,我们需要再次设置它。
*
* 原理:这是一个处理CPU热插拔的健壮性逻辑。如果一个per-cpu线程
* 因为其CPU下线而被停泊,当它被唤醒时,必须重新确立其
* CPU亲和性。
*/
/* 检查该线程是否为per-cpu线程。*/
if (test_bit(KTHREAD_IS_PER_CPU, &kthread->flags))
/* 如果是,则调用__kthread_bind重新将其绑定到其预设的CPU上。
* 传入TASK_PARKED状态,是因为此时任务正处于该状态。*/
__kthread_bind(k, kthread->cpu, TASK_PARKED);

/*
* 原子地清除KTHREAD_SHOULD_PARK标志位。
* 这是给正在__kthread_parkme中循环的线程一个明确的信号:你可以继续工作了。
*/
clear_bit(KTHREAD_SHOULD_PARK, &kthread->flags);
/*
* 注释:__kthread_parkme()要么会看到!SHOULD_PARK,要么会收到这次唤醒。
*
* 原理:调用wake_up_state(),这是一个精确的唤醒函数。它只会唤醒那些
* 任务状态与第二个参数(TASK_PARKED)完全匹配的任务k。
* 这确保了我们只唤醒那个因为停泊而睡眠的线程。
*/
wake_up_state(k, TASK_PARKED);
}
/*
* 将kthread_unpark函数符号以GPL兼容的方式导出,
* 使得遵循GPL许可的可加载内核模块也能调用它。
*/
EXPORT_SYMBOL_GPL(kthread_unpark);

kthread 所有新内核线程的入口包装函数

  • 它是所有通过kthread_create()系列函数创建的内核线程,在被kthreadd派生(fork)出来后,第一个真正执行的C函数。它是一个通用的“外壳”或“启动器”
  • kthread函数是新创建的内核线程的通用入口点。它并不执行用户指定的具体任务,而是负责在新线程的上下文中,完成一系列关键的初始化、同步和生命周期管理工作,然后才调用用户提供的线程主函数(threadfn)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/*
* kthread - 所有新内核线程的入口包装函数。
* @_create: 指向kthread_create_info结构体的指针,包含了创建信息。
* struct kthread_create_info *create;

create = list_entry(kthread_create_list.next,
struct kthread_create_info, list);
*/
static int kthread(void *_create)
{
/* 定义一个静态的调度参数,用于设置默认的调度策略和优先级。*/
static const struct sched_param param = { .sched_priority = 0 };
/*
* 注释:拷贝数据:因为它位于kthread的栈上。
* 更准确地说,_create指向kthreadd栈上的一个结构体,
* 必须先拷贝到当前线程的栈上或寄存器中,因为kthreadd可能很快就释放它。
*/
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn;
void *data = create->data;
struct completion *done;
struct kthread *self;
int ret;

/* 获取与当前task_struct关联的kthread结构体。*/
self = to_kthread(current);

/*
* 注释:当调用者被致命信号杀死时,释放这个结构体。
* 这是与__kthread_create_on_node()进行同步的关键部分。
*/
/* 原子地将create->done与NULL交换,并获取其旧值。*/
done = xchg(&create->done, NULL);
/* 如果done为NULL,说明创建者已被信号中断并放弃了等待。
* 我们必须负责清理,并调用kthread_exit()退出。*/
if (!done) {
kfree(create->full_name);
kfree(create);
kthread_exit(-EINTR);
}

/* 将线程名、函数指针和数据从临时结构体复制到自己的结构体中。*/
self->full_name = create->full_name;
self->threadfn = threadfn;
self->data = data;

/*
* 注释:新线程继承了kthreadd的优先级和CPU掩码。
* 如果它们被改变过,这里重置回默认值。
*/
/* 将自己的调度策略设置为SCHED_NORMAL,优先级为默认。
fair公平调度*/
sched_setscheduler_nocheck(current, SCHED_NORMAL, &param);

/*
* 注释:告诉用户我们已经生成了,等待stop或wakeup。
* 将自己的状态置为不可中断睡眠。
*/
__set_current_state(TASK_UNINTERRUPTIBLE);
/* 将自己的task_struct指针存入结果字段,以便创建者获取。*/
create->result = current;
/*
* 注释:线程将要调用schedule(),不要抢占它,否则创建者可能会在
* wait_task_inactive()中花费更多时间。
*/
preempt_disable();
/* 调用complete(),唤醒正在等待的创建者。*/
complete(done);
/* 调用调度器,将自己投入睡眠,并让出CPU。*/
schedule_preempt_disabled();
preempt_enable();

/* 到达这里时,表示创建者已经通过wake_up_process()唤醒了我们。*/
self->started = 1;

/* 如果允许设置CPU亲和性且没有预设的亲和性,则根据NUMA节点进行设置。*/
if (!(current->flags & PF_NO_SETAFFINITY) && !self->preferred_affinity)
kthread_affine_node();

/* 初始化默认返回值为-EINTR。*/
ret = -EINTR;
/* 检查KTHREAD_SHOULD_STOP标志,如果创建者在我们运行前就要求停止,
* 就不执行线程主函数。*/
if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {
/* 通知cgroup子系统,本kthread已准备就绪。*/
// cgroup_kthread_ready();
/* 调用__kthread_parkme(),这是一个“停泊点”,如果被要求park,会再次睡眠。*/
__kthread_parkme(self);
/* 执行用户指定的线程主函数,并获取其返回值。*/
ret = threadfn(data);
}

/* 当线程主函数返回后,调用kthread_exit()来结束生命周期。*/
kthread_exit(ret);
}

kthread_exit 结束内核线程的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* kthread_exit - 使当前kthread向kthread_stop()返回@result。
* @result: 要返回给kthread_stop()的整型值。
*
* 虽然kthread_exit可以被直接调用,但它作为一个独立函数存在,是为了
* 让那些在非模块化代码中需要做额外工作的函数(例如
* module_put_and_kthread_exit)可以被实现。
*
* 此函数不会返回。
*/
/*
* __noreturn: 这是一个GCC属性,告诉编译器和静态分析工具,这个函数永远不会返回。
* 这有助于编译器进行更好的优化,并避免产生不必要的警告。
*/
void __noreturn kthread_exit(long result)
{
/* 获取与当前任务(current)关联的kthread私有数据结构体。*/
struct kthread *kthread = to_kthread(current);

/* 将调用者传入的结果码,保存到kthread结构体的result字段中。
* 之后,调用kthread_stop()的线程可以从这里获取到这个返回值。*/
kthread->result = result;

/* 检查该线程是否被注册用于处理CPU热插拔事件(通过检查其hotplug_node链表节点是否在链表中)。*/
if (!list_empty(&kthread->hotplug_node)) {
/* 如果是,则必须将其从全局的热插拔链表中移除。*/
/* 获取保护该全局链表的互斥锁。*/
mutex_lock(&kthreads_hotplug_lock);
/* 将该线程的节点从链表中安全地删除。*/
list_del(&kthread->hotplug_node);
/* 释放互斥锁。*/
mutex_unlock(&kthreads_hotplug_lock);

/* 如果为该线程动态分配了首选CPU亲和性掩码。*/
if (kthread->preferred_affinity) {
/* 释放该掩码所占用的内存。*/
kfree(kthread->preferred_affinity);
/* 将指针置为NULL,防止悬空指针。*/
kthread->preferred_affinity = NULL;
}
}

/*
* 调用内核通用的任务退出函数do_exit()。
* 传入的参数0是传统的Unix退出码,对于内核线程,这个值通常没有意义。
* do_exit()将完成剩余的所有清理工作,将任务置为僵尸状态,并让出CPU。
* 此函数永远不会返回。
*/
do_exit(0);
}
/*
* 将kthread_exit函数符号导出,使得可加载内核模块(LKM)也能够调用它。
*/
EXPORT_SYMBOL(kthread_exit);

kthread_should_stop 检查当前内核线程是否应该停止

1
2
3
4
5
6
7
8
9
10
11
12
/**
* kthread_should_stop - should this kthread return now?
*
* When someone calls kthread_stop() on your kthread, it will be woken
* and this will return true. You should then return, and your return
* value will be passed through to kthread_stop().
*/
bool kthread_should_stop(void)
{
return test_bit(KTHREAD_SHOULD_STOP, &to_kthread(current)->flags);
}
EXPORT_SYMBOL(kthread_should_stop);

kthread_stop 请求一个指定的内核线程k停止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* kthread_stop - 停止一个由kthread_create()创建的线程。
* @k: 由kthread_create()创建的线程的task_struct指针。
*
* 为@k设置kthread_should_stop()使其返回true,唤醒它,并等待它退出。
* 这个函数也可以在kthread_create()之后,代替wake_up_process()被调用:
* 此时,该线程会直接退出而不会调用其threadfn()。
*
* 如果threadfn()可能会自己调用kthread_exit(),那么调用者必须确保
* task_struct不会在此期间消失(例如,预先增加引用计数)。
*
* 返回值:threadfn()的结果,或者如果wake_up_process()从未被调用过,
* 则返回%-EINTR。
*/
int kthread_stop(struct task_struct *k)
{
/* kthread: 指向与任务k关联的kthread私有数据结构体。*/
struct kthread *kthread;
/* ret: 用于存储并返回目标线程的退出结果。*/
int ret;

/* 为内核追踪系统记录“停止kthread”事件。*/
trace_sched_kthread_stop(k);

/*
* 增加目标线程k的task_struct的引用计数。
* 这是至关重要的,它确保了即使k在我们等待期间自己退出了,
* 它的task_struct结构体也不会被释放,我们可以安全地访问它。
*/
get_task_struct(k);
/* 获取与任务k关联的kthread结构体。*/
kthread = to_kthread(k);
/*
* 原子地设置KTHREAD_SHOULD_STOP标志位。
* 正在运行的目标线程k在其主循环中,会通过kthread_should_stop()检查此标志。
*/
set_bit(KTHREAD_SHOULD_STOP, &kthread->flags);
/*
* 调用kthread_unpark()。如果目标线程k正处于“停泊”状态(在__kthread_parkme中睡眠),
* 此函数会清除其SHOULD_PARK标志并唤醒它。
*/
kthread_unpark(k);
/*
* 设置TIF_NOTIFY_SIGNAL标志,这是一个用于唤醒处于特殊等待状态任务的机制。
*/
set_tsk_thread_flag(k, TIF_NOTIFY_SIGNAL);
/*
* 调用wake_up_process(),这是最通用的唤醒函数。
* 它会将目标线程k的状态设置为TASK_RUNNING,并将其放入运行队列。
* 这三步(unpark, notify, wake_up)确保了无论k处于何种睡眠状态,都能被可靠地唤醒。
*/
wake_up_process(k);
/*
* 调用wait_for_completion(),使当前线程进入不可中断的睡眠,
* 直到目标线程k在其退出路径的最后调用complete(&kthread->exited)。
* 这是一个同步点,确保我们能等到k完全退出。
* 路径:kthread_exit() -> exit_mm()-> exit_mm_release()-> complete_vfork_done()
* -> complete(&kthread->exited)。
*/
wait_for_completion(&kthread->exited);
/*
* 当被唤醒后,从kthread结构体中获取由目标线程通过kthread_exit()保存的结果码。
*/
ret = kthread->result;
/*
* 减少我们在一开始获取的对k的task_struct的引用计数。
* 这可能是导致该task_struct被最终释放的最后一次put操作。
*/
put_task_struct(k);

/* 为内核追踪系统记录“停止kthread”操作的返回值。*/
trace_sched_kthread_stop_ret(ret);
/* 返回获取到的结果。*/
return ret;
}
/*
* 将kthread_stop函数符号导出,使得可加载内核模块(LKM)也能够调用它。
*/
EXPORT_SYMBOL(kthread_stop);

kthread_stop_put 停止一个内核线程并释放其task_struct

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* kthread_stop_put - 停止线程并释放其任务结构
* @k: 由 kthread_create() 创建的线程。
*
* 停止由 kthread_create() 创建的线程并释放其 task_struct。
* 仅在通过调用 get_task_struct() 获取额外的任务结构引用时使用。
*/
int kthread_stop_put(struct task_struct *k)
{
int ret;

ret = kthread_stop(k);
put_task_struct(k);
return ret;
}
EXPORT_SYMBOL(kthread_stop_put);

kthread_queue_work 将一个kthread_work排入队列

  • 它的核心作用是:将一个指定的工作项(struct kthread_work)安全地、原子性地排入一个特定的kthread工作者(struct kthread_worker)的待办事项链表中,并唤醒该工作者线程(如果它在睡眠)来处理这个工作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
* Returns true when the work could not be queued at the moment.
* It happens when it is already pending in a worker list
* or when it is being cancelled.
*/
static inline bool queuing_blocked(struct kthread_worker *worker,
struct kthread_work *work)
{
lockdep_assert_held(&worker->lock);

return !list_empty(&work->node) || work->canceling;
}

/* insert @work before @pos in @worker */
static void kthread_insert_work(struct kthread_worker *worker,
struct kthread_work *work,
struct list_head *pos)
{
kthread_insert_work_sanity_check(worker, work);

trace_sched_kthread_work_queue_work(worker, work);

list_add_tail(&work->node, pos);
work->worker = worker;
if (!worker->current_work && likely(worker->task))
wake_up_process(worker->task);
}

/**
* kthread_queue_work - 将一个kthread_work排入队列。
* @worker: 目标kthread_worker。
* @work: 要排入队列的kthread_work。
*
* 将@work排入工作处理器@task以进行异步执行。@task必须是通过
* kthread_create_worker()创建的。如果@work被成功排入队列,则返回%true;
* 如果它已经是待处理状态,则返回%false。
*
* 如果需要将该work用于另一个worker,需要对其进行重新初始化。
* 例如,当worker被停止并重新启动时。
*/
bool kthread_queue_work(struct kthread_worker *worker,
struct kthread_work *work)
{
/* ret: 函数的返回值,布尔类型,默认为false。*/
bool ret = false;
/* flags: 用于保存spin_lock_irqsave获取锁前的中断状态。*/
unsigned long flags;

/*
* 获取保护worker内部状态(特别是work_list链表)的自旋锁,并禁用本地中断。
*/
raw_spin_lock_irqsave(&worker->lock, flags);
/*
* 调用queuing_blocked()检查work是否已经被排入某个队列。
* 这是通过检查work内部的一个标志位来实现的,防止重复排队。
*/
if (!queuing_blocked(worker, work)) {
/*
* 如果没有被阻塞,则调用kthread_insert_work。
* 这个函数会将work加入到worker->work_list链表的尾部,
* 并唤醒与该worker关联的内核线程。
*/
kthread_insert_work(worker, work, &worker->work_list);
/* 将返回值设置为true,表示排队成功。*/
ret = true;
}
/* 释放自旋锁,并恢复之前的中断状态。*/
raw_spin_unlock_irqrestore(&worker->lock, flags);

/* 返回操作结果。*/
return ret;
}
/*
* 将kthread_queue_work函数符号以GPL兼容的方式导出,
* 使得遵循GPL许可的可加载内核模块也能调用它。
*/
EXPORT_SYMBOL_GPL(kthread_queue_work);

kthread_set_per_cpu 为一个内核线程设置其per-cpu身份和关联的CPU

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
* kthread_set_per_cpu - 为一个内核线程设置其per-cpu身份和关联的CPU。
* @k: 指向目标kthread的task_struct。
* @cpu: 该kthread所属的CPU编号。如果为负数,则表示清除per-cpu身份。
*/
void kthread_set_per_cpu(struct task_struct *k, int cpu)
{
/* kthread: 指向与任务k关联的kthread私有数据结构体。*/
struct kthread *kthread = to_kthread(k);

/* 如果to_kthread返回NULL(理论上不应发生,除非k不是一个kthread),则直接返回。*/
if (!kthread)
return;

/*
* 这是一个健壮性断言。它检查任务k是否已经被设置了PF_NO_SETAFFINITY标志。
* 这个标志禁止用户空间修改其CPU亲和性。一个per-cpu线程必须有这个标志,
* 以保证其CPU绑定的稳定性。如果标志未设置,则触发一次性内核警告。
*/
WARN_ON_ONCE(!(k->flags & PF_NO_SETAFFINITY));

/* 如果传入的cpu编号是负数,则视为一个“解绑”请求。*/
if (cpu < 0) {
/* 原子地清除KTHREAD_IS_PER_CPU标志位,取消其per-cpu身份。*/
clear_bit(KTHREAD_IS_PER_CPU, &kthread->flags);
return;
}

/* 将指定的CPU编号,保存到kthread私有结构体的cpu字段中。*/
kthread->cpu = cpu;
/* 原子地设置KTHREAD_IS_PER_CPU标志位,正式赋予其per-cpu身份。*/
set_bit(KTHREAD_IS_PER_CPU, &kthread->flags);
}

kthreads_init 初始化kthread子系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/*
* kthreads_init - 内核线程相关的热插拔状态初始化函数
*/
static int kthreads_init(void)
{
/*
* **核心操作**:调用 cpuhp_setup_state 函数,向CPU热插拔框架注册一个回调。
* 我们来分解它的参数:
*
* 1. CPUHP_AP_KTHREADS_ONLINE:
* 这是一个枚举常量,代表了CPU热插拔状态机中的一个特定“状态”或“阶段”。
* - CPUHP: 表示这是CPU Hotplug相关的状态。
* - AP: 表示这适用于应用CPU(Application Processors),即非引导CPU。
* - KTHREADS_ONLINE: 状态名,含义是“内核线程已上线”。这通常是CPU上线流程中
* 比较靠后的一个阶段,此时CPU已经准备好,可以开始创建和运行其专属的内核线程了。
* 这个参数指定了回调函数的**触发时机**。
*
* 2. "kthreads:online":
* 这是一个可读的名称字符串,用于标识这次注册。它会出现在调试信息和
* /sys/kernel/cpu/hotplug/states/ 目录中,方便开发者和管理员查看。
*
* 3. kthreads_online_cpu:
* 这是一个函数指针,指向当事件发生时需要被调用的**回调函数**。
* 当任何一个CPU进入 CPUHP_AP_KTHREADS_ONLINE 状态时,cpuhp框架就会
* 自动调用 kthreads_online_cpu(cpu_id)。
*
* 4. NULL:
* 这个位置通常是用于注册“下线”时的回调函数。这里传入NULL,表示我们只关心
* CPU上线时的事件,不关心它下线时需要做什么(或者下线的清理工作由其他
* 模块负责)。
*
* 返回值:函数返回 cpuhp_setup_state 的执行结果,0表示注册成功。
*/
return cpuhp_setup_state(CPUHP_AP_KTHREADS_ONLINE, "kthreads:online",
kthreads_online_cpu, NULL);
}