Linux驱动开发——中断编程之顶半部与底半部机制(1)

简介: Linux驱动开发——中断编程之顶半部与底半部机制(1)

文章目录

Linux中断编程之顶半部和底半部机制

概述

Linux内核对中断处理函数(isr_handler)的要求

Linux内核中断编程之顶半部特点

Linux中断编程之底半部特点

底半部实现机制之tasklet

案例实现(使用tasklet在按键后处理执行打印信息)

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

案例实现(使用工作队列在按键后处理执行打印信息)

底半部实现机制之软中断

总结:


Linux中断编程之顶半部和底半部机制

概述

前面已经简单了解并实现了基于中断机制的任务处理在内核中,但是实际上Linux的中断机制远远不是只要这么简单的。

在Linux内核中,任务处理类型细分的话分为三类:进程、软中断、硬件中断。而其中软中断和硬件中断都是属于中断方式(打断当前任务获取CPU资源处理触发事件在还原当前执行任务)。

前面也曾提到过“优先级”一词,“优先级”是衡量一个任务获取CPU资源的一种能力评价,优先级越高那么获取CPU资源的能力越强,它投入运行的时间就能提前。Linux内核任务优先级划分:


  • 硬件中断的优先级高于软中断。
  • 软中断的优先级高于进程。
  • 进程之间也存在优先级,高优先级的进程和低优先级的进程。
  • 软中断同样具有优先级之分,但是硬件中断并没有优先级之分,先到先处理。
  • 当高优先级任务占用CPU资源的适合,其他进程会进入“休眠”,但是中断的世界里是没有休眠的,并且中断不参与进程的调度。中断的世界没有休眠,那么对中断的使用要求就很高了,越是高优先级的中断任务,那么对其处理时间就越要求短,如果高优先级任务长期占有CPU资源自然会导致延误很多其他中断或者进程的处理,所以就有了Linux中断编程中的顶半部和底半部机制了。

Linux内核对中断处理函数(isr_handler)的要求

注意:Linux内核要求中断处理函数执行的速度越快越好,更不能进行休眠操作,如果中断处理函数长时间占用CPU资源,而本身它的优先级最高,那么就会导致其他任务无法及时获取到CPU资源,也就无法及时运行处理,从而影响了并发和响应能力。

那么Linux内核中有些硬件的中断处理函数无法满足Linux内核的这个要求呢,比如在中断处理中需要等待外设的一些数据进行读取呢,那么对于这种不能满足Linux内核的要求的中断处理,势必会影响到Linux内核的并发和响应能力,对于这种情况,我们就需要使用Linux内核的顶半部和底半部机制来对中断处理任务进行优化了。


Linux内核中断编程之顶半部特点

顶半部就是做原先中断处理函数中比较紧急、耗时较短的内容,一旦硬件中断触发,CPU首先执行顶半部,执行时间非常快,执行完会立即释放CPU资源给其他任务使用,CPU在执行期间不允许发生CPU资源的切换,也就能踏踏实实的执行顶半部任务,顶半部本质就是中断处理函数,只是将原先中断处理中的最紧要、耗时最短的内容留下了而已。


Linux中断编程之底半部特点

底半部做原先中断处理函数中比较耗时且不紧急的内容,CPU会在“适当”的适合去执行底半部的任务(当CPU执行底半部的时候,底半部的优先级势必是当前最高的),但是CPU在执行期间如果再次遇到一个高优先级的任务到来,那么势必会抢走底半部的CPU资源,等到高优先级的任务执行完毕后再将CPU资源还给底半部,底半部继续进行处理,底半部本质就是延后执行的一种手段而已。

如果仅仅单纯将某个事件延后执行,可以利用底半部机制来实现,底半部实现方法有三种:


  • tasklet
  • 工作队列
  • 软中断

底半部实现机制之tasklet

tasklet特点: tasklet本身是基于软中断实现,优先级是高于进程而低于硬件中断,所以tasklet对应的延后处理函数不能进行休眠操作,tasklet对应的延后处理函数执行原先中断处理函数中毕竟耗时和不紧急的内容。


  • Linux内核描述tasklet的数据结构:
