内核线程(Kernel Thread)创建和执行机制:
在 Linux 内核中,内核线程(kthread) 是一种特殊类型的线程,专门用于执行内核任务。与用户态的进程和线程不同,内核线程不具有用户空间,它仅在内核空间运行。内核线程的创建、启动和管理都通过内核提供的接口来完成,最常用的是 kthread_create()
和 wake_up_process()
。
1. 内核线程创建 (kthread_create
):
struct task_struct *kthread_create(
int (*threadfn)(void *data),
void *data,
const char *namefmt, ...);
-
参数解释:
int (*threadfn)(void *data)
:- 这是线程的主函数指针,它是一个接收
void *data
参数的函数。在创建的线程开始执行时,内核会调用这个函数。 - 该函数通常是一个包含线程执行逻辑的循环,例如处理任务或响应事件。
- 这是线程的主函数指针,它是一个接收
void *data
:- 传递给
threadfn
函数的数据,作为线程的执行上下文或参数。
- 传递给
const char *namefmt, ...
:- 这是可变参数列表,通常用于设置线程的名称,便于调试和监控。
-
返回值:
- 返回一个指向内核线程对应的
task_struct
的指针。如果线程创建成功,该task_struct
用于表示新创建的内核线程。 - 该线程不会立即开始执行,必须通过调用
wake_up_process()
启动线程。
- 返回一个指向内核线程对应的
2. 线程启动 (wake_up_process
):
int wake_up_process(struct task_struct *p);
kthread_create()
创建的线程不会自动开始执行。要使线程开始运行,需要将创建返回的task_struct
指针传递给wake_up_process()
函数,显式唤醒线程。- 这相当于告诉内核线程调度器,该线程已经准备好,可以开始调度并执行。
3. 内核线程执行函数 (thread_function
):
int thread_function(void *data);
thread_function
是由kthread_create()
创建的内核线程的主函数。它接收一个void *data
参数,该参数是在线程创建时传入的上下文或数据。- 通常,
thread_function
包含一个循环,用于处理任务或等待某些条件满足。它可以通过多种方式退出,如直接调用do_exit()
或者返回kthread_should_stop()
为true
。
4. 内核线程停止和退出:
- 直接退出:
do_exit(0);
- 如果线程是独立运行且不需要被显式停止(例如不需要外部的
kthread_stop()
调用来结束它),线程可以在完成其任务后直接调用do_exit()
函数。这会立即终止线程的执行,并释放相关资源。 - 通过
kthread_should_stop()
退出:if (kthread_should_stop()) return 0;
- 内核提供了
kthread_should_stop()
函数,用于检测线程是否应该停止。当其他进程调用kthread_stop()
时,该函数会返回true
。 - 线程在循环中检查
kthread_should_stop()
的返回值,如果为true
,则线程可以选择退出。这通常用于线程需要被外部显式停止的情况。
完整内核线程示例:
#include <linux/kthread.h> // 内核线程 API
#include <linux/delay.h> // 休眠函数
#include <linux/sched.h> // 调度 API
#include <linux/module.h> // 模块定义
#include <linux/kernel.h> // 内核函数
// 线程函数
int thread_function(void *data) {
while (!kthread_should_stop()) {
pr_info("Kernel thread running\n");
ssleep(5); // 线程休眠 5 秒
}
pr_info("Kernel thread stopping\n");
return 0; // 返回退出
}
static struct task_struct *task;
// 模块初始化函数
static int __init my_module_init(void) {
// 创建内核线程
task = kthread_create(thread_function, NULL, "my_kthread");
if (IS_ERR(task)) {
pr_err("Failed to create kernel thread\n");
return PTR_ERR(task);
}
// 启动线程
wake_up_process(task);
pr_info("Kernel thread created successfully\n");
return 0;
}
// 模块退出函数
static void __exit my_module_exit(void) {
// 停止线程
if (task) {
kthread_stop(task);
pr_info("Kernel thread stopped\n");
}
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
你提到的几个函数是与内核线程相关的重要 API,用于创建、启动和管理内核线程。下面我将详细说明每个函数的返回值和使用场景,并解释 kthread_run()
函数的便捷性。
1. kthread_create()
返回值
struct task_struct *kthread_create(
int (*threadfn)(void *data),
void *data,
const char *namefmt, ...);
- 成功:
- 当
kthread_create()
成功执行时,它会返回一个指向新创建的内核线程的task_struct
结构体指针。这个task_struct
包含了内核线程的所有信息,类似于进程的描述符。
- 当
- 失败:
- 如果
kthread_create()
执行失败,它不会返回一个普通的指针,而是返回一个通过ERR_PTR
宏包装的错误码。你可以使用IS_ERR()
或PTR_ERR()
来检查和解析这个错误码。 ERR_PTR
:这是内核用于将错误码转换为指针的机制,避免直接返回NULL
指针。
- 如果
- 示例:
struct task_struct *task; task = kthread_create(thread_function, NULL, "my_kthread"); if (IS_ERR(task)) { pr_err("Thread creation failed: %ld\n", PTR_ERR(task)); return PTR_ERR(task); // 返回错误码 }
2. 启动内核线程:wake_up_process()
int wake_up_process(struct task_struct *p);
-
内核线程在创建时并不会立即运行。必须通过调用
wake_up_process()
函数将线程加入调度队列,通知内核调度器开始调度该线程。 -
参数:
struct task_struct *p
:指向内核线程的task_struct
结构体。
-
返回值:
- 返回
1
,表示成功唤醒线程并加入调度队列。 - 返回
0
,表示线程已经处于活动状态,不需要再次唤醒。
- 返回
-
示例:
if (task) { wake_up_process(task); }
3. kthread_run()
:创建并启动内核线程的便捷函数
struct task_struct *kthread_run(
int (*threadfn)(void *data),
void *data,
const char *namefmt, ...);
-
kthread_run()
是kthread_create()
和wake_up_process()
的便捷组合。它在内核中同时创建和启动一个新线程。你无需显式调用wake_up_process()
,因为该函数会自动调用它。 -
参数:
- 与
kthread_create()
相同:int (*threadfn)(void *data)
:指向线程主函数的指针。void *data
:传递给线程函数的参数。const char *namefmt
:线程的名称。
- 与
-
返回值:
- 成功: 返回一个指向新线程的
task_struct
指针,表示线程已经创建并启动。 - 失败: 返回一个
ERR_PTR
,可以使用IS_ERR()
来检查。
- 成功: 返回一个指向新线程的
-
示例:
struct task_struct *task; // 创建并启动内核线程 task = kthread_run(thread_function, NULL, "my_kthread"); if (IS_ERR(task)) { pr_err("Failed to create and run kernel thread: %ld\n", PTR_ERR(task)); return PTR_ERR(task); // 返回错误码 }
总结:
-
kthread_create()
:用于创建一个新的内核线程,但不会立即启动,必须使用wake_up_process()
来唤醒线程。- 返回值:成功时返回
task_struct *
,失败时返回ERR_PTR
。
- 返回值:成功时返回
-
wake_up_process()
:用于启动由kthread_create()
创建的线程,将其加入调度队列。- 返回值:
1
表示成功唤醒线程,0
表示线程已经在运行。
- 返回值:
-
kthread_run()
:这是一个便捷函数,结合了kthread_create()
和wake_up_process()
的功能,创建并启动线程。- 返回值:成功时返回
task_struct *
,失败时返回ERR_PTR
。
- 返回值:成功时返回
通过使用 kthread_run()
,可以简化内核线程的创建和启动过程,避免显式调用 wake_up_process()
。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
MODULE_LICENSE("GPL");
static struct task_struct *task;
// Implement test function
int func(void *data) {
int time_count = 0;
do {
printk(KERN_INFO "thread_function: %d times\n", ++time_count);
} while (!kthread_should_stop() && time_count < 30);
return time_count;
}
static int __init KT_init(void) {
printk("KT module create kthread start\n");
// Create a kthread
task = kthread_create(&func, NULL, "MyThread");
// Wake up new thread if ok
if (!IS_ERR(task)) {
printk("kthread starts\n");
wake_up_process(task);
}
return 0;
}
static void __exit KT_exit(void) {
printk("KT module exits! \n");
}
module_init(KT_init);
module_exit(KT_exit);
代码解释:
-
模块和内核线程的初始化:
- 包含了必要的内核头文件,如
init.h
、module.h
和kthread.h
,这些头文件提供了内核模块和内核线程相关的函数。 MODULE_LICENSE("GPL");
用于指定模块的许可证,这里是 GPL。
- 包含了必要的内核头文件,如
-
func()
函数:- 这是内核线程的主执行函数,函数
func(void *data)
将被线程在执行时调用。 - 功能:
time_count
是一个计数器,初始值为 0。- 在
do...while
循环中,线程每次循环增加计数并使用printk()
输出当前的计数值。 - 循环条件是
!kthread_should_stop()
并且time_count < 30
。kthread_should_stop()
用于检测线程是否被请求停止。 - 当计数达到 30 或
kthread_should_stop()
返回true
时,循环结束,函数返回计数器的最终值。
- 这是内核线程的主执行函数,函数
-
KT_init()
函数:- 这是模块初始化函数,在模块加载时执行。
- 功能:
printk("KT module create kthread start\n");
用于输出信息,表示模块正在创建一个内核线程。task = kthread_create(&func, NULL, "MyThread");
创建一个内核线程,线程主函数为func()
,线程名称为"MyThread"
。task
保存返回的task_struct
。- 检查
task
是否有效,IS_ERR(task)
用于检查线程创建是否失败。如果成功,则调用wake_up_process(task)
启动线程。 - 线程启动后,内核线程开始执行
func()
中的逻辑。
-
KT_exit()
函数:- 这是模块卸载时执行的函数。
- 功能:
printk("KT module exits! \n");
输出模块退出的提示。- 此函数并没有显式停止内核线程(可以在实际开发中通过
kthread_stop()
来显式停止线程)。
-
模块入口和退出函数:
module_init(KT_init);
指定模块加载时调用KT_init()
。module_exit(KT_exit);
指定模块卸载时调用KT_exit()
。