目录

目录

linux 中断之irq

前言

中断注册与释放

第一个参数:irq相关

查看/proc/interrupts

查看/proc/stat

Probe IRQ Number问题

第二个参数:Handler相关

第三个参数:flag相关

第四个参数:dev_name

第五个参数:dev_id


 

linux 中断之irq

关键字:中断号、共享中断、dev_id、proc文件系统、中断处理函数、中断标识、tasklet、workqueue

前言

前面一篇文章已经对中断做了一些简单介绍《关于Linux中断一些思考》,但是中断是怎么告诉内核(kernel)的呢?这里我们带着问题去了解一下irq的调用。

所谓中断它其实是硬件产生了一个信号,然后告诉内核要去处理它。Linux处理中断很像用户空间(user space)处理信号(signal)一样,内核启动的时候注册了这个中断之后,当中断产生的时候(由硬件触发)就会调用对应的中断处理函数。这里需要注意在中断处理的时候要考虑公共资源的竞态和并发问题(这个后续文章会单独介绍)

在介绍中断注册前,先了解一下中断线(interrupt lines),像IO资源一样,系统的中断线也是非常有限和宝贵的资源,所以会出现多个模块共享一个中断线的情况。模块在使用中断线前必须先注册它,而且用完之后必须释放它(不然在共享同一个中断的模块无法使用这个中断)。

 

中断注册与释放

int request_irq(unsigned int irq,
                irqreturn_t (*handler)(int, void *, struct pt_regs *),
                unsigned long flags, 
                const char *dev_name,
                void *dev_id);

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

说明:返回值,成功为0,失败负值,-EBUSY表示一个驱动正在使用这个中断线(不常见);

unsigned int irq:请求的中断号,这个是系统预先设定好的,可以在irq.h中找到对应值;

irqreturn_t (*handler) (int ,void *, struct pt_regs *):中断处理函数指针;

unsigned long flags:中断标志位,可以是一个或者多个,它跟中断处理相关;

const char *dev_name:中断名称,这个可以在系统运行后查看/proc/interrupts找到对应名称

void *dev_id:主要用在共享型中断线中,它一般指向对应驱动的私有数据,以便中断释放的时候,只释放指定驱动中的中断,它具有唯一性;当它不是共享型的时候,可以设定为NULL,但是建议将它指向设备结构体;

提示:中断处理的安装可以在驱动模块的初始化中,也可以在设备被打开使用的过程中。通常建议在设备打开使用的时候执行中断处理的请求,因为系统中的中断号本来就有限。当一个模块在初始化的时候请求IRQ,它将会阻止其他任何驱动使用这个中断,即使这个中断没有被用到。而在打开设备的时候请求中断,将会把资源极大利用起来。

所以最佳调用request_irq的位置是设备被打开的时候,而最佳关闭中断free_irq的时候是最后关闭设备的时候。但是这个也不是绝对的,针对一些短的请求,可以将它放到初始化函数中。

第一个参数:irq相关

如果想要查看相关中断的一些状态或者使用情况怎么办?Linux在proc系统中提供了两个文件供查看。

查看/proc/interrupts

cat  /proc/interrupts
    CPU0 CPU1
0: 4848108 34 IO-APIC-edge timer
2: 0 0 XT-PIC cascade
8: 3 1 IO-APIC-edge rtc
10: 4335 1 IO-APIC-level aic7xxx
11: 8903 0 IO-APIC-level uhci_hcd
12: 49 1 IO-APIC-edge i8042
NMI: 0 0
LOC: 4848187 4848186
ERR: 0
MIS: 0

其中第一列是IRQ 号,可以看到有些中断号没有显示出来,这代表它在当前执行cat这个时间点没有被用到;通常中断处理在第一个CPU中,这是为了最大限度利用缓存;最后两列中断控制器和中断名(上面提到的dev_name);中间的CPU列代表中断调用次数;

查看/proc/stat

通过/proc/interrupts可以查看当前运行中的中断信息,但是如果中断退出后要查看它的历史使用情况怎么办呢?这里可以通过/proc/stat来查看,用cat会看到它有很多行输出,这里我们只关注intr行

intr 5167833 5154006 2 0 2 4907 0 2 68 4 0 4406 9291 50 0 0

其中第一个数字表示所有的中断调用统计总数5167833次,后面依次是中断号为0,1,2,3...,从这里看看到从系统启动到执行当前命令后所有中断被调用的次数以及总和。

Probe IRQ Number问题

为什么提出自动探测interrupt number问题?因为有时候用户的关注点不在interrupt number而在他想让硬件产生中断并处理它,也就说并不关注你是什么中断,只要能作用就行了。但是也不能随便给一个中断号就草草了事,不然就会有1、中断号出现驴唇不对马嘴,后续看代码的人异常痛苦;2、中断冲突,本来这个这个中断号是某一个中断专属的,结果被你抢去用了,这样会导致一系列问题。所以针对这种不关心具体中断号意义的用户,内核提供了一个probe 接口。

  • 自动处理

