内核线程
有时候我们需要有个程序来监听和处理某些指定事件,此时经常会做个服务,让其在后台执行,这在应用程序这样的用户态很经常用到,而在Linux Kernel里也会有类似的经历,此时同样可以使用线程来实现,不过它叫内核线程,只能在内核中由其他的线程来创建,而所有的内核线程由kthreadd创建,故而使用ps -ef命令看到所有被[]括起来的内核线程(守护进程)对应的PPID均为2。内核线程与普通用户态线程除了内核线程没有独立地址空间(其mm成员指向NULL)外,其他的可被调度和被抢占均支持。我们还是以实践为主,下面是这次会使用到跟内核线程相关的API函数和宏,在include/linux/kthread.h文件中均有定义:
1.kthread_create(),创建内核线程
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, -1, namefmt, ##arg)
2.kthread_run(),创建并运行内核线程
#define kthread_run(threadfn, data, namefmt, ...) \
({ \
struct task_struct *__k \
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \
if (!IS_ERR(__k)) \
wake_up_process(__k); \
__k; \
})
看到与kthread_create()的区别了吧,多了个wake_up_process()操作。
3.kthread_stop(),停止指定内核线程
int kthread_stop(struct task_struct *k);
4.kthread_should_stop(),线程该停止吗?
bool kthread_should_stop(void);
5.kthread_bind(),绑定创建好的线程在执行CPU核心上运行
void kthread_bind(struct task_struct *k, unsigned int cpu);
也可在创建的时候调用如下函数在创建的同时一起绑定:
struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data),
void *data,
unsigned int cpu,
const char *namefmt);
两者在使用后需要调用kthread_run()里用到的wake_up_process()才能进入运行队列。
从上面的API看到,关键人物是struct task_struct,其在include/linux/sched.h文件中定义。
至此,相关需使用的API已简略说明,而作为线程,对编程来说只是一个函数的实现,该函数一般作为死循环处理,当然在该循环中会进行判断,确认该线程是不是该停止退出了,如果该退出则做相应的处理,如果不退出则继续处理。而作为内核线程,要能主动让出CPU去运行其他线程也必须能重新被调度,这需要调用schedule函数等相关方式,schedule相关函数在kernel/timer.c文件中有定义:
1.schedule_timeout(),让CPU调度运行其他线程并等待指定时间后本线程被重新调度,其不改变当前状态
signed long __sched schedule_timeout(signed long timeout) ;
2.schedule_timeout_interruptible(),其会改变当前状态为可被中断
signed long __sched schedule_timeout_interruptible(signed long timeout) ;
其相当于sleep操作,好了,下面是重头戏了,看看源代码是如何使用这些API的吧:
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/slab.h>
static struct task_struct * slam_unbind_thread = NULL;
static int slam_bind_func(void *data)
{
unsigned int cur_cpu = *((unsigned int *)data);
printk("[slam_bind_thread/%d] start!\n", cur_cpu);
while(!kthread_should_stop())
{
printk("I'm in [slam_bind_thread/%d]!\n", cur_cpu);
schedule_timeout(msecs_to_jiffies(5000));
}
printk("[slam_bind_thread/%d] end!\n", cur_cpu);
return 0;
}
static int slam_unbind_func(void *data)
{
char *slam_data = kzalloc(strlen(data)+1, GFP_KERNEL);
strncpy(slam_data, data, strlen(data));
while(!kthread_should_stop())
{
printk("Unbind Thread:%s(%ld)\n", slam_data, jiffies);
schedule_timeout(msecs_to_jiffies(5000));
}
kfree(slam_data);
return 0;
}
static __init int kthread_example_init(void)
{
int cur_cpu;
unsigned int cpus = num_online_cpus();
unsigned int bind_thread_params[cpus];
struct task_struct *slam_bind_threads[cpus];
slam_unbind_thread = kthread_run(slam_unbind_func, "slam-xinu", "slam_unbind");
for_each_present_cpu(cur_cpu)
{
bind_thread_params[cur_cpu] = cur_cpu;
slam_bind_threads[cur_cpu] = kthread_create(slam_bind_func, (void *)(bind_thread_params+cur_cpu), "BindThread/%d", cur_cpu);
kthread_bind(slam_bind_threads[cur_cpu], cur_cpu);
wake_up_process(slam_bind_threads[cur_cpu]);
}
schedule_timeout_interruptible(msecs_to_jiffies(30*1000));
for(cur_cpu=0;cur_cpu<cpus;cur_cpu++)
{
kthread_stop(slam_bind_threads[cur_cpu]);
}
return 0;
}
static __exit void kthread_example_exit(void)
{
if(slam_unbind_thread)
{
kthread_stop(slam_unbind_thread);
}
}
module_init(kthread_example_init);
module_exit(kthread_example_exit);
相应的Makefile文件内容如下:
obj-m += kthread_example.o
CUR_PATH:=$(shell pwd)
LINUX_KERNEL_PATH:=/home/xinu/linux-3.13.6
all:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) modules
clean:
make -C $(LINUX_KERNEL_PATH) M=$(CUR_PATH) clean
对应的源码文件目录树如下:
/home/xinu/xinu/linux_kernel_driver_l1/kthread_example/
├── kthread_example.c
└── Makefile
该驱动测试验证了内核线程,实现了一个标准的未绑定指定CPU的线程,还有多个绑定指定CPU核心的线程。其中,绑定的多个在模块加载30秒内正常运行,模块等完这30秒就停掉这些线程,此时insmod也不再卡住,回到命令行状态,而未绑定的线程仍继续运行,直到rmmod停止。
参考网址:
http://blog.chinaunix.net/uid-28693738-id-4137973.html
http://www.bluezd.info/archives/kernel-thread-%E7%9A%84%E5%88%9B%E5%BB%BA
http://www.cnblogs.com/zhuyp1015/archive/2012/06/11/2545624.html