文章目录
一、概念
在中断顶半部处理函数中只能做简短的不耗时的操作,但有的时候有希望在中断到来的时候做尽可能多的事情,所以两者就产生的矛盾,内核为了解决这一矛盾设计了中断底半部机制。
在中断顶半部处理函数中只能做简短的不耗时的操作,处理的是紧急的、不耗时的任务;中断底半部处理的是不紧急的、耗时的任务
中断底半部机制一共有三种:
软中断,tasklet,工作队列
- 注:软中断有个数限制(32个),一般不在驱动中使用
二、tasklet底半部机制
(一)简介
基于软中断实现的,没有个数限制,因为它内部是通过链表实现的。
tasklet是中断的一个部分,不能够脱离中断顶半部单独执行,在中断顶半部执行即将结束时,可以开启tasklet底半部机制。
tasklet是工作在中断上下文的,tasklet的底半部处理函数中只能做耗时任务或者短延时任务,但不能做休眠工作
(底半部优先级高于进程,他是工作在中断上下文的)
- 注:ARM架构中,中断不允许套中断
- tasklet绝不能做休眠动作,内核会崩溃(表现为乱码刷屏,不停止)
(二)tasklet的API接口
c
1. 分配对象
struct tasklet_struct
{
struct tasklet_struct *next; //构成链表成员
unsigned long state; //是否被触发的状态
atomic_t count; //触发的次数
bool use_callback; //处理函数的选择 false->func true->callback
union {
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底半部处理函数
c
#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++){
//core
key_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
c
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); //等待工作队列执行结束,在卸载驱动
(三)使用示例
c