内核自带api:

unsigned long probe_irq_on(void);

int probe_irq_off(unsigned long);

第一个probe_irq_on函数,输入参数为void,返回一个unsigned long 中断号。以下是根据端口地址来自动分配中断号的代码片段,这块代码会跟具体的平台相关。

if (short_irq < 0) /* not yet specified: force the default on */
    switch(short_base) {
        case 0x378: short_irq = 7; break;
        case 0x278: short_irq = 2; break;
        case 0x3bc: short_irq = 5; break;
}
  • 加载时指定

除了自动侦测还有一种是,在模块加载的时候指定它,这种方式非常灵活,适用于调试阶段

insmod ./short.ko irq=x

 

第二个参数:Handler相关

request_irq的第二个参数是中断处理函数,也就是中断发生之后,要执行的代码。先介绍这个函数

irqreturn_t (*handler)(int irq, void *dev_id, struct pt_regs *pt_regs)

输入参数:

第一个参数irq就是中断号;

第二个参数dev_id通用指针在irq_flag为shared的时候发挥作用,因为在共享中断号在中断处理do_IRQ中,需要区分具体是哪个中断,然后处理对应的中断。(打个比方,中断号irq对应具体的班级,dev_id对应班级里面的哪个学生学号,就像学校广播找人,先说哪个班--irq,然后说哪个学生--dev_id);

第三个参数pt_regs,主要用来记录CPU进入中断前保存CPU的上下文,仅做调试和监视作用,实际中断中使用非常少;

返回值:

enum irqreturn{

    IRQ_NONE, //中断处理程序检测到一个中断,但该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回这个

    IRQ_HANDLED,//中断处理程序被正确调用且确实时它对应的设备产生了中断,返回这个

    IRQ_WAKE_THREAD,

};

如果在执行过程中想disable某一个或所有中断,又或者enable某一个或所有中断呢?Linux内核也为我们提供了对应的接口。

  • 某一个中断
void disable_irq(int irq);
void disable_irq_nosync(int irq);
void enable_irq(int irq);

这里输入参数都是irq,就是指定要disable或enable的中断号。这里着重介绍disable_irq和disable_irq_nosync这两个差异,从nosync这个字面意思可以看到它是简单粗暴的方式返回。事实上,disable_irq是既disable指定的中断也会等待当前正在处理的中断(这里要注意,如果执行这个api的线程用到了handler里面用到的一些资源,比如spinlock,它会导致系统死锁);disable_irq_nosync会快速返回,但是它可能会导致系统产生竞态问题(由于它处理简单粗暴,如果handle里面有一些公共资源没有释放,其他进程再用就会拿不到资源)

  • 所有中断
void local_irq_save(unsigned long flags);
void local_irq_disable(void);

void local_irq_restore(unsigned long flags);
void local_irq_enable(void);

在disable所有中断之前先保存当前中断的状态给一个flags,这里有人可能会想,这里为什么不是一个指针类型?通过阅读local_irq_save的实现发现它是一个define形式,也就是说它就是赋值给这个flag。建议读者可以看看源码/linux/include/linux/irqflags.h,这样会有一个比较深刻的认识。

那既然熟悉了handle,是不是我们可以随心所欲的在handle写自己想要的代码?情况并非如此,handle函数是有限制的。

Handle的使用注意事项:

  1. 不可以和用户空间传递数据--因为传动动作不是在处理的上下文中执行的;
  2. 不允许执行sleep操作,比如wait_event、内存分配、锁定一个semaphore;
  3. 不允许任务调度

总之,耗时、pending的操作不能在handle中使用,handle中的操作要少、快;如果有耗时或者pending的操作,建议使用tasklet和workqueue,这部分属于中断Bottom half,后续文章单独介绍它怎么使用。简单说明两者注意事项,tasklet也是执行比较快的操作,而且属于原子操作型;workqueue可以用于延时性要求比较高的操作,不必追求原子性,但是依然不能用于跟用户空间的数据拷贝动作,因为他们不在同一个处理上下文中。

第三个参数:flag相关

IRQF_DISABLED:如果被指定它,那么它会禁用其他所有中断,如果不设定,程序可以和其他中断程序同时运行;

IRQF_TIMER:为系统的定时器中断而准备的。

IRQF_SHARED:表示多个中断处理程序可以共享中断号,即如果没有设定只有一个中断程序使用这个中断号,如果设定了,多个中断程序可以共享这个中断号。

第四个参数:dev_name

可以根据自定义来,在上面介绍/proc/interrupts中可以看到它。

第五个参数:dev_id

前面也已经做了相关介绍,主要是用在共享中断中,在执行中断do_IRQ中识别身份的唯一标识,方便找到指定的handle,也方便在enable、disable、free等操作中找到指定的中断,并对它进行相关处理。

 

 

 

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