文章目录
- 一、概念
- 二、tasklet底半部机制
- (一)简介
- (二)tasklet的API接口
- (三)tasklet底半部处理函数
- 三、工作队列
- (一)简介
- (二)API
- (三)使用示例
一、概念
在中断顶半部处理函数中只能做简短的不耗时的操作,但有的时候有希望在中断到来的时候做尽可能多的事情,所以两者就产生的矛盾,内核为了解决这一矛盾设计了中断底半部机制。
在中断顶半部处理函数中只能做简短的不耗时的操作,处理的是紧急的、不耗时的任务;中断底半部处理的是不紧急的、耗时的任务
中断底半部机制一共有三种:
软中断,tasklet,工作队列
- 注:软中断有个数限制(32个),一般不在驱动中使用
二、tasklet底半部机制
(一)简介
基于软中断实现的,没有个数限制,因为它内部是通过链表实现的。
tasklet是中断的一个部分,不能够脱离中断顶半部单独执行,在中断顶半部执行即将结束时,可以开启tasklet底半部机制。
tasklet是工作在中断上下文的,tasklet的底半部处理函数中只能做耗时任务或者短延时任务,但不能做休眠工作
(底半部优先级高于进程,他是工作在中断上下文的)
- 注:ARM架构中,中断不允许套中断
- tasklet绝不能做休眠动作,内核会崩溃(表现为乱码刷屏,不停止)
(二)tasklet的API接口
1. 分配对象
struct tasklet_struct{struct tasklet_struct *next; //构成链表成员unsigned long state; //是否被触发的状态atomic_t count; //触发的次数bool use_callback; //处理函数的选择 false->func true->callbackunion {void (*func)(unsigned long data); //旧版本的处理函数void (*callback)(struct tasklet_struct *t);//新版本的处理函数};unsigned long data; //向底半部处理函数传递的参数};struct tasklet_struct tasklet;2. 初始化对象void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data) //旧版本的初始化void tasklet_setup(struct tasklet_struct *t,void (*callback)(struct tasklet_struct *)) //新版本的初始化3. 调用执行
void tasklet_schedule(struct tasklet_struct *t)
(三)tasklet底半部处理函数
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h> //设备树文件相关头文件
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/of_gpio.h>
// mykeys{
// interrupt-parent = <&gpiof>;
// interrupt = <7 0>,<8 0>,<9 0>;
// };struct device_node *key_node;
unsigned int key_gpiono[3];struct timer_list mytimer;//定时器
//7 8 9------2 3 1
unsigned int irqno[3]={0};struct tasklet_struct mytasklet; //1.定义结构体
void mytasklet_bottom_func(struct tasklet_struct *tasklet){int i=0;for(i=0;i<50;i++){printk("i = %d\n",i);}
}
void timer_handler(struct timer_list* timer){int i;for(i=0;i<3;i++){if(!gpio_get_value(key_gpiono[i])){switch(i){case 0:printk("key1 down ......");break;case 1:printk("key2 down ......");break;case 2:printk("key3 down ......");break;}}}tasklet_schedule(&mytasklet);
}irqreturn_t irq_handler(int irq, void *dev){mod_timer(&mytimer,jiffies+1);return IRQ_HANDLED;
}static int __init mynode_init(void){int i;/***GPIO***///1. 获取节点key_node = of_find_node_by_path("/mykeys");if(NULL == key_node){pr_err("of_find_node_by_path error");return -EINVAL;}printk("of_find_node_by_path success\n");//2.获取gpio号for(i=0;i<3;i++){//corekey_gpiono[i] = of_get_named_gpio(key_node,"keys",i);if(key_gpiono[i] < 0){pr_err("of_get_named_gpio error");return key_gpiono[i];}}printk("of_get_named_gpio success\n");//3. 申请gpio,是为了防止竞态/***中断***///1. 获取节点key_node = of_find_node_by_name(NULL,"mykeys");if(NULL == key_node){pr_err("of_find_node_by_name error");return -EINVAL;}//2.获取中断号for(i=0;i<3;i++){irqno[i] = irq_of_parse_and_map(key_node,i);if (irqno[i] == 0) {pr_err("irq_of_parse_and_map error\n");return -EAGAIN;}}//3.注册中断号for(i=0;i<3;i++){request_irq(irqno[i],irq_handler,IRQF_TRIGGER_FALLING,"my_IRQ_test",(void *)irqno[i]);}/***定时器****///2.定时器对象初始化mytimer.expires = jiffies+1; //定时10ms timer_setup(&mytimer, timer_handler, 0); //3.启动定时器add_timer(&mytimer);tasklet_setup(&mytasklet,mytasklet_bottom_func);return 0;
}
static void __exit mynode_exit(void){int i=0;//注销中断号for(i=0;i<3;i++){free_irq(irqno[i],(void *)irqno[i]);}
}module_init(mynode_init);
module_exit(mynode_exit);
MODULE_LICENSE("GPL");
- 注:此时安装时就会触发一次,因为此处使用了定时器中断
三、工作队列
(一)简介
linux内核在启动时会默认启动一个events线程,这个线程默认处于休眠状态,它的内部维护一个工作队列。将
没有个数限制(链式队列)
工作队列可以脱离中断顶半部单独执行。工作队列工作在进程上下文,所以在工作队列的底半部处理函数中可以做延时,耗时,甚至休眠操作
必须等工作队列底半部处理函数结束后才能卸载函数,否则会出现空指针,导致内核崩溃。可以使用cancel_
(二)API
1.分配对象struct work_struct {atomic_long_t data; //结构体内部的data变量struct list_head entry; //构成队列成员work_func_t func; //工作队列的底半部处理函数//typedef void (*work_func_t)(struct work_struct *work);}struct work_struct work;
2.初始化对象INIT_WORK(&work, 底半部处理函数);
3.调用执行schedule_work(struct work_struct *work)
4.延时取消工作队列cancel_work_sync(&work); //等待工作队列执行结束,在卸载驱动
(三)使用示例