欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux中断及处理方式

程序员文章站 2022-04-18 08:34:41
...

Linux中断

中断

Linux中断及处理方式

中断的处理

处理方式

  1. 保护现场
  2. 中断处理函数
  3. 恢复现场

arm对中断的处理流程

  1. 初始化
    a.设置中断源,使其可以产生中断
    b.设置中断控制器,是否屏蔽,优先级等属性
    c.使能中断
  1. 执行其他程序
  2. 产生中断
  3. CPU检查异常,处理中断,不同的中断跳去不同的地址执行(中断也是一种异常,参考异常向量表)

Linux中断相关API

  1. 中断号

每个中断都有一个中断号,通过中断号即可区分不同的中断,有的资料也把中断号叫做中断线。在Linux内核中使用一个int变量表示中断号。

  1. 申请中断

在 Linux 内核中要想使用某个中断是需要申请的,request_irq函数用于申请中断。

int request_irq(unsigned int irq,       /*中断号*/
                irq_handler_t handler,  /*中断处理函数*/
                unsigned long flags,    /*中断标志*/
                const char *name,       /*中断名称*/
                void *dev)/*dev用来区分不同的中断,一般情况下将dev 设置为设备结构体*/

常用的几个标志如下表: 更多请查看 /include/linux/interrupt.h 中相关宏定义

标志 描述
IRQF_SHARED 多个设备共享一个中断线,共享的所有中断都必须指定此标志。如果使用共享中断的话,request_irq函数的dev参数就是唯一区分他们的标志。
IRQF_ONESHOT 单次中断,中断执行一次就接触
IRQF_TRIGGER_NONE 无触发
IRQF_TRIGGER_RISING 上升沿触发
IRQF_TRIGGER_FALLING 下降沿触发
IRQF_TRIGGER_HIGH 高电平触发
IRQF_TRIGGER_LOW 低电平触发

3.释放中断

有申请就有释放

void free_irq(unsigned int irq, /*中断号*/
                void *dev)      /*使用共享中断号的话,dev区分具体设备*/
  1. 中断处理函数
irqreturn_t (*irq_handler_t) (int, void *)

返回值为 irqreturn_t 的类型

enum irqreturn {
    IRQ_NONE = (0 << 0),
    IRQ_HANDLED = (1 << 0),
    IRQ_WAKE_THREAD = (1 << 1),
    };
 typedef enum irqreturn irqreturn_t;

一般中断服务函数返回值使用如下形式:
return IRQ_RETVAL(IRQ_HANDLE);

  1. 中断开关
    中断使能与禁止
void enable_irq(unsigned int irq);
void disable_irq(unsigned int irq);

全局中断开关

local_irq_enable();
local_irq_disable();

中断处理的原则

  1. 中断不得嵌套,嵌套中断会导致保护现场的栈越来越多,最终爆炸
  2. 中断处理函数时间越短越好,时间太长会导致其他线程不能运行,表先出来就是卡顿

然而,总有一些中断需要很长的时间的去处理,linux内核就引入了上半部和下半部的机制。

Linux中断及处理方式

上半部:接受中断,它就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。顶半部简单快速,执行时禁止一些或者全部中断。

下半部:上半部执行完毕后会将中断打开,然后随即执行下半部,下半部在先执行过程中是可以响应中断的。

Linux中断及处理方式

上/下半部划分原则

  • 如果一个任务对时间非常敏感,将其放在上半部中执行
  • 如果一个任务和硬件有关,将其放在上半部中执行
  • 如果一个任务要保证不被其他中断打断,将其放在上半部中执
  • 其他所有任务,考虑放置在底半部执行

下半部实现和处理

  • 软中断

建议使用tasklet

  • tasklet

如果下半部处理的时间不太长,处于可以接受程度,可以使用tasklet处理下半部。tasklet是由软中断实现的。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。

Linux中断及处理方式

tasklet在内核中的定义形式

struct tasklet_struct
{
 struct tasklet_struct *next; /* 下一个 tasklet */
 unsigned long state; /* tasklet 状态 */
 atomic_t count; /* 计数器,记录对 tasklet 的引用数 */
 void (*func)(unsigned long); /* tasklet 执行的函数 */
 unsigned long data; /* 函数 func 的参数 */
 }
 

步骤:

定义并初始化tasklet

void tasklet_init(struct tasklet_struct *t, /*要初始化的tasklet*/
                void (*func)(unsigned long),/*tasklet执行的函数*/
                unsigned long data)         /*func的参数*/
                
也可以直接使用宏定义 定义并初始化tasklet
DECLARE_TASKLET(name, func, data)        
                

在中断处理函数上半部完成后调度tasklet

void tasklet_schedule(struct tasklet_struct *t);
  • 工作队列

当下半部处理的时间相对较长时,可以采用工作队列的方式。中断处理在处理完上半部开放中断以后,下半部会被内核线程接管,参与线程调度。所以使用工作队列处理的下半部中可以有堵塞或者睡眠情况。

内核线程kwork会从工作队列work queue中取出一个个work去执行。 我们只需要将下半部处理函数,封装成work结构体并添加到work queue中即可。

Linux用 work_struct 来表示一个工作

struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func; /* 工作队列处理函数 */
    };

创建并初始化一个工作

两种方式
struct work_struct testwork;
INIT_WORK(&testwork, testwork_func_t);

 #define DECLARE_WORK(work_struct, func)

和 tasklet 一样,工作也是需要调度才能运行的

bool schedule_work(struct work_struct *work);
  • 新技术:threaded_irq

以前用work来线程化的处理内核,一个worker线程只能由一个CPU执行,在多cpu中凭什么让一个cpu处理所有的中断。
threaded_irq 可以为每一个中断创建一个内核进程,参与进程调度。

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
			            irq_handler_t thread_fn, unsigned long irqflags,
			            const char *devname, void *dev_id)

这个函数允许只提供一个 thread_fn 可以将 handler设置为NULL

当中断触发时,系统会为其创建内核线程,执行thread_fn。

相关标签: 设备驱动