基于内核版本 2.6.30.4 异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction ,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能。其中的中断也占有很大的一部分。例如下面的这几种情况:
//下面函数在/arch/arm/kernel/trap.c中 void __init trap_init(void) { return; } void __init early_trap_init(void) { unsigned long vectors = CONFIG_VECTORS_BASE; extern char __stubs_start[], __stubs_end[]; extern char __vectors_start[], __vectors_end[]; extern char __kuser_helper_start[], __kuser_helper_end[]; int kuser_sz = __kuser_helper_end - __kuser_helper_start; /* * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */ memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz); /* * Copy signal return handlers into the vector page, and * set sigreturn to be a pointer to these. */ memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes, sizeof(sigreturn_codes)); flush_icache_range(vectors, vectors + PAGE_SIZE); modify_domain(DOMAIN_USER, DOMAIN_CLIENT); } 这个函数才是真正要用到的,在init/mian.c中可以找到,调用了trap_init(),而early_trap_init()函数在setup_arch(&command_line)函数中调用。在Linux/arch/arm/kernel/setup.c void __init setup_arch(char **cmdline_p) { struct tag *tags = (struct tag *)&init_tags; struct machine_desc *mdesc; char *from = default_command_line; ................ early_trap_init(); } 这样我们就明白了trap_init()函数的具体调用过程了。下面我们具体来看一下这个trap_init()函数,确切的说是earl_trap_init()函数。earl_tarp_init函数(代码在arch/arm/kernel/traps.c中)被用来设置各种异常的处理向量,包括中断向量。所谓“向量”,就是一些被安放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上的指令。ARM架构的CPU的异常向量基址可以是0x00000000,也可以是0xffff0000,Linux内核使用后者。earl_trap_init函数将异常向量复制到0xffff0000处,我们可以在该函数中看到下面的两行代码。
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start); (1)、 vectors为目标地自己 等于0xffff0000; (2)、地址__vectors_start ~ __vectors_end之间的代码就是异常向量;在arch/arm/kernel/entry-armv.S中定义,它们复制到地址0xffff0000处。 (3)、异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。 (4)、这些“更复杂的代码”在地址__stubs_start ~__stubs_end之间,它们在arch/arm/kernel/entry-armv.S中定义。将它们复制到地址0xffff0000+0x200处。 异常向量、异常向量跳去执行的代码都是使用汇编写的,它们在arch/arm/kernel/entry-armv.S中。 (5)、异常向量的代码如下,其中的“stubs_offset”用来重新定位跳转的位置(向量被复制到地址0xffff0000处,跳转的目的代码被复制到地址0xffff0000+0x200处)。
中断向量表 .globl __vectors_start __vectors_start: swi SYS_ERROR0 //复位时跳转到此条代码处 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset //irq异常中断 b vector_fiq + stubs_offset .globl __vectors_end __vectors_end: (1)当异常发生时跳转到相应的项去执行; (2)其中,vector_und、vector_pabt等表示要跳转去执行的代码。以vector_irq为例,它仍在arch/arm/kernel/entry-armv.S中,通过vector_stub宏来定义。 /* * Interrupt dispatcher */ vector_stub irq, IRQ_MODE, 4 .long __irq_usr @ 0 (USR_26 / USR_32) .long __irq_invalid @ 1 (FIQ_26 / FIQ_32) .long __irq_invalid @ 2 (IRQ_26 / IRQ_32) .long __irq_svc @ 3 (SVC_26 / SVC_32) .long __irq_invalid @ 4 .long __irq_invalid @ 5 .long __irq_invalid @ 6 .long __irq_invalid @ 7 .long __irq_invalid @ 8 .long __irq_invalid @ 9 .long __irq_invalid @ a .long __irq_invalid @ b .long __irq_invalid @ c .long __irq_invalid @ d .long __irq_invalid @ e .long __irq_invalid @ f vector_stub是一个宏,它根据后面的参数"irq, IRQ_MODE"定义了以“vector_irq”为标号的一段代码。vector_stub宏的功能为:计算处理完异常后的返回地址、保存一引起寄存器(比如r0、lr、spsr),然后进行管理模式,最后根据被中断的工作模式调用下面的某个跳转分支。当发生异常时,CPU会根据异常的类型进入某个工作模式,但是很快vector_stub宏又会强制CPU进行管理模式,在管理模式下进行后续处理,这种方法简化了程序的设计,使得异常发生前的工作模式要么是用户模式,要么是管理模式
init_IRQ函数分析 中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须、共用的中断(比如系统时钟中断、片内外设UART中断)外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建一个非常容易扩充的中断处理体系。 init_IRQ函数(代码在arch/arm/kernel/irq.c中)被用来初始化中断和处理框架,设置各种中断的默认处理函数。 void __init init_IRQ(void) { int irq; init_vectors(); for (irq = 0; (irq < NR_IRQS); irq++) { irq_desc[irq].status = IRQ_DISABLED; irq_desc[irq].action = NULL; irq_desc[irq].depth = 1; irq_desc[irq].chip = &m_irq_chip; } } 当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数进行下一步处理。 中断处理的一般过程: (1)异常发生; (2)分辨是哪个中断;(从INTOFFSET寄存器中获得) (3)调用处理函数; (4)清中断 (以便下一个中断发生,EINTFEND) 后面的三项都是在linux内核中的asm_do_IRQ函数中实现. asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs) { struct pt_regs *old_regs = set_irq_regs(regs); irq_enter(); /* * Some hardware gives randomly wrong interrupts. Rather * than crashing, do something sensible. */ if (irq >= NR_IRQS) handle_bad_irq(irq, &bad_irq_desc); else generic_handle_irq(irq); /* AT91 specific workaround */ irq_finish(irq); irq_exit(); set_irq_regs(old_regs); } static inline void generic_handle_irq(unsigned int irq) { generic_handle_irq_desc(irq, irq_to_desc(irq)); } static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc) { #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ desc->handle_irq(irq, desc); #else if (likely(desc->handle_irq)) desc->handle_irq(irq, desc); else __do_IRQ(irq); #endif } 现在 通过irq_desc结构数组就可以了解中断处理体系结构,irq_desc结构的数据类型include/linux/irq.h 中定义, struct irq_desc { unsigned int irq;struct timer_rand_state *timer_rand_state; unsigned int *kstat_irqs; #ifdef CONFIG_INTR_REMAP struct irq_2_iommu *irq_2_iommu; #endif irq_flow_handler_t handle_irq; // 当前中断的处理函数入口 struct irq_chip *chip; //低层的硬件访问 struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; // 用户提供的中断处理函数链表 unsigned int status; //IRQ状态 ........ const char *name; //中断的名称 } ____cacheline_internodealigned_in_smp; handle_irq是这个或这组中断的处理函数入口。发生中断时,总入口函数asm_do_IRQ将根据中断号调用相应irq_desc数组项中handle_irq.handle_irq使用chip结构中的函数清除、屏蔽或者重新使能中断,还要调用用户在action链表中注册的中断处理函数。 irq_chip结构类型也是在include/linux/irq.h中定义,其中的成员大多用于操作底层硬件,比如设置寄存器以屏蔽中断,使能中断,清除中断等。 struct irq_chip { const char *name; unsigned in (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable void (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disab void (*enable)(unsigned int irq);// 使用中断,如果不设置,缺省为"unmask" void (*disable)(unsigned int irq);//禁止中断,如果不设置,缺省为“mask” void (*ack)(unsigned int irq);/*响应中断,通常是清除当前中断使得可以接收下一个中 void (*mask)(unsigned int irq); //屏蔽中断源 void (*mask_ack)(unsigned int irq);//屏蔽和响应中断 void (*unmask)(unsigned int irq);//开启中断源 void (*eoi)(unsigned int irq); ........ const char *typename; }; irq_desc结构中的irqaction结构类型在include/linux/iterrupt.h中定义。用户注册的每个中断处理函数用一个irqaction结构来表示,一个中断比如共享中断可以有多个处理函数,它们的irqaction结构链接成一个链表,以action为表头。irqation结构定义如下: struct irqaction { irq_handler_t handler; //用户注册的中断处理函数 unsigned long flags; //中断标志,比如是否共享中断,电平触发还是边沿触发 const char *name; //用户注册的中断名字 void *dev_id; //用户传给上面的handler的参数,还可以用来区分共享中断 struct irqaction *next; //指向下一个用户注册函数的指针 int irq; //中断号 struct proc_dir_entry *dir; irq_handler_t thread_fn; struct task_struct *thread; unsigned long thread_flags; }; irq_desc结构数组、它的成员“struct irq_chip *chip” "struct irqaction *action",这3种数据结构构成了中断处理体系的框架。下图中描述了Linxu中断处理体系结构的关系图: ![]() 中断处理流程如下: (1)发生中断时,CPU执行异常向量vector_irq的代码 (2)在vector_irq里面,最终会调用中断处理的总入口函数asm_do_IRQ (3)asm_do_IRQ根据中断号调用irq_desc数组项中的handle_irq。 (4)handle_irq会使用chip成员中的函数来设置硬件,比如清除中断、禁止中断、重新使能中断等 (5)handle_irq逐个调用用户在aciton链表中注册的处理函数 中断体系结构的初始化就是构造这些数据结构,比如irq_desc数组项中的handle_irq、chip等成员;用户注册中断时就是构造action链表;用户卸载中断时就是从action链表中去除不需要的项。 中断处理体系结构的初始化 init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中 void __init init_IRQ(void) { int irq; init_vectors(); for (irq = 0; (irq < NR_IRQS); irq++) { irq_desc[irq].status = IRQ_DISABLED; irq_desc[irq].action = NULL; irq_desc[irq].depth = 1; irq_desc[irq].chip = &m_irq_chip; } } for循环 初始化irq_desc结构数组中每一项的中断状态 irq_desc[irq].chip = &m_irq_chip; 调用架构相关的中断初始化函数。对于S3C2440开发板,这个函数就是s3c24xx_init_irq,移植machine_desc结构中的init_irq成员就指向这个函数s3c24xx_init_irq函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有中断设置了芯片相关的数据结构(irq_desc[irq].chip),设置了处理函数入口(irq_desc[irq].handle_irq)。以外部中断EINT4-EINT23为例,用来设置它们的代码如下: void __init s3c24xx_init_irq(void) { unsigned long pend; unsigned long last; int irqno; int i; irqdbf("s3c2410_init_irq: clearing interrupt status flags\n"); /* first, clear all interrupts pending... */ last = 0; for (i = 0; i < 4; i++) { pend = __raw_readl(S3C24XX_EINTPEND); if (pend == 0 || pend == last) break; __raw_writel(pend, S3C24XX_EINTPEND); printk("irq: clearing pending ext status %08x\n", (int)pend); last = pend; } last = 0; for (i = 0; i < 4; i++) { pend = __raw_readl(S3C2410_INTPND); if (pend == 0 || pend == last) break; __raw_writel(pend, S3C2410_SRCPND); __raw_writel(pend, S3C2410_INTPND); printk("irq: clearing pending status %08x\n", (int)pend); last = pend; } last = 0; for (i = 0; i < 4; i++) { pend = __raw_readl(S3C2410_SUBSRCPND); if (pend == 0 || pend == last) break; printk("irq: clearing subpending status %08x\n", (int)pend); __raw_writel(pend, S3C2410_SUBSRCPND); last = pend; } /* register the main interrupts */ irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlers\n"); for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno++) { /* set all the s3c2410 internal irqs */ switch (irqno) { /* deal with the special IRQs (cascaded) */ case IRQ_EINT4t7: case IRQ_EINT8t23: case IRQ_UART0: case IRQ_UART1: case IRQ_UART2: case IRQ_ADCPARENT: set_irq_chip(irqno, &s3c_irq_level_chip); set_irq_handler(irqno, handle_level_irq); break; case IRQ_RESERVED6: case IRQ_RESERVED24: /* no IRQ here */ break; default: //irqdbf("registering irq %d (s3c irq)\n", irqno); set_irq_chip(irqno, &s3c_irq_chip); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); } } /* setup the cascade irq handlers */ set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7); set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8); set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0); set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1); set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2); set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc); /* external interrupts */ for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) { irqdbf("registering irq %d (ext int)\n", irqno); set_irq_chip(irqno, &s3c_irq_eint0t4); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); } for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) { irqdbf("registering irq %d (extended s3c irq)\n", irqno); set_irq_chip(irqno, &s3c_irqext_chip); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); } /* register the uart interrupts */ irqdbf("s3c2410: registering external interrupts\n"); for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) { irqdbf("registering irq %d (s3c uart0 irq)\n", irqno); set_irq_chip(irqno, &s3c_irq_uart0); set_irq_handler(irqno, handle_level_irq); set_irq_flags(irqno, IRQF_VALID); } for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) { irqdbf("registering irq %d (s3c uart1 irq)\n", irqno); set_irq_chip(irqno, &s3c_irq_uart1); set_irq_handler(irqno, handle_level_irq); set_irq_flags(irqno, IRQF_VALID); } for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) { irqdbf("registering irq %d (s3c uart2 irq)\n", irqno); set_irq_chip(irqno, &s3c_irq_uart2); set_irq_handler(irqno, handle_level_irq); set_irq_flags(irqno, IRQF_VALID); } for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) { irqdbf("registering irq %d (s3c adc irq)\n", irqno); set_irq_chip(irqno, &s3c_irq_adc); set_irq_handler(irqno, handle_edge_irq); set_irq_flags(irqno, IRQF_VALID); } irqdbf("s3c2410: registered interrupt handlers\n"); set_irq_chip(irqno, &s3c_irq_chip);函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。 set_irq_handler(irqno, handle_edge_irq); 设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数 set_irq_flags(irqno, IRQF_VALID); 设置中断标志为“IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_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) { struct irqaction *action; struct irq_desc *desc; int retval; if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) == (IRQF_SHARED|IRQF_DISABLED)) { pr_warning( "IRQ %d/%s: IRQF_DISABLED is not guaranteed on shared IRQs\n", irq, devname); } irqflags |= IRQF_DISABLED; if ((irqflags & IRQF_SHARED) && !dev_id) return -EINVAL; desc = irq_to_desc(irq); if (!desc) return -EINVAL; if (desc->status & IRQ_NOREQUEST) return -EINVAL; if (!handler) return -EINVAL; action = kzalloc(sizeof(struct irqaction), GFP_KERNEL); if (!action) return -ENOMEM; action->handler = handler; action->thread_fn = thread_fn; action->flags = irqflags; action->name = devname; action->dev_id = dev_id; retval = __setup_irq(irq, desc, action); if (retval) kfree(action); unsigned long flags; disable_irq(irq); local_irq_save(flags); handler(irq, dev_id); local_irq_restore(flags); enable_irq(irq); } return retval; } retval = __setup_irq(irq, desc, action);setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能 (1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。如果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入
void irq_chip_set_defaults(struct irq_chip *chip) { if (!chip->enable) chip->enable = default_enable; if (!chip->disable) chip->disable = default_disable; if (!chip->startup) chip->startup = default_startup;
if (!chip->shutdown) chip->shutdown = chip->disable != default_disable ? chip->disable : default_shutdown; if (!chip->name) chip->name = chip->typename; if (!chip->end) chip->end = dummy_irq_chip.end; } (3) 启动中断,如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。 总结一下request_irq函数注册
中断的处理过程 asm_do_IRQ是中断的C语言总入口函数,它在/arch/arm/kernel/irq.c中定义,
desc_hand_irq函数直接调用desc结构中的hand_irq成员函数,它就是irq_desc[irq].handle.irq asm_do_IRQ函数中参数irq的取值范围为IRQ_EINT0~(IRQ_EINT0 + 31),只有32个取值。它可能是一个实际的中断号,也可能是一组中断的中断号。这里有S3C2440的芯片特性决定的:发生中断时,INTPND寄存器的某一位被置1,INTOFFSET寄存器中记录了是哪一位(0--31),中断向量调用asm_do_IRQ之前要把INTOFFSET寄存器的值确定irq参数。每一个实际的中断在irq_desc数组中都有一项与它对应,它们的数目不止32.当asm_do_IRQ函数参数irq表示的是“一组”中断时,irq_desc[irq].handle_irq成员函数还需要先分辨出是哪一个中断,然后调用irq_desc[irqno].handle_irq来进一步处理。 以外部中断EINT8—EINT23为例,它们通常是边沿触发 (1) 它们被触发里,INTOFFSET寄存器中的值都是5,asm_do_IRQ函数中参数irq的值为(IRQ_EINTO+5),即IRQ_EINT8t23, (2)irq_desc[IRQ_EINT8t23].handle_irq在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irq_demux_extint8. (3)s3c_irq_demux_extint8函数的代码在arch/arm/plat-s3c24xx/irq.c中,它首先读取EINTPEND、EINTMASK寄存器,确定发生了哪些中断,重新计算它们的中断号,然后调用irq_desc数组项中的handle_irq成员函数
(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq/chip.c中定义,它用来处理边沿触发的中断, 中断发生的次数统计
响应中断,通常是清除当前中断使得可以接收下一个中断,对于IRQ_EINT8~IRQ_EINT23这几个中断,desc->chip在前面init_IRQ函数初始化中断体系结构的时候被设为s3c_irqext_chip.desc->chip->ack就是s3c_irqext_ack函数,(arch/armplat-s3c24xx/irq.c)它用来清除中断 handle_IRQ_event函数来逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义。
用户注册的中断处理函数的参数为中断号irq,action->dev_id。后一个参数是通过request_irq函数注册中断时传入的dev_id参数,它由用户自己指定、自己使用,可以为空,当这个中断是“共享中断”时除外。 对于电平触发的中断,它们的irq_desc[irq].handle_irq通常是handle_level_irq函数。它也是在kernel/irq/chip.c中定义,其功能与上述handle_edge_irq函数相似, 对于handle_level_irq函数已经清除了中断,但是它只限于清除SoC内部的的信号,如果外设输入到SoC的中断信号仍然有效,这就会导致当前中断处理完成后,会误认为再次发生了中断,对于这种情况,需要用户注册的中断处理函数中清除中断,先清除外设的中断,然后再清除SoC内部的中断号。 中断的处理流程可以总结如下 (1)中断向量调用总入口函数asm_do_IRQ,传入根据中断号irq (2)asm_do_IRQ函数根据中断号irq调用irq_desc[irq].handle_irq,它是这个中断的处理函数入口,对于电平触发的中断,这个入口函数通常为handle_level_irq,对于边沿触发的中断,这个入口通常为handle_edge_irq (3)入口函数首先清除中断,入口函数是handle_level_irq时还要屏蔽中断 (4)逐个调用用户在irq_desc[irq].aciton链表中注册的中断处理函数 (5) 入口函数是handle_level_irq时还要重新开启中断 卸载中断处理函数这通过free_irq函数来实现,它与request_irq一样,也是在kernel/irq/mangage.c中定义。 它需要用到两个参数:irq和dev_id,它们与通过request_irq注册中断函数时使用的参数一样,使用中断号irq定位action链表,再使用dev_id在action链表中找到要卸载的表项。同一个中断的不同中断处理函数必须使用不同的dev_id来区分,在注册共享中断时参数dev_id必惟一。 free_irq函数的处理过程与request_irq函数相反 (1)根据中断号irq,dev_id从action链表中找到表项,将它移除 (2)如果它是惟一的表项,还要调用IRQ_DESC[IRQ].CHIP->SHUTDOWN 或IRQ_DESC[IRQ].CHIP->DISABLW来关闭中断。 在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》 |
|
来自: nt_bookworm > 《驱动》