Linux系统驱动(十五)中断底半部---tasklet、工作队列

一、概念

在中断顶半部处理函数中只能做简短的不耗时的操作,但有的时候有希望在中断到来的时候做尽可能多的事情,所以两者就产生的矛盾,内核为了解决这一矛盾设计了中断底半部机制。
在中断顶半部处理函数中只能做简短的不耗时的操作,处理的是紧急的、不耗时的任务;中断底半部处理的是不紧急的、耗时的任务

中断底半部机制一共有三种:
软中断,tasklet,工作队列

  • 注:软中断有个数限制(32个),一般不在驱动中使用

二、tasklet底半部机制

(一)简介

基于软中断实现的,没有个数限制,因为它内部是通过链表实现的。
tasklet是中断的一个部分,不能够脱离中断顶半部单独执行,在中断顶半部执行即将结束时,可以开启tasklet底半部机制。

tasklet是工作在中断上下文的,tasklet的底半部处理函数中只能做耗时任务或者短延时任务,但不能做休眠工作
(底半部优先级高于进程,他是工作在中断上下文的)

  • 注:delay和sleep的区别
  • delay相当于写了一个循环函数,cpu一直在执行这个循环
  • sleep会放弃cpu,cpu去执行其他进程

如果中断处理函数的结尾处开启了中断底半部,当返回调用do_irq的位置,清除了中断标志位后,会再次回调底半部的处理函数;
如果此时再次触发中断顶半部,且再次开启了中断底半部,两次底半部处理函数会顺序执行。

  • 注:ARM架构中,中断不允许套中断
  • tasklet绝不能做休眠动作,内核会崩溃(表现为乱码刷屏,不停止)

(二)tasklet的API接口

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底半部处理函数

#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线程,这个线程默认处于休眠状态,它的内部维护一个工作队列。

没有个数限制(链式队列)
工作队列可以脱离中断顶半部单独执行。工作队列工作在进程上下文,所以在工作队列的底半部处理函数中可以做延时,耗时,甚至休眠操作

必须等工作队列底半部处理函数结束后才能卸载函数,否则会出现空指针(Oops/painc),导致内核崩溃。
可以使用cancel_work_sync延时取消工作队列
如果是内部模块

(二)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); //等待工作队列执行结束,在卸载驱动

(三)使用示例


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值