驱动学习笔记6 内核中断编程之顶半部与底半部,tasklet,工作队列,软中断

本文深入探讨Linux内核中断处理机制,包括进程、硬件中断和软中断的概念。重点讲解顶半部和底半部的原理,强调快速响应和并发能力的重要性。当中断处理函数耗时过长可能影响系统性能时,采用顶半部处理紧急、短暂的任务,底半部处理非紧急、耗时的任务。介绍了tasklet和工作队列两种底半部实现方式,前者基于软中断,后者基于进程,允许休眠。总结了两者适用场景,为优化中断处理提供了策略。
摘要由CSDN通过智能技术生成

1.linux内核中断编程之顶半部和底半部机制


a)掌握linux系统相关的概念


    1.任务:计算机中的任务细分两类:进程,硬件中断和软中断
       进程:进程一开始在用户空间运行,一旦调用系统调用立刻陷入内核空间继续运行
        运行完毕又回到用户空间继续运行
       硬件中断:外设给CPU发送的中断信号,内核执行其硬件中断处理函数,此函数永远运行在内核空间
       软中断:软件单独调用swi或svc指令立刻触发软中断异常,立刻执行其软中断处理函数,此函数
        同样位于内核空间
        注意:任何一种任务要想运行必须先获取到CPU资源!
    
    2.任务优先级:衡量任务获取CPU资源的能力
        优先级越高,获取CPU资源的能力越强,越能够及早运行
        在linux内核中,三类任务的优先级划分:
        硬件中断高于软中断
        软中断高于进程
        进程之间存在优先级之分(nice命令可以设置优先级)
        软中断之间存在优先级
        硬件中断无优先级之分(哪怕中断控制器支持优先级,内核不认)
        
    3.进程调度:linux内核会给每个进程分配时间片,进程一旦轮到其运行,在此时间片之内可以一直运行
        时间片到期,进程调度器(软件,有很牛叉的算法)会将当前进程的CPU资源撤下给其他进程使用
        切记:中断不参与进程调度,中断来了,直接会抢夺CPU资源,因为优先级高
    
    4.休眠sleep:休眠只能用于进程,不可用于中断(中断的世界里没有休眠一词)
        进程休眠:当前获取CPU资源的进程一旦要进入休眠状态,此进程会立马释放占用的CPU资源
        给其他进程 


b)结论:由以上概念得到:在linux内核中,内核希望中断处理函数的执行速度要越快越好


    如果中断处理函数长时间占用CPU资源,势必影响系统的并发能力(其他任务无法获取CPU资源投入运行)和
    响应能力(其他的硬件中断再也无法得到响应,各种卡)
    问:不可能所有的中断处理函数都能够满足内核的希望
           例如:网卡利用中断获取网络数据包,其中断处理函数本身执行的速度就很慢
           如果长时间占用CPU资源,就会造成丢包现象!
    答:对于此种情况,必须要采用内核提供的顶半部和底半部机制来优化


c)顶半部和底半部实现机制


    具体参见:bh.png(面试时边说边画)


1.顶半部特点
    硬件中断触发,立刻执行
    顶半部本质就是中断处理函数,只是现在里面做紧急,耗时较短的内容
    CPU在执行顶半部中断处理函数期间不允许发生CPU资源的切换
    顶半部同样不能休眠
2.底半部特点
    底半部对应的处理函数做不紧急,并且耗时较长的内容
    CPU在"适当"的时候执行底半部的处理函数
    在执行期间允许高优先级的任务来抢夺CPU资源,等处理完毕再回到底半部继续运行
    所以底半部的处理函数实现可以基于软中断或者进程实现
    底半部实现方法三种:tasklet,工作队列,软中断
    底半部的本质就是延后执行的一种手段,并不是非要和顶半部配合使用,也就是可以单独用底半部来
    将你想要延后执行的事情进行延后,可以将宝贵的CPU资源给其他高优先级的任务使用


3.底半部实现方法之:tasklet


a)特点:
    基于软中断实现,它对应的处理函数的优先级高于进程而低于硬件中断 
    tasklet对应的处理函数中不能进行休眠操作
    tasklet对应的处理函数又称延后处理函数,里面做不紧急,耗时较长的内容
