Linux中断简述

一 Linux中断机制由来:
内核空间和用户空间是现代操作系统的两种工作模式,内核模块运行在内核空间,而用户态应用程序运行在用户空间,他们代表不同的级别,对系统资源有着不同的访问权限,内核模块运行在最高级别(内核态),这个级别下的所用操作都受系统信任,而应用程序运行在比较低级别的(用户态)。处理器总是处于以下状态中的一种:

内核态:运行于进程上下文,内核态代表进程运行于内核空间
内核态:运行于中断上下文,内核态代表硬件运行于内核空间
用户态:运行于用户空间

linux操作系统是以进程调度为单位,中断服务程序的执行并不在进程上下文中,所以要求中断服务程序的时间要尽量短,为此,Linux在中断处理中引入了顶半部和底半部分离的机制。

二 Linux中断分类:
1 根据中断来源:分为 内部中断 和 外部中断

内部中断 :来源于CPU内部:软件中断指令、溢出等等例如操作系统从用户态切换到内核态借助CPU内部的软件中断
外部中断 :来源于CPU外部外设申请 

2 根据中断是否可以屏蔽 :分为 可屏蔽中断 和 不可屏蔽中断

3 根据中断入口跳转方式不同:分为 向量中断 和 非向量中断

向量中断 :采用向量中断的CPU通常为不同的中断分配不同的中断号,当检测到某中断号的中断到来后,就会自动跳转到与该中断号对应的地址执行,不同的中断号有不同的入口地址,向量中断是由硬件提供中断服务程序入口地址。

非向量中断 :多个中断共享一个入口地址,进入该入口地址后,再通过软件判断中断标志来识别具体是哪一个中断,非向量中断由软件提供中断服务程序入口地址

三 Linux中断处理机制
操作系统是以进程调度为单位,中断服务程序的执行并不在进程上下文中,设备的中断会打断内核进程的正常调度运行,所以系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍,但是大多数中断来临时,要完成的工作量往往不会小,可能需要较大量的耗时处理。中断处理机制就是为了在中断处理程序执行时间尽量短和中断处理完需要完成的工作量尽量大之间找一个平衡点,由此,Linux将中断处理程序分解为两个半部:顶半部 和 底半部

顶半部 :又称硬中断,用于完成比较紧急的功能,往往只是简单的读取寄存器中的中断状态,并在清除中断标志后,启动 底半部(软中断),意味着将底半部处理流程序列挂到底半部执行队列中去,这样顶半部的执行时间就很快,从而服务更多的中断请求。中断无法打断顶半部

底半部 :又称软中断,他需要完成中断事件的绝大多数任务,由系统自行安排运行时机,它可以被新的中断打断,软中断的处理非常像硬中断。然而,它们仅仅是由当前正在运行的进程所产生的(硬中断是由硬件产生的)

四 实现底半部机制:
Linux 实现底半部的机制主要有 tasklet、工作队列、软中断和线程化irq等方式

4.1 tasklet机制
Tasklet 是 Linux中断处理机制中的 软中断延迟机制,在Linux中存在着硬中断和软中断之别在Linux 接收到 硬件中断 之后,通过tasklet函数来设定软中断被执行的优先程度从而导致 软中断处理函数被优先执行的差异性

 模板:
               //定义底半部处理函数
              void xxx_do_tasklet(unsigned long);
              
              //定义一个 tasklet结构体 xxx_tasklet,与底半部处理函数 xxx_do_tasklet 绑定
              DECLARE_TASKLET(xxx_tasklet,xxx_do_tasklet,0);

              //中断处理底半部
              void xxx_do_tasklet(undigned long)
              {
                ...
              }

              //中断处理顶半部
              irqreturn_t xxx_interrupt(int irq,void *dev_id)
              {
                ...
                //tasklet机制的 系统调度 调用底半部
                tasklet_schedule(&xxx_tasklet);
                ... 
              }

              //设备驱动模块加载
              int __init xxx_init(void)
              {
                ...
                //申请中断
                result = request_irq(xxx_irq,xxx_interrupt,0,"xxx",NULL);
                ... 
                return IRQ_HANDLED;
              }

              void _exit xxx_exit(void)
              {
                ...
                //释放中断
                free_irq(xxx_irq,xxx_interrupt);
                ... 
              }

RK3288 Android7.1 代码笔记:
场景:3288主板I2C4 连接外部单片机小板,外部小板发生变化时通过中断提示3288主板,3288主板通过i2c获取外部小板某寄存器内数值。

....

//中断处理底半部
void chensai_do_tasklet(unsigned long date)
{
    chensai_i2c_read(chensai_client, register_addr, key_status, chensai_iic_addr);
}

