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 复制代码
相关推荐
知白守黑26715 分钟前
Linux磁盘阵列
linux·运维·服务器
维尔切1 小时前
Linux中基于Centos7使用lamp架构搭建个人论坛(wordpress)
linux·运维·架构
tan77º2 小时前
【项目】分布式Json-RPC框架 - 项目介绍与前置知识准备
linux·网络·分布式·网络协议·tcp/ip·rpc·json
正在努力的小河5 小时前
Linux设备树简介
linux·运维·服务器
荣光波比5 小时前
Linux(十一)——LVM磁盘配额整理
linux·运维·云计算
LLLLYYYRRRRRTT5 小时前
WordPress (LNMP 架构) 一键部署 Playbook
linux·架构·ansible·mariadb
轻松Ai享生活6 小时前
crash 进程分析流程图
linux
大路谈数字化7 小时前
Centos中内存CPU硬盘的查询
linux·运维·centos
luoqice8 小时前
linux下查看 UDP Server 端口的启用情况
linux
倔强的石头_9 小时前
【Linux指南】动静态库与链接机制:从原理到实践
linux