linux模块编程(四)——消息的使者list

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

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值