b)描述tasklet属性的结构体
    struct tasklet_struct {
        void (*func)(unsigned long data);
        unsigned long data;
        ...
    };    
    功能:描述tasklet的属性
    func:指定tasklet的延后处理函数,将来内核会在适当时候调用其延后处理函数,不能进行休眠操作
        形参data保存给这个延后处理函数传递的参数        
    data:给延后处理函数传递的参数
    
    配套函数:
    void tasklet_init(struct tasklet_struct *tasklet,void (*func)(unsigned long),unsigned long data)    
    功能:初始化tasklet对象,给tasklet对象指定一个延后处理函数并且给延后处理函数传递参数
    tasklet:tasklet对象的地址
    func:给tasklet指定一个延后处理函数
    data:给延后处理函数传递的参数
    
    void tasklet_schedule(struct tasklet_struct *tasklet)
    功能:向内核注册tasklet对象和其延后处理函数,一旦注册成功,内核会在适当的时候执行其延后处理函数

 

案例:利用tasklet优化按键驱动
    明确:之前的按键的中断处理函数本身执行速度很快了,理论上没必要再次优化
    如果非要发掘按键的中断处理函数浪费CPU的地方,这个代码就是中断处理函数
    打印按键信息的代码:printk(.....);此代码最浪费CPU资源,最消耗时间,因为此函数
    操作的硬件设备是UART串口,UART串口本身数据处理的速度极其慢!
    干脆用tasklet优化
参考代码:day06/1.0

  1 #include<linux/init.h>
  2 #include<linux/module.h>
  3 #include<linux/irq.h>
  4 #include<linux/interrupt.h>
  5 #include<linux/gpio.h>
  6 #include<mach/platform.h>
  7 
  8 //声明按键的硬件信息
  9 struct btn_resource{
 10     char *name;
 11     int gpio;
 12 };
 13 
 14 //定义按键的硬件信息对象
 15 static struct btn_resource btn_info[] = {
 16     {
 17         .name = "KEY_UP",
 18         .gpio = PAD_GPIO_A + 28
 19     },
 20     {
 21         .name = "KEY_DOWN",
 22         .gpio = PAD_GPIO_B + 9
 23     }
 24 };
 25 //定义tasklet对象
 26 static struct tasklet_struct btn_tasklet;
 27 
 28 //两个函数用到,所以定义全局变量
 29 static struct btn_resource *pdata;//暂存当前触发中断的按键硬件信息的首地址
 30 
 31 //定义tasklet延后处理函数
 32 //基于软中断实现
 33 //内核在适当的时候执行此函数,同样不能进行休眠操作
 34 //data = &g_data
 35 static void btn_tasklet_function(unsigned long data){
 36     //1.暂存当前触发中断的按键硬件信息
 37     //KET_UP产生中断,pdata=&btn_info[0]
 38     //KEY_DOWN产生中断,pdata=&btn_info[1]
 39     //struct btn_resource *pdata = (struct btn_resource *)dev;
 40 
 41     //2.分配内核缓冲区暂存按键的状态
 42     int kstate;
 43 
 44     //3.获取按键的状态保存到内核缓冲区
 45     kstate = gpio_get_value(pdata->gpio);
 46 
 47     //4.打印按键的状态
 48     printk("底半部%s:按键[%s]的状态是:%s\n", __func__, pdata->name, kstate?"松开":"按下");
 49 }
 50 
 51 //定义按键的中断处理函数,又称顶半部
 52 //只要来了按键中断,内核立刻执行,CPU执行期间不允许切换,更不能进行休眠操作
 53 static irqreturn_t button_isr(int irq, void *dev){
 54     //1.获取当前触发中断的按键硬件信息
 55     pdata = (struct btn_resource *)dev;
 56     //2.向内核登记tasklet延后处理函数,一旦登记完成,内核在适当的时候执行延后处理函数
 57     tasklet_schedule(&btn_tasklet);
 58     printk("顶半部:%s\n", __func__);
 59     return IRQ_HANDLED;//中观处理函数执行成功返回,执行失败返回IRQ_NONE
 60 }
 61 
 62 //传参验证
 63 static int g_data = 0x55;
 64 
 65 static int btn_init(void){
 66     int i;
 67     //1.申请GPIO资源
 68     //2.申请中断资源,注册中断处理函数
 69     for(i = 0; i < ARRAY_SIZE(btn_info); i++){
 70         int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
 71         gpio_request(btn_info[i].gpio, btn_info[i].name);
 72         request_irq(irq,//申请的硬件中断的中断号
 73                     button_isr,//注册的按键中断处理函数,目前是四个按键共用一个中断处理函数
 74                     IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,//指定按键中断触发的类型
 75                     btn_info[i].name,//指定按键中断的名称
 76                     &btn_info[i]//把每个按键的硬件信息的首地址传递给中断处理函数
 77                 );
 78     }
 79     //3.初始化tasklet对象,指定延后处理函数并且指定给延后处理函数传递的参数
 80     tasklet_init(&btn_tasklet, btn_tasklet_function, (unsigned long)&g_data);
 81     return 0;
 82 }
 83 
 84 static void btn_exit(void){
 85     int i;
 86     //1.释放GPIO资源
 87     //2.释放中断资源和删除中断处理函数
 88     for(i = 0; i < ARRAY_SIZE(btn_info); i++){
 89         int irq = gpio_to_irq(btn_info[i].gpio);//通过gpio编号换算对应的中断号
 90         gpio_free(btn_info[i].gpio);
 91         free_irq(irq, &btn_info[i]);
 92     }
 93 }
 94 module_init(btn_init);
 95 module_exit(btn_exit);
 96 MODULE_LICENSE("GPL");


