Linux设备驱动 之 中断处理程序

注册中断处理程序

中断处理程序是管理硬件驱动程序的组成部分;如果设备使用中断,那么相应的驱动程序就注册一个中断处理程序;

驱动程序通过request_irq()函数注册,并且激活给定的中断线,以处理中断;request_irq()函数可能会睡眠,因此,不能再中断上下文或者其他不行允许阻塞的代码中调用该函数,因为在该函数调用过程中会使用kmalloc()来分配内存,而函数kmalloc()是可以睡眠的;

1 int __must_check
2 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
3         const char *name, void *dev)

参数irq标识要分配的中断号,对某些设备,这个值是预先确定的;而对大多数其他设备来说,这个值要么尅通过探测获取,要么可以通过编程动态获取;

参数handler指向这个中断的实际中断处理程序,当操作系统接收到中断,该函数就会被调用;

1 typedef irqreturn_t (*irq_handler_t)(int, void *)

参数flag可以为0,也可能是下列一个或者多个标志位的掩码;

IRQF_DISABLED-该标志被设置后,意味着内核在处理中断处理程序本身期间,要禁止所有其他中断;如果不设置,中断处理程序可以与除本身以外的其他任何中断同时运行;多数中断处理程序不会设置该位;这种用法留给希望快速执行的轻量级中断;

IRQF_SAMPLE_RANDOM-设备产生的中断对内核熵池有贡献,内核熵池负责提供从各种随机事件导出真正的随机数;

IRQF_TIMER-该标志是特别为系统定时器中断处理而准备的;

IRQF_SHARED-该标志标明可以在多个中断处理程序之间共享中断线;在同一个给定线上注册的每个处理程序必须指定这个标记;否则,在每条线上只能有一个处理程序;

参数name是与中断相关的ascii表示;

参数dev用于共享中断线,当一个中断处理程序需要释放时,dev将提供唯一的标志信息,以便从共享中断线的多个中断处理程序中删除指定的那一个;如果无须共享中断线,那么该函数赋值为NULL即可;但是如果中断线是被共享的,那么就必须传递唯一信息;实践中往往会通过它传递驱动程序的设备结构;这指针是唯一的,而且有可能在中断处理程序内被使用到;

释放中断处理程序

卸载驱动程序时,需要注销相关的中断处理程序,并释放中断线,需要调用下面函数:

1 void *free_irq(unsigned int, void *)

如果指定的中断线不是共享的,那么该函数删除处理程序时还将禁止这条中断线,如果中断线是共享的,则仅删除dev所对应的处理程序,而这条中断线本身只有在删除了最后一个处理程序时才会被禁用;

编写中断处理程序
1 typedef irqreturn_t (*irq_handler_t)(int, void *)

第一个参irq就是这个处理程序要响应的中断号;

第二个参数dev是一个通用指针,它与中断处理程序注册时传递给request_irq()的参数dev必须一致;对于每个设备而言,设备结构是唯一的,而且也可能在中断处理程序中用到,因此,常常被看做是设备指针dev;

中断处理程序的返回值是一个irqreturn_t类型,对着着两个不同的返回值:IRQ_NONE和IRQ_HANDLED;当中断处理程序检测到一个中断,但是该中断对应的设备并不是在注册处理函数期间指定的产生源时,返回IRQ_NONE;当中断处理程序被正确调用时,且确实是它所对应的设备产生了中断时,返回IRQ_HANDLED;

中断处理程序是无须重入的;当一个给定的中断处理程序正在执行时,相应的中断线在所有处理器上都会被屏蔽掉,以防止在同一个中断线上接收另外一个新的中断;通常情况下,其他中断都是被打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断线总是被禁止的;

中断控制

Linux内核提供了一组接口用于操作机器上的中断状态;这些接口为我们提供了能够禁止当前处理器的中断信息,或者屏蔽掉整个机器的一条中断线的能力;

控制中断系统的原因归根结底还是需要提供同步;通过禁止中断,可以确保摸个中断处理程序不会抢占当前的代码;此外,禁止中断还可以禁止内核抢占;然而,不管是禁止中断还是禁止内核抢占,都没有提供任何保护机制来防止来自其他处理器的并发访问;Linux支持多处理器,因此,内核代码一般都需要获取某种锁,防止来自前提处理器对共享数据的并发访问;获取这些所得同时也伴随着禁止本地中断;锁提供保护机制,防止来自其他处理器的并发访问,而禁止中断提供保护机制,则是防止来自其他中断处理程序的并发访问;

禁止和激活当前处理器上的本地中断的方式如下:

1 local_irq_disable()
2 local_irq_enable()

如果在调用local_irq_disable()之前就已经禁止了中断,那么该例程会带来危险;同样相应的local_irq_enable()也存在风险,它们将无条件的激活中断,尽管这些中断可能在开始时是关闭的;

内核提供了一种机制把中断恢复到以前的状态而不是简单的禁止或者激活;

1 local_irq_save(flags)
2 local_irq_restore(flags)

flags参数必须定义为unsigned long类型,是以值传递的,改参数包含了具体体系结构的数据,也就是包含中断系统的状态;因为至少有一种体系结构把栈信息与值相结合,所以这两个函数的调用必须在同一个函数中;

 

某些情况下,不需要禁止整个处理器上的所有中断,只禁止整个系统中一条特定的中断线就足够了;这就是所谓的屏蔽掉一条中断线;Linux提供了四个接口:

1 void disable_irq(unsigned int irq)
2 void disable_irq_nosync(unsigned int irq)
3 void enable_irq(unsigned int irq)
4 void synchronize_irq(unsigned int irq)

前两个函数禁止中断控制器上的指定中断线,即禁止给定中断向系统中所有处理器的传递;另外,函数只有在当前正在执行的所有处理器程序完成后,disable_irq()才能返回;因此,调用者不仅要确保不再指定线上传递新的中断,同时还要确保所有已经开始执行的处理程序已全部退出;disable_irq_nosync则不会等待当前中断处理程序执行完毕;

synchronize_irq等待一个特定的中断处理程序退出;如果该吹了程序正在执行,那么该函数必须退出后才能返回;

这些函数的调用可以嵌套,但在一条指定的中断线上,对disable_irq或者disable_irq_nosync的每次调用,都要对应调用一次enable_irq,只有在对enable_irq完成最后一次调用之后,才能真正激活中断线;

所有者三个函数可以从中断或者进程上下文中调用,而且不会睡眠;

禁止多个中断处理程序共享的中断线是不合适的,禁止了中断线就禁止了这条线上所有设备的中断传递,所以新设备驱动程序趋向于不使用这些接口;

 

通常有必要了解中断系统的状态,如中断是禁止的还是激活的,或者当前是否处于中断上下文的执行状态中;

Linux中提供了两个宏用来检查内核当前的上下文:

1 in_interrupt()
2 in_irq()

in_interrupt最有用,如果内核处于任何类型的中断处理中,他返回非0,说明内核此刻正在执行中断处理程序,或者正在执行下半部的处理程序;

in_irq只有在内核确实正在执行中断处理程序时才返回非0;

通常情况下,需要检查自己是否处于进程上下文中,也就是说,希望确保自己不在中断上下文中,这种情况很常见,因为代码要做一些睡眠这样只能从进程上下文中做的事;如果in_inerrupt()返回0,则此刻内核处于进程上下文中;

 

posted @ 2019-10-29 21:37  AlexAlex  阅读(1416)  评论(0编辑  收藏  举报