ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

【Linux驱动篇】中断实现机制

2022-01-16 15:30:51  阅读:204  来源: 互联网

标签:中断 irq void Linux int tasklet 驱动 my


一、中断

  中断分为上半部和底半部。上半部也就是硬中断,软中断只是底半部的一种实现机制
  上半部主要处理有严格时限的工作,比如读取寄存的中断状态,清除中断标志,将底半部处理程序挂到底半部的执行队列中去
底半部执行大部分耗时的工作,并且可以被其他中断打断
  1、硬中断是由硬件产生的,比如,像磁盘,网卡,键盘,时钟等。每个设备或设备集都有它自己的IRQ(中断请求)。基于IRQ,CPU可以将相应的请求分发到对应的硬件驱动上(注:硬件驱动通常是内核中的一个子程序,而不是一个独立的进程)
  2、软中断是一组静态定义的下半部分接口,可以在所有的处理器上同时执行,即使两个类型相同也可以。但是一个软中断不会抢占另外的一个软中断,唯一可以抢占软中断的硬中断。可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保其数据结构。

二、中断函数
1.开关中断

/************关闭指定中断*************************/
void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq) //要等到当前正在执行的中断处理函数执行完才返回
void disable_irq_nosync(unsigned int irq)  //函数调用以后立即返回

/**************硬中断****************************/
local_irq_disable():关闭中断
local_irq_enable():开启中断
local_irq_save(flags):关闭中断并保存中断标志位
local_irq_restore(flags):开启中断并清除中断标志位

/*************底半部开关************************/
local_bh_disable();
local_bh_enable();

/*************判断中断状态*********************/
#define in_interrupt() (irq_count())   // 是否处于中断状态(硬中断或软中断)
#define in_irq() (hardirq_count())     // 是否处于硬中断
#define in_softirq() (softirq_count()) // 是否处于软中断

  disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。
  当任务A调用local_irq_disable企图关闭中断1s,然后再开启中断,但在中断中出现了另一个优先级更高的任务B也调用了local_irq_disable关闭中断,然后100ms后执行local_irq_enable开启中断,导致任务A的中断没有持续1s就被打开了,这显然是有问题的,所以需要用到后两个中断函数,当B任务关闭中断的时候保存中断标志位,开启中断的时候清除中断标志位,还原之前的中断状态,才能使任务A继续处于中断状态中正确执行。

2、申请中断

int request_irq(unsigned int irq, irq_handle_t handle, unsigned int flags, 
                const char *name, void* dev_id);


int devm_request_irq(struct device *dev, unsigned int irq, irq_handle_t handle, 
                unsigned int flags, const char *name, void* dev_id);

其中flags代表中断标志,可以取值如下:
  上升沿触发:IRQF_TRIGGER_RISING
  下降沿触发:IRQF_TRIGGER_FALLING
  高电平触发:IRQF_TRIGGER_HIGH
  低电平触发:IRQF_TRIGGER_LOW
  共享中断:IRQF_SHARED

  dev_id代表要传给中断处理程序的私有数据,一般是这个设备的结构体或者NULL
  devm_request_irq和request_irq区别在于前者是申请的内核“managed”资源,不需要自己手动释放,会自动回收资源,而后者需要手动调用free_irq来释放中断资源

3、释放中断

void free_irq(unsigned int irq, void *dev_id);

二、底半部实现机制:软中断、tasklet、工作队列、线程化irq

  1、软中断一般不使用,由于必须要设计为可重入函数,导致复杂度高,并且无法睡眠、不可阻塞,无法重新调度。
  软中断应该保留给系统中对时间要求最严格的下半部使用。目前只有两个子系统直接使用软中断:网络和SCSI。此外,内核定时器和tasklet也是基于软中断实现的。由于同一个软中断可以在不同处理器上同时执行,所以软中断要求对共享资源的处理要非常严格,锁保护的要求很高。

  2、tasklet是在两种软中断类型的基础上实现的,因此如果不需要软中断的并行特性,tasklet就是最好的选择。也就是说tasklet是软中断的一种特殊用法,即延迟情况下的串行执行。