实验步骤同上    

4.底半部实现机制之工作队列  


    工作队列特点:
    1.工作队列基于进程实现,所以其延后处理函数可以进行休眠操作
    2.工作队列诞生的本质就是解决tasklet的延后处理函数不能休眠的问题
    所以如果在延后执行的内容中有休眠操作,必须采用工作队列
    3.工作队列的延后处理函数的优先级也是最低的
    
    描述工作队列属性的数据结构:
    struct work_struct {
        void (*func)(struct work_struct *work);
    };
    func:工作队列的延后处理函数,基于进程实现,可以进行休眠操作
        形参work:指向驱动定义初始化的work_struct对象,work指向自己
        用于给延后处理函数传递参数
        问:如何利用自己的地址给延后处理函数传递参数呢?
        答:必须配合大名鼎鼎的container_of宏来实现传递参数!
        作业:先自行研究大名鼎鼎的container_of宏
    
    配套函数:
    INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));
    功能:给work对象指定一个延后处理函数
    参数:
    work:work对象的地址
    func:指定延后处理函数

    schedule_work(struct work_struct *work);
    功能:向内核登记注册一个延后处理函数,一旦注册成功,内核在适当的时候调用其延后处理函数
    
案例:利用工作队列优化按键驱动
    明确:之前的按键的中断处理函数本身执行速度很快了,理论上没必要再次优化
    如果非要发掘按键的中断处理函数浪费CPU的地方,这个代码就是中断处理函数
    打印按键信息的代码:printk(.....);此代码最浪费CPU资源,最消耗时间,因为此函数
    操作的硬件设备是UART串口,UART串口本身数据处理的速度极其慢!
    这里采用工作队列优化
参考代码:day06/2.0


实验步骤同上

 

 

5.底半部实现机制之软中断(了解即可) 


    软中断特点:
    1.软中断同样有对应的延后处理函数,此函数可以同时运行在多个CPU核上,效率极其高
    这就要求其延后处理函数如果进行全局变量的访问,记得要做好互斥保护,当一个CPU核
    在访问全局变量是,禁止其他CPU核访问,但是这种互斥保护势必降低代码的效率
    2.tasklet基于软中断实现,但是它的延后处理函数同一时刻使能运行在一个CPU核上
    也不用关心互斥访问的问题!
    3.软中断的延后处理函数不能insmod和rmmod动态的安装和卸载,必须和uImage编写在一起
    编译在一起,无形加大了开发的难度和加大了代码的维护难度
    tasklet就可以动态的insmod和rmmod

6.终极总结:
    1.tasklet基于软中断实现,工作队列基于进程实现
    2.如果要进行延后执行,并且延后执行的内容中有休眠操作,只能用工作队列
    3.如果要进行延后执行,延后执行的内容中无休眠操作,但又要考虑效率问题,建议使用tasklet
    4.如果要进行延后执行,延后执行的内容中无休眠操作,但又不考虑效率问题,建议使用工作队列
    5.如果要进行延后执行,延后执行的内容中无休眠操作,并且对效率要求极高,想多核同时运行处理,建议采用软中断
       如果有对全局变量共享资源的同时访问还要进行互斥访问,所以建议还是tasklet吧!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值