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

文章目录

一、概念

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

在中断顶半部处理函数中只能做简短的不耗时的操作,处理的是紧急的、不耗时的任务;中断底半部处理的是不紧急的、耗时的任务

中断底半部机制一共有三种:

软中断,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 复制代码
相关推荐
xiaozhiwise18 分钟前
Makefile 之 自动化变量
linux
意疏3 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu3 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿3 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
我的K84094 小时前
Flink整合Hudi及使用
linux·服务器·flink
1900434 小时前
linux6:常见命令介绍
linux·运维·服务器
Camellia-Echo4 小时前
【Linux从青铜到王者】Linux进程间通信(一)——待完善
linux·运维·服务器
Linux运维日记5 小时前
k8s1.31版本最新版本集群使用容器镜像仓库Harbor
linux·docker·云原生·容器·kubernetes
我是唐青枫5 小时前
Linux dnf 包管理工具使用教程
linux·运维·服务器