linux下的中断

什么是中断

  • 中断是指在CPU正常运行期间,由内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,保存当前状态,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去,恢复之前的状态,继续运行被暂时中断的程序。
  • 中断可为外部中断和内部中断。外部中断由外部硬件设备产生(又叫硬件中断、异步中断)和​​​​,内部中断由CPU本身产生(又叫异常中断,同步中断)。
  • 中断可分为向量中断非向量中断。向量中断是不同中断分配不同的中断号,非向量中断则省多个中断共享一个入口地址。
  • 中断又可分为硬中断软中断。硬中断是外设引发的强行中断。软中断则是有正在进行的进程所产生的,通常为 I/O 口的请求。​​​​​​​

中断又分为顶半部和底半部:

  • 中断会打断进程正常的调度和运行,然而中断又往往比较耗时,与系统实时性不相符。所以linux内核将中断分为了顶半部和底半部,(上半部)解决耗时的问题的来提高系统的实时性,(下半部)完成中断所需要的任务。
    •  顶半部:读取寄存器中的中断状态,清除中断标志,将底半部挂到设备底半部的执行队列中,即可返回。其它比较耗时的内容就可以放在底半部完成,这样就符合耗时小的要求。(当然有些耗时比较小的可以直接在顶半部完成,就不需要底半部了)。
    • 底半部:完成中断所需要处理的事情。这个过程是可以被其他中断打断,顶半部是不能被其他中断打断的。下半部则处理相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

中断的申请:成功返回0,返回-EINVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

  • int request_irq(unsigned int irq, irq_handler_t handler,
                             unsigned long irqflags, const char *name, void *dev)
  • irq         第一个参数,所需要申请的硬件中断号
  • handler 第二个参数,是向系统注册的中断处理顶半部的函数,是一个回调函数,中断发生                                         时,系统调用这个函数,dev参数将被传递给它。
  • irqflags 第三个参数,中断处理属性。中断的触发方式或者处理属性。
    • 触发方式:IRQF_TRIGGER_RISING(上升沿)、IRQF_TRIGGER_FALLING(下降沿)、IRQF_TRIGGER_HIGH(高电平)、IRQF_TRIGGER_LOW(低电平)等。
    • 处理属性:IRQF_SHARED (共享中断)、IRQF_DISABLED(禁止其他中断)。
  • name  第四个参数,设备的驱动名称,可以在 /proc/interrupt 查看。
  • dev     第五个参数,传递给中断函数的使用数据,一般设置为这个设备的结构体或NULL。

中断的释放:void free_irq(unsigned int irq, void *dev),参数跟以上的 irq 和 dev 一致。

底半部中断:常见的底半部的有两种方式tasklet(小任务)、工作队列。

小任务tasklet:其实是一个软中断,一般都是在顶半部快结束了时执行,小任务不能睡眠,不能在小任务中使用信号量或者其它产生阻塞的函数。使用方法分为三步:

  • void my_tasklet_func(unsigned long); //定义一个底半部处理函数
  • DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定义一个tasklet结构my_tasklet,与 my_tasklet_func 函数相关联
  • tasklet_schedule(&my_tasklet);  //在顶半部调度,上一步已经完成和底半部的关联,这里就可以直接调用,会跳转到底半部处理函数去。

tasklet使用示范如下:

void my_tasklet_func(unsigned long data); //定义一个底半部处理函数
DECLARE_TASKLET(my_tasklet,my_tasklet_func,data); //定义一个tasklet结构my_tasklet,与 my_tasklet_func 函数相关联

//中断底半部处理函数
void my_tasklet_func(unsigned long)
{
    ...
}

//中断顶半部
irqrerurn_t xxx_handle(int irq, void *dev_id)
{
    ...
    tasklet_schedule(&my_tasklet);  //在顶半部调度。
    ...
}

xxx_init()
{
    ret = requset_irq(xxx_irq, xxx_handle, 0, "xxx", NULL);
}

void xxx_exit(void)
{
    free_irq(xxx_irq, xxx_handle);
}

module_init(xxx_init);
module_exit(xxx_exit);

工作队列 workqueue是另外一种将中断的部分工作推后的一种方式,它可以实现一些tasklet不能实现的工作,比如工作队列机制可以睡眠。这种差异的本质原因是,在工作队列机制中,将推后的工作交给一个称之为工作者线程(worker thread)的内核线程去完成。也就是说由工作队列所执行的中断代码会表现出进程的一些特性,最典型的就是可以重新调度甚至睡眠。使用如下:

  • struct work_struct my_work //定义一个工作队列
  • viod my_wq_func(struct work_struct *work) //定义一个底半部处理函数
  • INIT_WORK(&my_work, my_wq_func); //初始化并将工作队列和处理函数绑定
  • schedule_work(&my_work) //马上调度work,一旦工作线程被唤醒,这个工作就会被执行

工作队列使用示范如下:

struct work_struct my_work; //定义一个工作队列
viod my_wq_func(struct work_struct *work); //定义一个底半部处理函数

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

//中断顶半部
irqrerurn_t xxx_handle(int irq, void *dev_id)
{
    ...
    schedule_work(&my_work) //马上调度work,一旦工作线程被唤醒,这个工作就会被执行
    return IRQ_HANDLED;
}

xxx_init()
{
    INIT_WORK(&my_work, my_wq_func); //初始化并将工作队列和处理函数绑定
    ret = requset_irq(xxx_irq, xxx_handle, 0, "xxx", NULL);
}

void xxx_exit(void)
{
    destroy_workqueue(&my_work);
    free_irq(xxx_irq, xxx_handle);
}

module_init(xxx_init);
module_exit(xxx_exit);

猜你喜欢

转载自blog.csdn.net/weixin_42432281/article/details/124422449