DECLARE_TASKLET(chensai_tasklet,chensai_do_tasklet,0);

//中断顶半部
static irqreturn_t chensai_irq_handler(int irq, void *dev_id)
{
//进入中断函数 禁止中断
    disable_irq_nosync(chensai_irq_num);
    DBG("%s :enter\n", __func__);

    tasklet_schedule(&chensai_tasklet);
    
    //使能中断
    enable_irq(chensai_irq_num);
    return IRQ_HANDLED;
}

static int chensai_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int error;
    int ret;
    unsigned int gpio_num; 
    struct device_node *np = client->dev.of_node;
    enum of_gpio_flags flags;
    static struct task_struct *task;
    
    ...
    //从设备树获取中断gpio的 gpio_num 
    gpio_num =of_get_named_gpio_flags(np, "irq-gpio", 0, &flags);
    DBG("%s  gpio_num=%d\n", __func__, gpio_num);
    
    //判断引脚有否有效
    if (!gpio_is_valid(gpio_num)){
        DBG("%s  gpio_is_unvalid \n", __func__);
    } 
    
    //申请该gpio
    if (gpio_request(gpio_num, "irq-gpio")) {
        DBG("%s  failed to request  irq-gpio, gpio_num =%d\n", __func__, gpio_num);
    }
    
    //将该引脚设置为输入
    gpio_direction_input(gpio_num);
    chensai_irq_num = gpio_to_irq(gpio_num);    //将gpio转换成对应的中断号
    DBG("%s  chensai_irq_num=%d\n", __func__, chensai_irq_num);
    
    //初始化中断 中断方式为下降沿触发
    ret = request_irq(chensai_irq_num, chensai_irq_handler, IRQ_TYPE_EDGE_FALLING, "chensai_irq", NULL);
    if (ret) {
            printk("request_irq error\n");
    }
...
    return 0;
}


static const struct i2c_device_id chensai_id[] = {
    {"chensai_keyboard", 0},
    {}
};
MODULE_DEVICE_TABLE(i2c, chensai_id);

static struct i2c_driver chensai_drv = { 
    .driver     = { 
        .name   = "chensai",
        .owner  = THIS_MODULE,
    },
    .probe = chensai_probe,
    .id_table = chensai_id,
};

static int chensai_init(void)
{
    i2c_add_driver(&chensai_drv);
    return 0;
}

static void chensai_exit(void)
{
    i2c_del_driver(&chensai_drv);
    free_irq(chensai_irq_num, chensai_irq_handler);
}

module_init(chensai_init);
module_exit(chensai_exit);
MODULE_LICENSE("GPL");

设备树:

&i2c4 {
       status = "okay";
       clock-frequency = <100000>;
       chensai_keyboard@09 {       
                       compatible = "chensai_keyboard";
                       reg = <0x09>;
                       irq-gpio = <&gpio7 6 IRQ_TYPE_EDGE_FALLING>;
                       status = "okay";
               };
       };

4.2:工作队列
工作队列与tasklet方法非常类似,使用一个结构体定义一个工作队列和一个底半部执行函数,而且工作队列的执行上下文是内核进程,所以可以调度和睡眠,(中断上下文中不能睡眠)

       //定义一个工作队列
       struct work_struct xxx_wq;
       
       //中断处理底半部
       void xxx_do_work(struct work_struct *work)

       //处理底半部中断处理函数
       void xxx_do_work(struct work_struct *work)
       {
            ... 
       }

       //中断处理顶半部
       irqreturn_t xxx_interrupt(int irq,void *dev_id)
       {
            ...
            //系统调度 调用底半部
            schedule_work(&xxx_wq);
            ... 
       }

       //设备驱动模块加载
       int xxx_init(void)
       {
            ...
            //申请中断
            result = request_irq(xxx_irq,xxx_interrupt,0,"xxx",NULL);

              /* 初始化工作队列 */
              INIT_WORK(&xxx_wq, xxx_do_work);
              ...
              return IRQ_HANDLED;
           }

       //
        void _exit xxx_exit(void)
        {
             ...
            //释放中断
             free_irq(xxx_irq,xxx_interrupt);
             ...    
        }

4.3:软中断

 软中断是一种传统的底半部处理机制,它的执行时机往往放生在顶半部返回的时候,
 tasklet是基于软中断实现的,因此是运行在软中断上下文。
 软中断和tasklet运行在软中断上下文,仍然属于中断上下文的一种。而工作队列
 则运行于进程上下文,所以,在软中断和tasklet的处理函数中不允许睡眠,而在
 工作队列中允许睡眠。

原文链接:https://blog.csdn.net/LinuxArmbiggod/article/details/86312343

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页