通过之前几节,我们已经了解了内核线程的创建方法kthread,内核同步的工具completion。现在我们就来学学内核线程传递消息的方法list。或许大家会说,list不是链表吗。不错,list是链表,但它可以变成承担消息传递的消息队列。消息的发送者把消息放到链表上,并通过同步工具(如completion)通知接收线程,接收线程再从链表上取回消息,就这么简单。linux内核或许没有给我们定制好的东西,但却给了我们可随意变换的、基础的工具,把这些工具稍加组合就能完成复杂的功能。list又是这些万能工具中最常用的。
前面两篇文章的惯例是先对新增的功能做出介绍,并解释要用到的API。但我感觉这种既要解释原理,又要分析代码,又要写应用样例的十全文章,写起来实在吃力,而且漏洞百出。与其如此,我还不如把两部分分开,这里的模块编程就专心设计模块,编写内核API的组合使用样例;而原理介绍、API代码分析的部分,会转到linux内核部件分析的部分。这样我一方面能安心设计样例,一方面能把API介绍地更全面一些。
模块设计
本模块目的是展示list作为消息队列使用时的情况。所以会创建一个全局链表work_list,定义一种消息的结构struct work_event,并创建两个内核线程work_thread和watchdog_thread。work_thread是消息的接收者,它循环检查work_list,如果其上有消息,就将其取出并执行,否则阻塞。watchdog_thread是消息的发送者,它周期性地发送消息到work_list,并唤醒work_thread。
模块实现
1、建立list子目录。
2、编写list.c,使其内容如下。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/completion.h>
#include <linux/kthread.h>
MODULE_LICENSE("Dual BSD/GPL");
static struct task_struct *work_tsk;
static struct task_struct *watchdog_tsk;
static DECLARE_SPINLOCK(work_list_lock);
static LIST_HEAD(work_list);
static DECLARE_COMPLETION(work_wait);
enum WORK_EVENT_TYPE {
EVENT_TIMER,
EVENT_EXIT
};
struct work_event {
enum WORK_EVENT_TYPE type;
int need_free;
list_head list;
};
static int work_thread(void *data)
{
int count = 0;
while(1){
if(list_empty(&work_list))
wait_for_completion(&work_wait);
spin_lock(&work_list_lock);
while(!list_empty(&work_list)){
struct work_event *event;
event = list_entry(work_list.next, struct work_event, list);
list_del(&event->list);
spin_unlock(&work_list_lock);
if(event->type == EVENT_TIMER){
printk(KERN_INFO "event timer: count = %d\n", ++count);
}
else if (event->type == EVENT_EXIT){
if(event->need_free)
kfree(event);
goto exit;
}
if(event->need_free)
kfree(event);
spin_lock(&work_list_lock);
}
}
exit:
return count;
}
static int watchdog_thread(void *data)
{
int count = 0;
while(!kthread_should_stop()){
msleep(1000);
count++;
if(count%5 == 0){
struct work_event *event;
event = kmalloc(sizeof(struct work_event), GFP_KERNEL);
if(event == NULL){
printk(KERN_INFO "watchdog_thread: kmalloc failed!\n");
break;
}
event->type = EVENT_TIMER;
event->need_free = 1;
spin_lock(&work_list_lock);
list_add_tail(&event->list, &work_list);
spin_unlock(&work_list_lock);
complete(&work_wait);
}
}
return count;
}
static int list_init()
{
printk(KERN_INFO "list_init()\n");
watchdog_tsk = kthread_run(watchdog_thread, NULL, "watchdog_thread");
if(IS_ERR(watchdog_tsk))
goto err1;
work_tsk = kthread_run(work_thread, NULL, "work_thread");
if(IS_ERR(work_tsk))
goto err2;
return 0;
err2:
kthread_stop(watchdog_tsk);
err1:
return 1;
}
static void list_exit()
{
printk(KERN_INFO "list_exit()\n");
if(!IS_ERR(watchdog_tsk)){
int count = kthread_stop(watchdog_tsk);
printk(KERN_INFO "watchdog_thread: running for %ss\n", count);
}
if(!IS_ERR(work_tsk)){
get_task_struct(&work_tsk);
struct work_event event;
event.type = EVENT_EXIT;
event.need_free = 0;
spin_lock(&work_list_lock);
list_add(&event.list, &work_list);
spin_unlock(&work_list_lock);
complete(&work_wait);
int count = kthread_stop(work_tsk);
printk(KERN_INFO "work_thread: period 5s, running %d times\n", count);
}
}
module_init(list_init);
module_exit(list_exit);
整个模块较为简单,work_thread只是接收work_list中的消息并处理,所以在list_exit退出时也要给它发EVENT_EXIT类型的消息,使其退出。至于在list_exit发消息之前调用的get_task_struct,实在是无奈之举。因为我们发送EVENT_EXIT消息后work_thread会在kthread_stop调用前就马上结束,导致之后的kthread_stop错误。所以要先get_task_struct防止work_thread退出后释放任务结构中的某些内容,虽然有对应的put_task_struct,但我们并未使用,因为put_task_struct并未引出到符号表。当然,这种情况只是一时之举,等我们学习了更强大的线程同步机制,或者更灵活的线程管理方法,就能把代码改得更流畅。
注意到代码中使用spin_lock/spin_unlock来保护work_list。如果之前不了解spin_lock,很容易认为它不足以保护。实际上spin_lock不止是使用自旋锁,在此之前还调用了preempt_disable来禁止本cpu任务调度。只要同步行为只发生在线程之间,spin_lock就足以保护,如果有中断或者软中断参与进来,就需要用spin_lock_irqsave了。
3、 编译运行模块。
可能本节介绍的list显得过于质朴,但只有简单的东西才能长久保留。不久之后,我们或许不再用kthread,不再用completion,但list一定会一直用下去。
附录
因为内核API介绍被移到了独立的文章中,所以这里的附录终于名副其实。可能一节中会用到不只一个部件分析的内容。我会把文中主要想使用的部件对应的文章放在首位,其余参考文章,如果有的话,再在下面一一列出。
linux内核部件分析(一)连通世界的list