动态加载和异步调用tasklet/workqueue day63 ay64
五:动态加载驱动
1.区别
静态编译进内核:驱动存在于ulmage
中,内核启动时加载驱动
动态加载驱动:驱动独立存在(xxx.ko
),内核启动后动态加载
动态编译只需要make modules
然后在内核insmod
,就不需要reboot重复流程,直接编译就行
2.代码
MODULE_LIENCES("GPL"); //在led4.c最后面加,符合gnu的协议,内核不会“脏”
config LED4tristate "this is led4" //改成三态default y ---help---this is demo_secondTest driver在.config CONFIG_LED4=m make menuconfig 中是 M 状态,改成 M
cp drivers/char/led4.ko ~/nfs/rootfs // make modules -- M全部编译成.ko
cp arch/arm/boot/uImage ~/tftpboot///make uImage
内核中
insmod led4.ko //动态加载驱动模块
mknod /dev/led4 c 253 0
lsmod //查看动态加载的驱动模块
rmmod led4 //卸载动态加载的驱动模块(卸载使用模块名)
1.自带寄存器函数
#define GPIO_LED S3C2410_GPB(5) //引脚GPB5
2.自动创建设备节点次class/device
alloc_chrdev_regionclass_create
device_create //创造设备节点
#include <asm/io.h>
#include <asm/ioctl.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/types.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/device.h>
#include <linux/cdev.h>#define DEV_NAME "led4_alloc"
#define GPIO_LED S3C2410_GPB(5)
#define LED_ON 0
#define LED_OFF 1static void led2_init(void) {gpio_request(GPIO_LED, "led4"); //申请gpio_direction_output(GPIO_LED, LED_OFF);
}static void led2_on(void) { gpio_set_value(GPIO_LED, LED_ON); }static void led2_off(void) { gpio_set_value(GPIO_LED, LED_OFF); }static void led2_deinit(void) {gpio_set_value(GPIO_LED, LED_OFF);gpio_free(GPIO_LED);
}//以下都是函数指针
static int open(struct inode *node, struct file *file) {led2_init();printk("led4 open ...\n"); //内核打印return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len,loff_t *offset) {// copy_to_user();printk("led4 read ...\n");return 0;
}static ssize_t write(struct file *file, const char __user *buf, size_t len,loff_t *offset) {unsigned char data[10] = {0};unsigned int len_cp = (sizeof(data) < len) ? sizeof(data) : len;ssize_t ret = len_cp;// strcmp(buf,"ledon")// copy_to_user 用户访问copy_from_user(data, buf,len_cp); //让代码不要在内核空间运行去访问,如果数据有问题,内核访问野指针,内核崩溃if (!strcmp(data, "ledon"))led2_on();else if (!strcmp(data, "ledoff"))led2_off();elseret = -EINVAL; // EINVAL “-” 返回负,返回非正常值printk("led4 write ...\n");return ret;
}static int close(struct inode *node, struct file *file) {led2_deinit();printk("led4 close ...\n");return 0;
}static dev_t dev_num; //创建设备号
//操作方法
static struct file_operations fops = {//结构体都是函数指针,上面函数指针都已经初始化了,//所以赋给对应的结构体中函数指针// gnu中,不像数据结构(windos)一个一个赋值,可以部分初始化.owner = THIS_MODULE, //指向自己模块,默认.open = open, //.read = read,.write = write,.release = close};static struct cdev cdev; //设备的结构体,//(要把设备号和操作方法放进去),然后在给到内核
static struct class *led_class;
static struct device *led_device;static int __init led_init(void) {int ret = 0;ret = alloc_chrdev_region(&dev_num, 0, 1, DEV_NAME);if (ret < 0) {printk("alloc_chrdev_region failed\n");return ret;}cdev_init(&cdev,&fops); // cdev_init 把操作方法放进cdev结构体ret = cdev_add(&cdev, dev_num,1); //添加几个设备?--和对应设备号放进cdev结构体if (ret < 0) ////判断int类型goto err_cdev;led_class = class_create(THIS_MODULE, "led_class");if (IS_ERR(led_class)) ////判断int类型goto err_class;led_device = device_create(led_class,NULL,dev_num,NULL,DEV_NAME);if (IS_ERR(led_device))goto err_device;printk("led4_init ....\n");return ret;err_class:unregister_chrdev_region(dev_num, 1); //取消注册class_destroy(led_class);printk("register_chrdev_region failed\n");err_device:unregister_chrdev_region(dev_num, 1); device_destroy(led_class, dev_num);printk("register_chrdev_region failed\n");err_cdev:cdev_del(&cdev); //销毁初始化的cdev结构体printk("cdev_add failed\n");return ret;
}static void __exit led_exit(void) {unregister_chrdev_region(dev_num, 1);device_destroy(led_class, dev_num);class_destroy(led_class);cdev_del(&cdev);printk("led4_exit ....\n");
}module_init(led_init); //模块初始化 修饰对应的函数,然后执行对应函数
module_exit(led_exit); //在操作系统注销时和卸载 -- 启动和注销
MMODULE_LICENSE("GPL");
六:misc
杂项设备驱动
#include <linux/major.h>
#include <linux/miscdevice.h>
//不需要#define MAJOR/MINOP NUMstatic struct miscdevice misc =
{.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops
};static int __init led_init(void)
{int ret = misc_register(&misc); //自动分配主设备号10,自动分配次设备号,//其中misc会把之前字符驱动的动作自动完成if(ret < 0)goto err_misc_register;printk("led4_init ....\n");return ret;err_misc_register:misc_deregister(&misc);printk("cmisc_deregister failed\n");return ret;
}static void __exit led_exit(void)
{misc_deregister(&misc);printk("led4_exit ....\n");
}
好处就是自动分配主设备号10,和随机分配次设备号
之后进入内核就不需要在mknod
节点,只需要insmod
就可以
七:字符和杂项驱动总结
1.字符设备驱动模板
#define DEV_NAME “led”dev_t dev_num;struct cdev cdev;struct file_operations fops;led_init(){MKDEV();register_chrdev_region(); alloc_chrdev_register(); // /proc/devices DEV_NAME ,二选一--------------------------------------------------------cdev_init();cdev_add();class_create();device_create(); // /dev/led DEV_NAME}led_exit(){//对应按相反顺序注销
}module_init(led_init);module_exit(led_exit);
2.杂项设备驱动(字符设备)
#define DEV_NAME “led”struct miscdevice misc; // DEV_NAMEled_init(){misc_register(); // -- /dev/led}led_exit(){}module_init(led_init);module_exit(led_exit);
八:ioctl
ioctl
: 实现设置和属性获取方面的功能
dev | NUM | DIR | DATA |
---|---|---|---|
led1 | 0,1,2 | ||
led2 | 0,1,2,3 | ||
led3 | 0,1 |
1.代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>#define DEV_NAME "led4_ioctl"
#define GPIO_LED1 S3C2410_GPB(5)
#define GPIO_LED2 S3C2410_GPB(6)
#define GPIO_LED3 S3C2410_GPB(7)
#define GPIO_LED4 S3C2410_GPB(8)
#define LED_ON 0
#define LED_OFF 1
#define LED_LED 2
#define MAGIC_NUM 'x'#define CMD_LED_ON _IO(MAGIC_NUM, LED_ON)
#define CMD_LED_OFF _IO(MAGIC_NUM, LED_OFF)
#define CMD_LED_LED _IO(MAGIC_NUM, LED_LED)static unsigned long led[4] = {GPIO_LED1, GPIO_LED2, GPIO_LED3, GPIO_LED4};static void led2_init(void) {int i = 0;gpio_request(GPIO_LED1, "led1"); //申请gpio_request(GPIO_LED2, "led2"); //申请gpio_request(GPIO_LED3, "led3"); //申请gpio_request(GPIO_LED4, "led4"); //申请for (i = 0; i < 4; i++) {gpio_direction_output(led[i], LED_OFF);//操作对应引脚}
}static void led2_on(unsigned char num)
{ gpio_set_value(led[num - 1], LED_ON); }static void led2_off(unsigned char num)
{ gpio_set_value(led[num - 1], LED_OFF); }static void led2_deinit(void) {int i = 0;for (i = 0; i < 4; i++) {gpio_set_value(led[i], LED_OFF);gpio_free(led[i]);}
}//以下都是函数指针
static int open(struct inode *node, struct file *file) {led2_init();printk("led4_ioctl open ...\n"); //内核打印return 0;
}static ssize_t read(struct file *file, char __user *buf, size_t len,loff_t *offset) {// copy_to_user();printk("led4 read ...\n");return 0;
}static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{printk("led4 write ...\n");return 0;
}
typedef struct __tag {unsigned char l1;unsigned char l2;unsigned char l3;unsigned char l4;
}led_t;static led_t leda;static long ioctl(struct file *file, unsigned int cmd, unsigned long arg) {int ret = 0;printk("cmd = %d arg = %ld\n", cmd, arg);switch (cmd) {case CMD_LED_ON:led2_on(arg);break;case CMD_LED_OFF:led2_off(arg);break;case CMD_LED_LED:copy_from_user(&leda, (led_t *)arg, sizeof(led_t));break;default:ret = -EINVAL;break;}return ret;
}static int close(struct inode *node, struct file *file) {led2_deinit();printk("led4 close ...\n");return 0;
}//操作方法
static struct file_operations fops = {//结构体都是函数指针,上面函数指针都已经初始化了,//所以赋给对应的结构体中函数指针// gnu中,不像数据结构(windos)一个一个赋值,可以部分初始化.owner = THIS_MODULE, //指向自己模块,默认.open = open, //.read = read,.write = write,.unlocked_ioctl = ioctl, //操作方法链接ioctl.release = close};static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR, .name = DEV_NAME, .fops = &fops};static int __init led_init(void) {int ret = misc_register(&misc); //自动分配主设备号10,自动分配次设备号//其中misc会把之前字符驱动的动作自动完成if (ret < 0)goto err_misc_register;printk("led4_ioctl_init ....\n");return ret;err_misc_register:misc_deregister(&misc);printk("cmisc_deregister failed\n");return ret;
}static void __exit led_exit(void) {misc_deregister(&misc);printk("led4_exit ....\n");
}module_init(led_init); //模块初始化 修饰对应的函数,然后执行对应函数
module_exit(led_exit); //在操作系统注销时和卸载 -- 启动和注销
MODULE_LICENSE("GPL");
/
1.1 _IO宏
#define _IOC(dir,type,nr,size) \((unsigned int) \(((dir) << _IOC_DIRSHIFT) | \((type) << _IOC_TYPESHIFT) | \((nr) << _IOC_NRSHIFT) | \((size) << _IOC_SIZESHIFT)))
//左移xx位,#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)//读
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))//写
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))//读写
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
2.配套配置文件
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>#define LED_ON 0
#define LED_OFF 1
#define LED_LED 2
#define MAGIC_NUM 'x'
#define CMD_LED_ON _IO(MAGIC_NUM, LED_ON)
#define CMD_LED_OFF _IO(MAGIC_NUM, LED_OFF)
#define CMD_LED_LED _IO(MAGIC_NUM, LED_LED)typedef struct __tag
{unsigned char l1;unsigned char l2;unsigned char l3;unsigned char l4;
}led_t;static led_t led;int main(int argc, const char *argv[])
{int fd = open("/dev/led4_alloc",O_RDWR); ////打开对应内核文件if(fd < 0){perror("open demo failed");}unsigned char buf[20] = {0};int ret = 0;
#if 1while(1){//ioctl(fd,1,1); //置电平1,led1ioctl(fd,CMD_LED_ON,4);sleep(1);ioctl(fd,CMD_LED_OFF,4);sleep(1);led.l1 = 0;led.l2 = 1;ret = ioctl(fd,CMD_LED_LED,&led);//CMD_LED_LED 为2 系统忽律,//要改在vim Documentation/ioctl/ioctl-number.txt printf("ret = %d\n",ret);sleep(1);}
#endif#if 0while(1){write(fd,"ledon",strlen("ledon"));sleep(1);write(fd,"ledoff",strlen("ledoff"));sleep(1);}
#endifclose(fd);return 0;
}
3.文档
This table lists ioctls visible from user land for Linux/i386. It contains
most drivers up to 2.3.14, but I know I am missing some.Code Seq# Include File Comments
========================================================
0x00 00-1F linux/fs.h conflict!
0x00 00-1F scsi/scsi_ioctl.h conflict!
0x00 00-1F linux/fb.h conflict!
0x00 00-1F linux/wavefront.h conflict!
0x02 all linux/fd.h
0x03 all linux/hdreg.h
0x04 D2-DC linux/umsdos_fs.h Dead since 2.6.11, but don't reuse these.
0x06 all linux/lp.h
0x09 all linux/md.h
0x12 all linux/fs.h如果修改 eg: 'x' 00-1f 对应文件位置//尽量不修改
九:irq_key
1 .inline
内联函数
函数体原地展开
2. waitevent
等待队列
static wait_queue_head_t wq;
static int condition;condition = 1;
wake_up_interruptible(&wq);wait_event_interruptible(wq,condition);ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);if(ret <0)goto err_request_irq;init_waitqueue_head(&wq);printk("key4_init ....\n");return ret;err_request_irq:disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);printk("request_irq failed\n");return ret;
2.1:代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>#define DEV_NAME "key_irq"
static wait_queue_head_t wq;
static int condition;irqreturn_t eint8_handler(int irq_num,void * dev)
{unsigned int arg = *(unsigned int *)dev;if(100 != arg){return IRQ_NONE;}condition = 1;wake_up_interruptible(&wq);printk("irq_num = %d dev = %d\n",irq_num,arg);return IRQ_HANDLED;
}static void key2_init(void)
{
}static void key2_deinit(void)
{
}static int open(struct inode * node, struct file * file)
{key2_init();printk("key4 open ...\n");return 0;
}static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{//copy_to_user();printk("key4 read start\n");condition = 0;wait_event_interruptible(wq,condition);printk("key4 read end...\n");return 0;
}static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{printk("key4 write ...\n");return 0;
}static int close(struct inode * node, struct file * file)
{key2_deinit();printk("key4 close ...\n");return 0;
}static struct file_operations fops =
{.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close
};static struct miscdevice misc =
{.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops
};static unsigned int arg = 100;static int __init key1_init(void)
{int ret = misc_register(&misc);if(ret < 0)goto err_misc_register;ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);if(ret <0)goto err_request_irq;init_waitqueue_head(&wq);printk("key4_init ....\n");return ret;err_request_irq:disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);printk("request_irq failed\n");return ret;err_misc_register:misc_deregister(&misc);printk("misc_register faikey\n");return ret;
}static void __exit key1_exit(void)
{disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);misc_deregister(&misc);printk("key4_exit ....\n");
}module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
2.2完整流程
1. 配套关系在 Linux 内核里,等待/唤醒机制是典型的 进程同步机制,核心元素是:等待队列(wait queue)static wait_queue_head_t wq;
init_waitqueue_head(&wq);它是一个链表,里面挂着所有等待某个条件的进程(task_struct)。用于管理阻塞的进程。条件变量(condition)static int condition;用来表示某个事件是否发生。wait_event_interruptible() 会检查它,如果为假就让进程睡眠,否则直接返回。阻塞等待函数wait_event_interruptible(wq, condition);宏会判断 condition 是否为真。如果为假,就把调用进程放入 wq 队列,然后进入 可中断睡眠(TASK_INTERRUPTIBLE)。当唤醒函数触发时,进程会被唤醒,再次检查 condition。唤醒函数wake_up_interruptible(&wq);将 wq 队列里的所有可中断睡眠进程唤醒。唤醒后的进程重新调度,继续执行 wait_event_interruptible() 后面的代码。2. 结合你的代码流程假设用户进程执行了 read():condition = 0;
wait_event_interruptible(wq, condition);流程拆解:进程检查条件condition == 0 → 条件不满足。进程进入睡眠进程状态变为 TASK_INTERRUPTIBLE。内核把进程添加到 wq 队列里。CPU 会调度其它可运行进程,当前进程阻塞。中断触发用户按键按下 → 外部中断触发 → 调用 eint8_handler()。中断服务函数处理condition = 1;
wake_up_interruptible(&wq);设置条件为真(condition = 1)唤醒等待队列中的所有进程这些进程状态从 TASK_INTERRUPTIBLE → TASK_RUNNING等待调度器分配 CPU 运行进程被调度回 CPUwait_event_interruptible() 重新检查条件condition == 1 → 条件满足 → 返回进程继续执行 read() 后面的代码printk("key4 read end...\n");3. wait_event / wake_up 配套总结
元素 作用
wait_queue_head_t wq 管理所有等待该事件的进程
condition 条件变量,表示事件是否发生
wait_event_interruptible(wq, condition) 阻塞调用进程直到条件成立
wake_up_interruptible(&wq) 唤醒等待队列里的所有进程,重新检查条件关键点:wait_event 宏里会不断检查条件,直到满足才返回 → 避免“虚假唤醒”问题。wake_up_interruptible 只是将等待队列中的进程标记为可运行,实际调度由内核决定。💡 总结一句话:wait_event 是“睡觉等事件”,wake_up 是“事件来了叫醒睡着的进程”,二者配套实现了 中断到进程的异步通知机制。
3.中断底半部三种方式
上半部(Top Half)
→ 就是中断服务程序 (ISR)。
→ 要求执行尽量快,只做“紧急”的事,比如:
- 读取寄存器清中断
- 保存必要的数据到缓存
- 通知内核有事件发生
下半部(Bottom Half)
→ 处理耗时的部分,比如:
- 数据拷贝、复杂计算
- 和用户空间交互
//下半部3种实现方式
1.软中断
2.tasklet //基于软中断实现一种软中断
3.workqueue
4.异步调用
4.1 tasklet
Tasklet = 一种基于 Softirq 的轻量级下半部机制,不能睡眠
在同一个 CPU 上,同一个 tasklet 永远不会并行执行(保证了串行化)
串行化保证
- 内核保证 同一个 tasklet 在同一个 CPU 上不会并行执行。
- 如果某个 tasklet 正在跑,调度器不会在同一时刻再次运行它。
- 所以你可以放心在 tasklet 函数里操作它自己的数据,不用加锁。
- 这一点确实有点像“隐式的互斥”。
避免数据竞争
- 因为 tasklet 是单线程化的,使用它处理某个设备的中断数据,可以避免并发访问导致数据出错。
4.2 代码
static wait_queue_head_t wq;
static int condition;
static struct tasklet_struct task;static void task_func(unsigned long arg)
{ssleep(1);condition = 1;wake_up_interruptible(&wq);printk("task_fun arg = %ld\n",arg);
}static irqreturn_t eint8_handler(int irq_num,void * dev)
{unsigned int arg = *(unsigned int *)dev;if(100 != arg){return IRQ_NONE;}tasklet_schedule(&task);printk("irq_num = %d dev = %d\n",irq_num,arg);return IRQ_HANDLED;
}static int __init key1_init(void)
{int ret = misc_register(&misc);if(ret < 0)goto err_misc_register;ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);if(ret <0)goto err_request_irq;init_waitqueue_head(&wq);tasklet_init(&task,task_func,200);printk("key4_init ....\n");return ret;err_request_irq:disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);printk("request_irq failed\n");return ret;err_misc_register:misc_deregister(&misc);printk("misc_register faikey\n");return ret;
}
//完整代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>#define DEV_NAME "key_irq"
static wait_queue_head_t wq;
static int condition;
static struct tasklet_struct task;static void task_func(unsigned long arg)
{ssleep(1);condition = 1;wake_up_interruptible(&wq);printk("task_fun arg = %ld\n",arg);
}static irqreturn_t eint8_handler(int irq_num,void * dev)
{unsigned int arg = *(unsigned int *)dev;if(100 != arg){return IRQ_NONE;}tasklet_schedule(&task);printk("irq_num = %d dev = %d\n",irq_num,arg);return IRQ_HANDLED;
}static void key2_init(void)
{
}static void key2_deinit(void)
{
}static int open(struct inode * node, struct file * file)
{key2_init();printk("key4 open ...\n");return 0;
}static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{//copy_to_user();printk("key4 read start\n");condition = 0;wait_event_interruptible(wq,condition);printk("key4 read end...\n");return 0;
}static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{printk("key4 write ...\n");return 0;
}static int close(struct inode * node, struct file * file)
{key2_deinit();printk("key4 close ...\n");return 0;
}static struct file_operations fops =
{.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close
};static struct miscdevice misc =
{.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops
};static unsigned int arg = 100;static int __init key1_init(void)
{int ret = misc_register(&misc);if(ret < 0)goto err_misc_register;ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);if(ret <0)goto err_request_irq;init_waitqueue_head(&wq);tasklet_init(&task,task_func,200);printk("key4_init ....\n");return ret;err_request_irq:disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);printk("request_irq failed\n");return ret;err_misc_register:misc_deregister(&misc);printk("misc_register faikey\n");return ret;
}static void __exit key1_exit(void)
{disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);misc_deregister(&misc);printk("key4_exit ....\n");
}module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
4.3 workqueue
Tasklet 虽然能把工作延后,但它仍然运行在 中断上下文:
- 不能睡眠
- 不能调度别的进程
- 不能调用可能阻塞的函数(例如
kmalloc(GFP_KERNEL)
,copy_to_user
,mutex_lock
) - 如果我们在下半部需要做一些 耗时或可能阻塞的工作(比如磁盘 IO,文件操作,访问用户空间),就必须要有一个能在 进程上下文 运行的机制(Workqueue)。
Workqueue 的特点
- 运行在内核线程(kworker)里,属于 进程上下文。
- 可以睡眠(这是和 tasklet 的最大区别)。
- 内核会维护一组 kworker 线程,专门执行 work。
4.4 代码
static wait_queue_head_t wq;
static int condition = 0;
static struct work_struct work;static void work_func(unsigned long arg)
{ssleep(1);condition = 1;wake_up_interruptible(&wq);printk("task_fun arg = %ld\n",arg);
}static irqreturn_t eint8_handler(int irq_num,void * dev)
{unsigned int arg = *(unsigned int *)dev;if(100 != arg){return IRQ_NONE;}schedule_work(&work);printk("irq_num = %d dev = %d\n",irq_num,arg);return IRQ_HANDLED;
}init_waitqueue_head(&wq);INIT_WORK(&work,work_func);
完整代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <mach/gpio-nrs.h>
#include <mach/gpio.h>
#include <linux/miscdevice.h>
#include <asm/ioctl.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/workqueue.h>
#include <linux/delay.h>#define DEV_NAME "key"
static wait_queue_head_t wq;
static int condition = 0;
static struct work_struct work;static void work_func(unsigned long arg)
{ssleep(1);condition = 1;wake_up_interruptible(&wq);printk("task_fun arg = %ld\n",arg);
}static irqreturn_t eint8_handler(int irq_num,void * dev)
{unsigned int arg = *(unsigned int *)dev;if(100 != arg){return IRQ_NONE;}schedule_work(&work);printk("irq_num = %d dev = %d\n",irq_num,arg);return IRQ_HANDLED;
}static void key2_init(void)
{
}static void key2_deinit(void)
{
}static int open(struct inode * node, struct file * file)
{key2_init();printk("key4_work open ...\n");return 0;
}static ssize_t read(struct file * file, char __user * buf, size_t len, loff_t * offset)
{//copy_to_user();printk("key4 read start\n");condition = 0;wait_event_interruptible(wq,condition);printk("key4 read end...\n");return 0;
}static ssize_t write(struct file * file, const char __user * buf, size_t len, loff_t * offset)
{printk("key4 write ...\n");return 0;
}static int close(struct inode * node, struct file * file)
{key2_deinit();printk("key4 close ...\n");return 0;
}static struct file_operations fops =
{.owner = THIS_MODULE,.open = open,.read = read,.write = write,.release = close
};static struct miscdevice misc =
{.minor = MISC_DYNAMIC_MINOR,.name = DEV_NAME,.fops = &fops
};static unsigned int arg = 100;static int __init key1_init(void)
{int ret = misc_register(&misc);if(ret < 0)goto err_misc_register;ret = request_irq(IRQ_EINT8, eint8_handler, IRQF_TRIGGER_FALLING, "irq_eint8", &arg);if(ret <0)goto err_request_irq;init_waitqueue_head(&wq);INIT_WORK(&work,work_func);printk("key4_init ....\n");return ret;err_request_irq:disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);printk("request_irq failed\n");return ret;err_misc_register:misc_deregister(&misc);printk("misc_register faikey\n");return ret;
}static void __exit key1_exit(void)
{disable_irq(IRQ_EINT8);free_irq(IRQ_EINT8,&arg);misc_deregister(&misc);printk("key4_exit ....\n");
}module_init(key1_init);
module_exit(key1_exit);
MODULE_LICENSE("GPL");
4.5两者区别
特性 | Tasklet | Workqueue | |
---|---|---|---|
执行上下文 | 软中断(不可睡眠) | 内核线程(可睡眠) | |
互斥性 | 同一 tasklet 永远不会并行 | 同一个 work 不会并行,不同 work 可并行 | |
并发性 | 多个 tasklet 可在不同 CPU 并发 | 多个 work 可在多个 worker 线程并发 | |
调度粒度 | 基于 softirq,立即调度 | 基于调度器,可延迟 | |
灵活性 | 简单、轻量 | 丰富(延迟、优先级、CPU 绑定) | |
典型用途 | 中断下半部、快速轻量任务 | 复杂/耗时任务,需要睡眠的场景 | |
上下文 | 软中断(softirq)上下文,属于 中断上下文 | 运行在内核线程(kworker)上下文,属于 进程上下文 |