tasklet由两类软中断代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。这两者唯一的区别在于,HI_SOFTIRQ类型的软中断先于TASKLET_SOFTIRQ类型的软中断执行。

tasklet_struct my_tasklet;
void my_tasklet_func(unsigned long);
//将my_tasklet和my_tasklet_func绑定关联
DECLARE_TASKLET(my_tasklet, may_tasklet_func, data);

void my_tasklet_func(unsigned long)
{
    ...
}

irqreturn_t my_tasklet_demo_interrupt(int irq, void *dev_id)
{
    ...
    //执行tasklet_shedule调度,会在适当的时候执行my_tasklet_func
    tasklet_shedule(&my_tasklet);
    ...
    return IRQ_HANDLED;
}

int __init my_tasklet_demo_init(void)
{
    ...
    int result = request_irq(irq_num, my_tasklet_demo_interrupt, IRQF_SHARED, "my_tasklet_demo", dev_id);
    ...
    return IRQ_HANDLED;
}

void __exit my_tasklet_demo_exit(void)
{
    ...
    free_irq(irq_num, my_tasklet_demo_interrupt);
    ...
}

  3、工作队列可以把工作推后,交由一个内核线程去执行。内核线程只在内核空间运行,没有自己的用户空间,它和普通进程一样可以被调度,也可以被抢占。该工作队列总是会在进程上下文执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势,最重要的就是工作队列允许重新调度甚至是睡眠。
  理论上可以用创建内核线程的方式来代替工作队列,但是由于随便创建内核线程会带来其他问题,所以实际上并不建议直接创建内核线程,而应使用工作队列的形式。

struct work_struct my_workqueue;
void my_wq_func(struct work_struct* work);

/*中断底半部工作队列的处理*/
void my_wq_func(struct work_struct* work)
{
    ...
}

irqreturn_t my_wq_interrupt(int irq, void *dev_id)
{
    ...
    schedule_work(&my_workqueue);
    ...
    return IRQ_HANDLED;
}

int __init my_wq_init(void)
{
    ...
    int result = request_irq(irq_num, my_wq_interrupt, IRQF_TRIGGER_RISING, "my_wq", dev_id);
    //初始化工作队列,并将my_workqueue和my_wq_func处理函数关联
    INIT_WORK(&my_workqueue, my_wq_func);
    ...
    return 0;
}

void my_wq_exit(void)
{
    ...
    free_irq(irq_num, my_wq_interrupt);
    ...
}

  4、threaded_irq:内核提供request_threaded_irq和devm_request_threaded_irq为中断分配内核线程,原型如下:

int request_threaded_irq(unsigned int irq, 
                    irq_handle_t* handle, 
                    irq_handle_t* thread_func, 
                    unsigned long flags, const char* name, void* dev_id);

int devm_request_threaded_irq(struct device* dev, unsigned int irq, 
                    irq_handle_t* handle, 
                    irq_handle_t* thread_func, 
                    unsigned long flags, const char* name, void* dev_id);

  其中handle对应的函数执行于中断上下文,thread_func对应的函数执行于内核线程,如果handle的返回值是IRQ_WAKE_THREAD,内核会自动调度对应的线程执行thread_func函数
  若flags中设置IRQF_ONESHOT标志,内核会自动在中断上下文中屏蔽该中断号,防止上半部无法清除中断,导致上半部一退出,瞬间大量中断同时发生导致异常。

三、中断里不能睡眠

  睡眠会导致进程切换,中断上下文没有进程的概念,中断有自己的内核栈,进程使用的task_struct结构体信息,schedule调度就是去访问这些task_struct,抢占一个进程运行另一个进程,所以中断里无法进行系统调度,如果在中断里睡眠,进程将无法被调度,从而引发内核崩溃
  假设中断里可以睡眠,也就是可以进行进程调度,如果此时内核正执行到临界区,处于加锁状态(一般是自旋锁),切换另一个进程执行到此处无法加锁,就会等待下去,造成死锁。

No pains, no gains

标签:中断,irq,void,Linux,int,tasklet,驱动,my
来源: https://blog.csdn.net/weixin_43943298/article/details/122523542

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有