文章目录
一、实验目的
掌握字符设备驱动程序中Tasklet和工作队列的编写和调用方法。
分析对比Tasklet和工作队列的差异。
二、实验环境
ubuntu 12.04 内核3.2.14
三、实验内容及实验原理
写一个简单的驱动程序,要求:
定义一个Tasklet和一个工作队列,实现打印输出;
定义两个定时器,定时器周期分别为T1和T2;
T1周期到时调度Tasklet,T2周期到时调度工作队列;
在加载驱动模块时注册Tasklet和工作队列
在卸载驱动模块时销毁Tasklet和工作队列
四、实验结果及其分析
1.编译模块(设备驱动程序)
(1)创建模块文件xxx.c
gedit rwbuf.c
// 模块
#include <linux/module.h>
// 内核
#include <linux/kernel.h>
// struct file_operations
#include <linux/fs.h>
// 中断
#include <linux/interrupt.h>
// 调度
#include <linux/sched.h>
// 定时器
#include <linux/timer.h>
#include <linux/time.h>
// 工作队列
#include <linux/workqueue.h>
// 原子操作
#include <asm/atomic.h>
atomic_t tasklet_run_time;
atomic_t wq_run_time;
static unsigned int t_failed_cnt = 0;
static unsigned int w_failed_cnt = 0;
typedef struct timer_data{
struct timer_list timer;
unsigned int loops;
}*timer_data_ptr;
// tasklet的定时器
struct timer_data timer_T;
// 工作队列的定时器
struct timer_data timer_W;
// tasklet要做什么
void timer_T_fun(unsigned long arg);
// 工作队列要做什么
void timer_W_fun(unsigned long arg);
// tasklet_struct结构体
static struct tasklet_struct my_tasklet;
static void do_work(struct work_struct *work);
// 工作队列静态创建任务
static DECLARE_WORK(workq, do_work);
// 工作队列结构体
static struct workqueue_struct *wqueue;
// tasklet处理程序
static void tasklet_handler(unsigned long data)
{
atomic_inc(&tasklet_run_time);
printk("[tasklet_handler-run_time] %u\n",atomic_read(&tasklet_run_time));
printk("[tasklet_handler-t_failed_cnt] %u\n",t_failed_cnt);
}
void do_work(struct work_struct *work)
{
atomic_inc(&wq_run_time);
printk("[workqueue-run_time] %u\n",atomic_read(&wq_run_time));
printk("[workqueue-w_failed_cnt] %u\n",w_failed_cnt);
}
// tasklet要做什么
void timer_T_fun(unsigned long arg)
{
timer_data_ptr data = (timer_data_ptr)arg;
data->timer.expires+=2*HZ;
add_timer(&data->timer);
// 调度
tasklet_schedule(&my_tasklet);
data->loops++;
printk("[timer_T_fun] loops=%u.\n",data->loops);
}
// 工作队列要做什么
void timer_W_fun(unsigned long arg)
{
timer_data_ptr data =(timer_data_ptr)arg;
data->timer.expires+=3*HZ;
add_timer(&data->timer);
// 提交工作给一个工作队列
queue_work(wqueue,&workq);
data->loops++;
printk("[timer_w_fun] loops=%u.\n",data->loops);
}
// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init isr_init(void)
{
printk("[isr_init-start-jiffies] %lu\n", jiffies);
// 初始化定时器
init_timer(&(timer_T.timer));
timer_T.timer.expires=jiffies+2*HZ;
timer_T.timer.data = (unsigned long)(&timer_T);
timer_T.timer.function=timer_T_fun;
add_timer(&(timer_T.timer));
// 初始化定时器
init_timer(&(timer_W.timer));
timer_W.timer.expires=jiffies+3*HZ;
timer_W.timer.data = (unsigned long)(&timer_W);
timer_W.timer.function=timer_W_fun;
add_timer(&(timer_W.timer));
// tasklet初始化
tasklet_init(&my_tasklet, tasklet_handler, 0);
// 工作队列初始化,只有单个工作者线程
wqueue=create_singlethread_workqueue("wq_test");
printk("[isr_init-success]\n");
printk("[isr_init-end-jiffies] %lu\n", jiffies);
return 0;
}
// module_exit()内的退出函数。
static void __exit isr_exit(void)
{
// 删除tasklet定时器
del_timer(&(timer_T.timer));
// 删除workqueue定时器
del_timer(&(timer_W.timer));
// 从挂起的队列中去掉我们的tasklet
tasklet_kill(&my_tasklet);
// 销毁我们的workqueue
destroy_workqueue(wqueue);
printk("[isr_exit-success]\n");
}
// 内核模块入口,相当于main()函数,完成模块初始化
module_init(isr_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(isr_exit);
// GPL协议证书
MODULE_LICENSE("GPL");
(2)Makefile
gedit Makefile
内容:
obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order Module.symvers
(3)编译
sudo make
2.插入内核模块(加载设备驱动程序)
先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西
sudo dmesg -c
插入内核模块(加载设备驱动程序)
sudo insmod rwbuf.ko
查看是否成功
dmesg
五、心得体会与建议
感觉做实验的体会还是挺直接的。比如:
如果按照一般函数的话,就直接是先执行tasklet_handler中的部分,而tasklet是延迟作用的,
所以执行结果中timer_T_fun中先执行完再去tasklet_handler中的部分。