struct tasklet_struct {
  void (*func)(unsigned long data);
  unsigned long data;
  ...
};


成员说明:


  • func:tasklet的延后处理函数,做原先中断处理函数中不紧急或者耗时较长的内容,由于基于软中断实现,所以里面不能进行休眠操作,形参data是保存传递进来的参数。
  • data:给tasklet延后处理函数传递的参数。
  • 利用tasklet实现底半部延后执行的编程步骤:


  1. 定义初始化一个tasklet对象
int g_data = 0x55;
DECLARE_TASKLET(btn_tasklet, //对象名
        btn_tasklet_func,//tasklet的延后处理函数
        (unsigned long)&g_data);//给延后处理函数传递的参数
//结果:
btn_tasklet.func = btn_tasklet_func;
btn_tasklet.data = (unsigned long)&g_data;

  1. 编写tasklet延后处理函数(例如上面的btn_tasklet_func)
//data = (unsigned long)&g_data;
void btn_tasklet_func(unsigned long data)
{
  //做原先中断处理函数中耗时不紧急的内容
}

  1. 在顶半部中断处理函数中向内核“登记”(而不是调用),一旦登记完成,内核会在“适当”的时候去调用tasklet的延后处理函数,登记的函数为:
tasklet_schedule(&btn_tasklet);

案例实现(使用tasklet在按键后处理执行打印信息)

  • btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/input.h> //标准按键值:KEY_*
//声明描述按键信息相关的数据结构
struct btn_resource {
    int gpio; //按键对应的GPIO编号
    char *name; //按键名称
    int code; //按键值
};
//定义初始化按键硬件信息对象
static struct btn_resource btn_info[] = {
    {
        .gpio = PAD_GPIO_A+28,
        .name = "KEY_UP",
        .code = KEY_UP
    },
    {
        .gpio = PAD_GPIO_B+9,
        .name = "KEY_DOWN",
        .code = KEY_DOWN
    },
};
//记录按键的硬件信息
static struct btn_resource *pdata;
//tasklet延后处理函数
//适当的时候被内核调用
//基于软中断实现
//不能进行休眠操作
//data = (unsigned long)&g_data;
static void btn_tasket_function(unsigned long data)
{
    int state;
    //1.获取按键对应GPIO的电平状态,获取按键的操作状态
    state = gpio_get_value(pdata->gpio);
    //2.打印按键信息
    printk("底半部:%s:按键值[%d]按键状态[%s]\n",
            __func__, pdata->code, state?"松开":"按下");
}
static int g_data = 0x5555;
//定义初始化一个tasklet对象
static DECLARE_TASKLET(btn_tasklet, //定义对象
                    btn_tasket_function,//指定延后处理函数
                    (unsigned long)&g_data);//传参
