中断
1、注册中断处理函数
如果设备使用中断,则相应的驱动程序就要注册一个是中断处理程序。
驱动程序通过request_irq() 函数向内核注册一个中断处理程序。
int
request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *),
unsigned long irqflags, const char * devname, void *dev_id)
第一个参数irq 表示要分配的中断号。对于某些设备,对于传统PC设备上的系统时钟或键盘,这个值通常是预先确定的。对于大多数其他设备来说,可以使用irq_of_parse_and_map() 函数通过设备树获得,也可使用gpio_to_irq() 函数通过gpio号得到。
第二个参数handler 是指向中断处理函数的指针。
第三个flags 可以为0,也可以是一个或多个标志的位掩码。位掩码定义在 <linux/interrupt.h> 中,通常都是形如IRQF_XXX 的宏。
第四个参数name 时候中断名字。会被 /proc/irq和/proc/interrupts 文件使用,以便于用户通信。
第五个参数dev 用于共享中断线。当一个中断处理程序需要释放时,dev将提供唯一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。另外,内核每次调用中断处理程序时,这个指针会作为中断处理程序的第二个参数传递过去,通常用来传递驱动的设备结构。
request_irq() 函数执行成功返回0,失败返回非0。
注意:request_irq() 函数可能会引起睡眠。因此,不能在中断上下文或其他不允许阻塞的代码中调用该函数。在注册中断的过程中,内核需要在/proc/irq文件中创建一个与中断固定的项,函数proc_mkdir() 就是用来创建这个procfs项的。proc_mkdir() 通过调用函数proc_create() 对新的profs项进行设置,proc_create() 会调用函数kmalloc() 来请求分配内存。kmalloc() 是一个可以引起睡眠的函数。
2、释放中断处理函数
卸载驱动时,需要注销相应的中断处理程序,并释放中断线:
void free_irq(unsigned int irq, void *dev_id)
如果指定的中断线不是共享的,在删除中出处理函数的同时会禁用这条中断线。如果是共享的,那么仅删除dev参数对应的处理程序,只有当中断线上的最后一个处理程序被删除时,中断线才会被禁用。
3、中断处理函数
static irqreturn_t (*handler)(int irq, void * dev)
第一个参数irq为中断处理函数要相应的中断号。没啥用,可能用于打印日志。
第二个参数dev可作为一个标志,在共享同一处理程序时区分多个设备。或者在非共享情况下,用来将设备结构传递给处理函数。不能给共享的中断处理程序传递NULL。
返回值irqreturn_t其实是一个int型,2.6之前内核的中断处理函数没有返回值。中断处理函数的返回值通常有两个:IRQ_NONE和IRQ_HANDLED。IRQ_NONE表示该中断对应的设备并不是在注册处理函数期间指定的产生源。IRQ_HANDLED表示中断确实是对应的设备产生的。
当一个中断处理程序正在执行时,相应中断线会被屏蔽以防止在同一中断线上接收另一个新的中断。其他所有中断都是打开的。
4、中断上下文
当中断发生时,在处理中断的过程中,current 宏会指向被中断的进程。
曾经,中断处理程序没有自己的栈,他们共享所中断进程的内核栈,大小是2页,8KB。后来,内核增加了一个选项,把内核栈大小从2页减少为1页,而每个处理器拥有1个专门提供给中断处理程序的栈的页。
5、中断处理机制实现
中断处理系统依赖于处理器、中断控制器的类型、体系结构和机器本身。
当设备产生中断,总线会把电信号发送给中断控制器,如果中断线是激活的,中断控制器会把中断信号发送给处理器(在大多数体系结构中,中断控制器是给处理器的特定管脚发送一个电信号)。除非处理器上禁用该中断,否则,处理器会立即停止它正在做的事,并且关闭中断系统,然后跳到内存预定义的位置(中断函数入口)执行代码。
每条中断线都有对应的唯一的一个预定义位置(中断函数入口)。这样当处理器跳到一个预定义位置时,内核也就知道了所接受中断的中断号了。初始入口点只是在栈中保存中断号,并存放被中断任务的信息(这些信息会先保存在寄存器中),然后内核调用函数do_IRQ() .
unsigned int do_IRQ(struct pt_regs regs)
在计算出中断号后,do_IRQ() 函数对所接受的中断做应答,禁用这条中断线,查找中断线上有效的中断处理函数,如果有,则调用handle_IRQ_event() 来运行注册的中断处理函数。当函数返回,回到do_IRQ() 后,do_IRQ() 在进行一些清理工作,然后返回初始入口点。初始入口点在跳转到ret_from_intr() 函数。
ret_from_intr() 函数用汇编实现,会在恢复现场之前检测need_resched标志和perrmpt_count来决定是否要调用schedule() 函数进行调度。如果不需要调度或者调度返回后,则恢复原先进程的现场。
6、/proc/interrupt
procfs 是一个虚拟的文件系统,它只存在于内核内存,一般安装于/proc目录。在procfs中读写文件都要调用内核函数,这些函数模拟从真实文件中读或写。
/proc/interrupt 文件存放系统中与中断相关的统计信息。例如:
root@OpenWrt:~# cat /proc/interrupts
CPU0
5: 9695183 MIPS 5 10100000.ethernet
7: 687053832 MIPS 7 systick
9: 0 INTC 1 10000100.timer
15: 0 INTC 7 Ralink_DMA
20: 11 INTC 12 serial
22: 0 INTC 14 10130000.sdhci
25: 5 INTC 17 gsw
26: 1 INTC 18 ehci_hcd:usb1, ohci_hcd:usb2
40: 4496 GPIO 8 lirc_rpi
ERR: 0
其中,第一列是中断线。没有显示未注册中断处理函数的中断线。
第二列是接收中断数目的计数器,存储着中断次数。
第三列是处理这个中断的中断控制器。
第四列是中断相关的设备名字。这个名字是在使用request_irq() 函数进行中断处理函数注册时,传递给devname参数的名字。
procfs的代码位于fs/proc中。其中,提供/proc/interrupts的函数是与体系结构相关的,叫做show_interrupts()。
7、中断控制
在 <asm/system.h>和<asm/irq.h> 中可以找到与体系结构相关的中断控制例程。
中断控制的实质的同步,通过禁止中断可以确保中断处理程序不会抢占当前代码,禁止中断还可以禁止内核抢占。然后,禁止中断缺不能防止SMP系统的并发访问,Linux中内核代码一般都要获取某种锁,用来防止其他处理器对共享数据的并发访问,而获得这些锁的同时意味着禁止本地中断。
7.1、禁止和激活中断
local_irq_disable();
local_irq_enable();
这两个函数依赖于体系结构,作用是无条件禁止和激活全局中断。一般成对出现,如果连续调用两次local_irq_disable() 随后再调用local_irq_enable() 会出现潜在危险,因为local_irq_enable() 会直接激活全局中断。因此,通常会用以下函数在禁用中断之前保存中断系统的状态,在激活的时候再把中断恢复为之前的状态即可:
unsigned long flags;
local_irq_save(flags); //保存当前中断状态,随后禁止中断
local_irq_restore(flags); //恢复中断前的状态
其中flags 是以值传递的。local_irq_save(flags) 和 local_irq_restore(flags) 的调用必须在同一个函数中进行。
在旧版本的内核中:使用cli() 函数可以禁止系统中所有处理器上的中断,使用sti() 函数可以激活所有中断。这两个接口在2.5版本的内核之后被取消了,所以,对于中断同步现在必须结合使用本地中断控制和自旋锁。
7.2、禁止指定中断线
Linux提供了四个接口用来禁止一条特定的中断线:
void disable_irq(unsigned int irq);
void disable_irq_nosync(unsigned int irq);
void enable_irq(unsigned int irq);
void synchronize_irq(unsigned int irq);
前两个函数禁止中断控制器上指定的中断线。disable_irq() 函数只有在当前正在执行的所有处理函数完成后才返回,因此,要保证指定中断线上没有新的中断,并且所有已经开始执行的处理程序全部退出。disable_irq_nosync() 函数不会等待当前中断处理程序执行完毕。
synchronize_irq() 函数等待一个特定的中断处理程序退出,只有中断处理程序退出后synchronize_irq() 函数才能返回。
对一条指定的中断线调用disable_irq() 和 disable_irq_nosync() 函数,每次调用都需要相应地调用一次enable_irq(),只有最后一次enable_irq() 被调用时才真正重新激活中断线。这三个函数不会引起睡眠。
7.3、中断系统的状态
如果想知道中断系统是禁止的还是激活的或者处于上下文的执行状态,可以使用以下函数:
irqs_disabled()
in_interrupt()
in_irq()
如果本地处理器上的中断系统被禁用,irqs_disabled() 函数返回非0,否则返回0。
如果内核处于任何的中断处理中(包括下半部处理),in_interrupt() 函数返回非0,否则返回0。
如果内核处于中断处理函数中,in_irq() 函数返回非0,否则返回0。
8、中断控制函数(表)