通过之前几节,我们已经了解了内核线程的创建方法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