//顶半部:中断处理函数
//一旦中断到来,先执行顶半部
//不允许CPU资源切换
static irqreturn_t button_isr(int irq, void *dev)
{
    //1.获取对应的当前触发的硬件中断的硬件信息
    pdata = (struct btn_resource *)dev;
    //2.向内核登记底半部tasklet延后处理函数,一旦登记
    //完成,内核将来在适当的时候调用延后处理函数
    tasklet_schedule(&btn_tasklet);
    //3.验证顶半部的执行先于延后处理函数
    printk("顶半部:%s\n", __func__);
    return IRQ_HANDLED; //执行成功
}
static int btn_init(void)
{
    int i;
    //1.申请GPIO资源
    //配置为输入功能由内核已经帮你实现
    //2.申请中断资源,注册中断处理函数
    //注意:多个按键共享一个中断处理函数
    for (i = 0; i < ARRAY_SIZE(btn_info); i++) {
        //由GPIO编号求中断号
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_request(btn_info[i].gpio, 
                        btn_info[i].name);
        request_irq(irq,  //中断号
                button_isr,//注册的中断处理函数
            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                btn_info[i].name,//中断名称
                &btn_info[i] //将每个按键对应的硬件信息传                                递给中断处理函数 
                );
    }
    return 0;
}
static void btn_exit(void)
{
    int i;
    //1.释放中断资源删除中断处理函数
    //2.释放GPIO资源
    for(i = 0; i < ARRAY_SIZE(btn_info); i++) {\
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_free(btn_info[i].gpio);
        free_irq(irq, &btn_info[i]);
    }
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");


  • Makefile
obj-m += btn_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行实现:

2019122922464946.png

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

工作队列特点: 工作队列诞生的本质就是解决tasklet的延后处理函数不能进行休眠操作的问题,因为在某些场合需要在延后的处理流程中进行休眠操作,对于这样的情况必须使用工作队列,所以工作队列是基于进程实现的,所以自然工作队列的延后处理函数是可以进行休眠操作,但是它的优先级低于软中断,低于硬件中断,工作队列延后处理函数中依旧还是去处理中断处理遗留下来的耗时较长或者不紧急的内容。


  • Linux内核描述工作队列的数据结构:
struct work_struct {
  work_func_t func;
  ...
};


成员说明:


func:工作队列的延后处理函数,里面可以进行休眠操作。
配套函数:
//给工作队列添加一个延后处理函数
INIT_WORK(&工作队列对象, 工作队列的延后处理函数);
//向内核登记工作队列的延后处理函数,一旦登记完成
//将来内核在适当的时候调用其延后处理函数
schedule_work(&工作队列对象);


利用工作队列实现延后执行的编程步骤:


  1. 定义初始化工作队列对象。
struct work_struct btn_work; //定义对象
INIT_WORK(&btn_work, btn_work_function);

  1. 编写工作队列延后处理,此函数可以进行休眠操作。
//work指针就是指向自己定义初始化的对象
//work=&btn_work
void btn_work_function(struct work_struct *work)
{
  //不紧急耗时较长的内容
}

  1. 向内核登记工作队列的延后处理函数,一旦登记完成内核在适当的时候调用延后处理函数。
schedule_work(&btn_work);

案例实现(使用工作队列在按键后处理执行打印信息)

  • btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/input.h> //标准按键值:KEY_*
//声明描述按键信息相关的数据结构
struct btn_resource {
    int gpio; //按键对应的GPIO编号
    char *name; //按键名称
    int code; //按键值
};
//定义初始化按键硬件信息对象
static struct btn_resource btn_info[] = {
    {
        .gpio = PAD_GPIO_A+28,
        .name = "KEY_UP",
        .code = KEY_UP
    },
    {
        .gpio = PAD_GPIO_B+9,
        .name = "KEY_DOWN",
        .code = KEY_DOWN
    },
};
//记录按键的硬件信息
static struct btn_resource *pdata;
//工作队列延后处理函数
//适当的时候被内核调用
//基于进程实现
//能进行休眠操作
//work=&btn_work
static void btn_work_function(struct work_struct *work)
{
    int state;
    //1.获取按键对应GPIO的电平状态,获取按键的操作状态
    state = gpio_get_value(pdata->gpio);
    //2.打印按键信息
    printk("底半部:%s:按键值[%d]按键状态[%s]\n",
            __func__, pdata->code, state?"松开":"按下");
}
//定义一个工作队列对象
static struct work_struct btn_work; 
//顶半部:中断处理函数
//一旦中断到来,先执行顶半部
//不允许CPU资源切换
static irqreturn_t button_isr(int irq, void *dev)
{
    //1.获取对应的当前触发的硬件中断的硬件信息
    pdata = (struct btn_resource *)dev;
    //2.向内核登记底半部延后处理函数,一旦登记
    //完成,内核将来在适当的时候调用延后处理函数
    schedule_work(&btn_work);
    //3.验证顶半部的执行先于延后处理函数
    printk("顶半部:%s\n", __func__);
    return IRQ_HANDLED; //执行成功
}
static int btn_init(void)
{
    int i;
    //1.申请GPIO资源
    //配置为输入功能由内核已经帮你实现
    //2.申请中断资源,注册中断处理函数
    //注意:多个按键共享一个中断处理函数
    for (i = 0; i < ARRAY_SIZE(btn_info); i++) {
        //由GPIO编号求中断号
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_request(btn_info[i].gpio, 
                        btn_info[i].name);
        request_irq(irq,  //中断号
                button_isr,//注册的中断处理函数
            IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                btn_info[i].name,//中断名称
                &btn_info[i] //将每个按键对应的硬件信息传                                递给中断处理函数 
                );
    }
    //2.初始化工作队列指定延后处理函数
    INIT_WORK(&btn_work, btn_work_function);
    return 0;
}
static void btn_exit(void)
{
    int i;
    //1.释放中断资源删除中断处理函数
    //2.释放GPIO资源
    for(i = 0; i < ARRAY_SIZE(btn_info); i++) {\
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_free(btn_info[i].gpio);
        free_irq(irq, &btn_info[i]);
    }
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");


  • Makefile
obj-m += btn_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行实现

20191229225245934.png底半部实现机制之软中断

软中断特点: tasklet本身是基于软中断,软中断的延后处理函数同样不能进行休眠操作,软中断的延后处理函数可以同时运行在多个CPU核上,而tasklet的延后处理函数同一时刻只能运行在一个CPU核上,所以软中断的延后处理函数在设计的时候务必考虑可重入性,软中断编程实现上不能以insmod/rmmod形式动态的安装和写在,必须和uImage编译在一起,这样软件设计相对比较繁琐,所以就有了tasklet的产生。tasklet本质就是解决软中断设计的繁琐问题(软中断的替代)。


  • 基于以上特点,所以这里就暂时不详细描述软中断的底半部实现了。

总结:

  • 如果延后执行的内容中,没有休眠操作,用tasklet或者工作队列都可以
  • 如果延后执行的内容中,有休眠操作,只能用工作队列
  • 如果延后执行的内容中,没有休眠操作,并且考虑效率问题,使用tasklet



相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4887 77
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
538 32
|
安全 算法 Ubuntu
Linux(openssl)环境:编程控制让证书自签的技巧。
总结:在Linux环境中,OpenSSL是一个非常实用的工具,可以帮助我们轻松地生成自签名证书。通过上述三个简单步骤,即可为内部网络、测试环境或开发环境创建自签名证书。但在公共访问场景下,建议购买经过权威认证机构签发的证书,以避免安全警告。
644 13
|
存储 Linux
Linux内核中的current机制解析
总的来说,current机制是Linux内核中进程管理的基础,它通过获取当前进程的task_struct结构的地址,可以方便地获取和修改进程的信息。这个机制在内核中的使用非常广泛,对于理解Linux内核的工作原理有着重要的意义。
633 11
|
JavaScript Ubuntu Linux
如何在阿里云的linux上搭建Node.js编程环境?
本指南介绍如何在阿里云Linux服务器(Ubuntu/CentOS)上搭建Node.js环境,包含两种安装方式:包管理器快速安装和NVM多版本管理。同时覆盖全局npm工具配置、应用部署示例(如Express服务)、PM2持久化运行、阿里云安全组设置及外部访问验证等步骤,助你完成开发与生产环境的搭建。
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
298 26
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
325 17
|
存储 负载均衡 网络协议
X86 linux异常处理与Ipipe接管中断/异常
本文讲述了X86平台上Xenomai的ipipe如何接管中断处理。首先回顾了X86中断处理机制,包括IDT(中断描述符表)的工作原理和中断处理流程。接着详细介绍了Linux中中断门的初始化,包括门描述符的结构、中断门的定义和填充,以及IDT的加载。在异常处理部分,文章讲解了早期异常处理和start_kernel阶段的异常向量初始化。最后,讨论了APIC和SMP中断在IDT中的填充,以及剩余中断的统一处理。文章指出,ipipe通过在中断入口处插入`__ipipe_handle_irq()`函数,实现了对中断的拦截和优先处理,确保了实时性。
618 0
X86 linux异常处理与Ipipe接管中断/异常
|
存储 负载均衡 算法
Linux内核17-硬件如何处理中断和异常
Linux内核17-硬件如何处理中断和异常