Linux内核线程详解

概要

Linux 的内核线程实际上是只存在于内核空间的一个进程。内核通常创建内核线程, 让它在后台周期性的处理一些事务。内核线程和普通进程一样可调度,可被抢先。他们的最显著的区别是内核线程的进程描述结构体 task_struct 的 mm 字段为 NULL。而一般进程的进程描述结构体的 mm 字段指向该进程的地址空间。因为内核线程永远只运行在内核态,永远不必切换至用户空间,并且所有用户态进程的地址空间的内核虚拟地址部分都是一样的,所以当处理器调度到内核进程时,内核进程可以随便使用某个用户态进程的地址空间的内核虚拟地址部分。Linux 内核线程的作法是借用上一个普通用户态进程的用户空间。

实现

内核线程由内核 API 函数kthread_create()创建,也可由kthread_run()创建。他们的区别是前者创建的是一个处于非运行状态的内核线程,需要使wake_up_process()把它转换为可运行状态;而kthread_run()创建的内核线程立即处于可运行状态,随时可能被调度而获得运行的机会。内核线程开始后会一直运行,直到它显式地调用 do_exit()或者其它内核代码调用 kthread_stop()kthread_stop()函数需要传入先前创建内核线程函数返回的 task_struct 作为参数。该函数在调用后会一直阻塞,直到等待的内核线程完全退出了才返回。

下面给出示例代码:

kernel_thread.c

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
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/slab.h>
#ifndef SLEEP_MILLI_SEC
#define SLEEP_MILLI_SEC(nMilliSec)\
do { \
long timeout = (nMilliSec) * HZ / 1000; \
while(timeout > 0) \
{ \
timeout = schedule_timeout(timeout); \
} \
}while(0);
#endif
static struct task_struct * MyThread = NULL;
static int MyPrintk(void *data)
{
char *mydata = kmalloc(strlen(data)+1,GFP_KERNEL);
memset(mydata,'\0',strlen(data)+1);
strncpy(mydata,data,strlen(data));
while(!kthread_should_stop())
{
SLEEP_MILLI_SEC(1000);
printk("%s\n",mydata);
}
kfree(mydata);
return 0;
}
static int __init init_kthread(void)
{
MyThread = kthread_run(MyPrintk,"hello world","mythread");
return 0;
}
static void __exit exit_kthread(void)
{
if(MyThread){
printk("stop MyThread\n");
kthread_stop(MyThread);
}
}
module_init(init_kthread);
module_exit(exit_kthread);

makefile

1
2
3
4
5
obj-m += kernel_thread.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

sudo insmod kernel_thread.ko
插入模块后,利用ps -ef查看,结果如下:

可见mythread内核线程已经成功运行。

当卸载模块的时候,内核线程也终止。
sudo rmmod kernel_thread


参考资料:

  1. 《Linux内核设计与实现》
  2. Linux kernel thread