第五篇 Linux内核及驱动编程-深入剖析Linux中断机..
时间:2009-07-12 来源:embededgood
【摘要】本文详解了Linux内核的中断实现机制。首先介绍了中断的一些基本概念,然后分析了面向对象的Linux中断的组织形式、三种主要数据结构及其之间的关系。随后介绍了Linux处理异常和中断的基本流程,在此基础上分析了中断处理的详细流程,包括保存现场、中断处理、中断退出时的软中断执行及中断返回时的进程切换等问题。最后介绍了中断相关的API,包括中断注册和释放、中断关闭和使能、如何编写中断ISR、共享中断、中断上下文中断状态等。
【关键字】中断,异常,hw_interrupt_type,irq_desc_t,irqaction,asm_do_IRQ,软中断,进程切换,中断注册释放request_irq,free_irq,共享中断,可重入,中断上下文
1 Linux中断的组织形式
1.1 IRQ描述符irq_desc
对于每个IRQ中断线,Linux都用一个irq_desc_t数据结构来描述,我们把它叫做IRQ描述符,NR_IRQS个IRQ形成一个全局数组irq_desc[],其定义在/include/linux/irq.h中: struct irq_desc – 中断描述符 148struct irq_desc { 149 irq_flow_handler_t handle_irq; 150 struct irq_chip *chip; 151 void *handler_data; 152 void *chip_data; 153 struct irqaction *action; /* IRQ action list */ 154 unsigned int status; /* IRQ status */ 155 156 unsigned int depth; /* nested irq disables */ 157 unsigned int wake_depth; /* nested wake enables */ 158 unsigned int irq_count; /* For detecting broken IRQs */ 159 unsigned int irqs_unhandled; 160 spinlock_t lock; 161#ifdef CONFIG_SMP 162 cpumask_t affinity; 163 unsigned int cpu; 164#endif 171 const char *name; 172} ____cacheline_aligned; 173 174extern struct irq_desc irq_desc[NR_IRQS]; handle_irq:上层的通用中断处理函数指针,如果未设置则默认为__do_IRQ()。通常针对电平触发或者边沿触发有不同的处理函数。每个中断线可分别设置; chip:底层中断的各种控制访问方法集合,各个CPU实现的都不同,这属于面向对象的中断处理方式中最底层的一部分; handler_data:附加参数,用于handle_irq; chip_data:平台相关的附加参数,用于chip; action:指向一个单向链表的指针,这个链表就是对中断服务例程进行描述的irqaction结构; status:中断当前的状态; depth:中断关闭打开的层数。如果启用这条IRQ中断线,depth则为0,如果禁用这条IRQ中断线不止一次,则为一个正数。如果depth等于0,每当调用一次disable_irq( ),该函数就对这个域的值加1,同时该函数就禁用这条IRQ中断线。相反,每当调用enable_irq( )函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。 Lock:此中断描述符为全局共享暑假,对于SMP需要互斥访问 Dir: /proc/irq/ 入口 Name: /proc/interrupts 中显示的中断名称 “____cacheline_aligned”表示这个数据结构的存放按32字节(高速缓存行的大小)进行对齐,以便于将来存放在高速缓存并容易存取 linux+v2.6.19/arch/arm/kernel/irq.c 157void __init init_IRQ(void) 158{ 159 int irq; 160 161 for (irq = 0; irq < NR_IRQS; irq++) 162 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | 163 IRQ_NOPROBE; 164 165#ifdef CONFIG_SMP 166 bad_irq_desc.affinity = CPU_MASK_ALL; 167 bad_irq_desc.cpu = smp_processor_id(); 168#endif 169 init_arch_irq(); 170} 1.2 中断控制器描述符irq_chip
由于CPU不同,故每个处理器对于中断的处理方式不一样。Linux为了实现统一的中断处理,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。 struct irq_chip –片级的中断描述符 94struct irq_chip { 95 const char *name; 96 unsigned int (*startup)(unsigned int irq); 97 void (*shutdown)(unsigned int irq); 98 void (*enable)(unsigned int irq); 99 void (*disable)(unsigned int irq); 100 101 void (*ack)(unsigned int irq); 102 void (*mask)(unsigned int irq); 103 void (*mask_ack)(unsigned int irq); 104 void (*unmask)(unsigned int irq); 105 void (*eoi)(unsigned int irq); 106 107 void (*end)(unsigned int irq); 108 void (*set_affinity)(unsigned int irq, cpumask_t dest); 109 int (*retrigger)(unsigned int irq); 110 int (*set_type)(unsigned int irq, unsigned int flow_type); 111 int (*set_wake)(unsigned int irq, unsigned int on); 121 const char *typename; 122}; Name:用于/proc/interrupts Startup:默认为enable if NULL Shutdown:默认为 disable if NULL Enable:允许中断,默认为unmask if NULL Disable:禁止中断,默认为mask if NULL Ack:响应一个中断 Mask:mask 一个中断源,通常是关闭中断 mask_ack:响应并mask中断源 unmask:unmask中断源 set_type:设置中断触发方式IRQ_TYPE_LEVEL 大多数控制方法都是重复的,基本上只要有中断响应、中断屏蔽、中断开启、中断触发类型设置等方法就可以满足要求了。其他各种方法基本上和这些相同。 linux+v2.6.19/arch/arm/mach-at91rm9200/irq.c 提供了中断响应、打开、关闭、设置触发类型等底层方法的接口 static struct irq_chip at91_aic_chip = { .name = "AIC", .ack = at91_aic_mask_irq, .mask = at91_aic_mask_irq, .unmask = at91_aic_unmask_irq, .set_type = at91_aic_set_type, .set_wake = at91_aic_set_wake, }; 124/* 125 * Initialize the AIC interrupt controller. 126 */ 127void __init at91_aic_init(unsigned int priority[NR_AIC_IRQS]) 128{ 129 unsigned int i; 130 131 /* 132 * The IVR is used by macro get_irqnr_and_base to read and verify. 133 * The irq number is NR_AIC_IRQS when a spurious interrupt has occurred. 134 */ 135 for (i = 0; i < NR_AIC_IRQS; i++) { 136 /* Put irq number in Source Vector Register: */ 137 at91_sys_write(AT91_AIC_SVR(i), i); 138 /* Active Low interrupt, with the specified priority */ 139 at91_sys_write(AT91_AIC_SMR(i), AT91_AIC_SRCTYPE_LOW | priority[i]); 140 141 set_irq_chip(i, &at91_aic_chip); 142 set_irq_handler(i, do_level_IRQ); 143 set_irq_flags(i, IRQF_VALID | IRQF_PROBE); 144 145 /* Perform 8 End Of Interrupt Command to make sure AIC will not Lock out nIRQ */ 146 if (i < 8) 147 at91_sys_write(AT91_AIC_EOICR, 0); 148 } 149 150 /* 151 * Spurious Interrupt ID in Spurious Vector Register is NR_AIC_IRQS 152 * When there is no current interrupt, the IRQ Vector Register reads the value stored in AIC_SPU 153 */ 154 at91_sys_write(AT91_AIC_SPU, NR_AIC_IRQS); 155 156 /* No debugging in AIC: Debug (Protect) Control Register */ 157 at91_sys_write(AT91_AIC_DCR, 0); 158 159 /* Disable and clear all interrupts initially */ 160 at91_sys_write(AT91_AIC_IDCR, 0xFFFFFFFF); 161 at91_sys_write(AT91_AIC_ICCR, 0xFFFFFFFF); 162} 163 以下这些宏定义都是为保持兼容性而设置的,后续版本中将彻底删除 47#define do_level_IRQ handle_level_irq 48#define do_edge_IRQ handle_edge_irq 49#define do_simple_IRQ handle_simple_irq 50#define irqdesc irq_desc 51#define irqchip irq_chip 55#define SA_INTERRUPT IRQF_DISABLED 57#define SA_SHIRQ IRQF_SHARED 60 61#define SA_TRIGGER_LOW IRQF_TRIGGER_LOW 62#define SA_TRIGGER_HIGH IRQF_TRIGGER_HIGH 63#define SA_TRIGGER_FALLING IRQF_TRIGGER_FALLING 64#define SA_TRIGGER_RISING IRQF_TRIGGER_RISING 65#define SA_TRIGGER_MASK IRQF_TRIGGER_MASK linux/kernel/irq/chip.c handle_level_irq 1.3 中断服务例程描述符irqaction
在IRQ描述符中我们看到指针action的结构为irqaction,它是为多个设备能共享一条中断线而设置的一个数据结构,代表了每个注册中断对应的信息。在include/linux/interrupt.h中定义如下: 67typedef irqreturn_t (*irq_handler_t)(int, void *); 68 69struct irqaction { 70 irq_handler_t handler; 71 unsigned long flags; 72 cpumask_t mask; 73 const char *name; 74 void *dev_id; 75 struct irqaction *next; 76 int irq; 77 struct proc_dir_entry *dir; 78}; Handler:指向一个具体I/O设备的中断服务例程。这是允许多个设备共享同一中断线的关键域,中断线可以相同,但处理函数可以不一样。 Flags:用一组标志描述中断线与I/O设备之间的关系。 SA_INTERRUPT 中断处理程序必须以禁用中断来执行。此标志表明给定的中断处理程序是一个快速中断处理程序(fast interrupt handler)。过去,Linux将中断处理程序分为快速和慢速两种。那些可以迅速执行但调用频率可能会很高的中断服务程序,会被贴上这样的标签。通常这样做需要修改中断处理程序的行为,使它们能够尽可能快地执行。现在,加不加此标志的区别只剩下一条了:在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行。这使得快速中断处理程序能够不受其他中断干扰,得以迅速执行。而默认情况下(没有这个标志),除了正运行的中断处理程序对应的那条中断线被屏蔽外,其他所有中断都是激活的。除了时钟中断外,绝大多数中断都不使用该标志。 SA_SHIRQ 此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志:否则,在每条线上只能有一个处理程序。各项该中断线的每一个例程都需要设置此标志。 Name:I/O设备名(通过读取/proc/interrupts文件,可以看到,在列出中断号时也显示设备名。) dev_id:对于共享中断,此特定值用来区分各中断。当一个中断处理程序需要释放时,dev_id将提供惟一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。如果没有这个参数,那么内核不可能知道在给定的中断线上到底要删除哪一个处理程序。 如果无需共享中断线,那么将该参数赋为空值(NULL)就可以了,但是,如果中断线是被共享的,那么就必须传递惟一的信息。另外,内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传递驱动程序的设备结构:这个指针是惟一的,而且有可能在中断处理程序内及设备模式中被用到。 Next:指向irqaction描述符链表的下一个元素。共享同一中断线的每个硬件设备都有其对应的中断服务例程,链表中的每个元素就是对相应设备及中断服务例程的描述。 Irq:对应的中断号 dir:proc文件系统对应的入口 1.4 三者的关系
三个主要的数据结构包含了与 IRQ 相关的所有信息:hw_interrupt_type、irq_desc_t 和 irqaction,下图解释了它们之间是如何关联的。中断服务例程ISR是irqaction 的Handler成员。 中断的处理是一种面向对象的机制,通过三个数据结果实现了三层结构,底层是和具体硬件相关的中断处理响应等,中间层是统一的中断处理流程,最上层是特定的中断处理例程。 IRQ 结构之间的关系 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sailor_8318/archive/2008/08/28/2841001.aspx
1.1 IRQ描述符irq_desc
对于每个IRQ中断线,Linux都用一个irq_desc_t数据结构来描述,我们把它叫做IRQ描述符,NR_IRQS个IRQ形成一个全局数组irq_desc[],其定义在/include/linux/irq.h中: struct irq_desc – 中断描述符 148struct irq_desc { 149 irq_flow_handler_t handle_irq; 150 struct irq_chip *chip; 151 void *handler_data; 152 void *chip_data; 153 struct irqaction *action; /* IRQ action list */ 154 unsigned int status; /* IRQ status */ 155 156 unsigned int depth; /* nested irq disables */ 157 unsigned int wake_depth; /* nested wake enables */ 158 unsigned int irq_count; /* For detecting broken IRQs */ 159 unsigned int irqs_unhandled; 160 spinlock_t lock; 161#ifdef CONFIG_SMP 162 cpumask_t affinity; 163 unsigned int cpu; 164#endif 171 const char *name; 172} ____cacheline_aligned; 173 174extern struct irq_desc irq_desc[NR_IRQS]; handle_irq:上层的通用中断处理函数指针,如果未设置则默认为__do_IRQ()。通常针对电平触发或者边沿触发有不同的处理函数。每个中断线可分别设置; chip:底层中断的各种控制访问方法集合,各个CPU实现的都不同,这属于面向对象的中断处理方式中最底层的一部分; handler_data:附加参数,用于handle_irq; chip_data:平台相关的附加参数,用于chip; action:指向一个单向链表的指针,这个链表就是对中断服务例程进行描述的irqaction结构; status:中断当前的状态; depth:中断关闭打开的层数。如果启用这条IRQ中断线,depth则为0,如果禁用这条IRQ中断线不止一次,则为一个正数。如果depth等于0,每当调用一次disable_irq( ),该函数就对这个域的值加1,同时该函数就禁用这条IRQ中断线。相反,每当调用enable_irq( )函数时,该函数就对这个域的值减1;如果depth变为0,该函数就启用这条IRQ中断线。 Lock:此中断描述符为全局共享暑假,对于SMP需要互斥访问 Dir: /proc/irq/ 入口 Name: /proc/interrupts 中显示的中断名称 “____cacheline_aligned”表示这个数据结构的存放按32字节(高速缓存行的大小)进行对齐,以便于将来存放在高速缓存并容易存取 linux+v2.6.19/arch/arm/kernel/irq.c 157void __init init_IRQ(void) 158{ 159 int irq; 160 161 for (irq = 0; irq < NR_IRQS; irq++) 162 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_DELAYED_DISABLE | 163 IRQ_NOPROBE; 164 165#ifdef CONFIG_SMP 166 bad_irq_desc.affinity = CPU_MASK_ALL; 167 bad_irq_desc.cpu = smp_processor_id(); 168#endif 169 init_arch_irq(); 170} 1.2 中断控制器描述符irq_chip
由于CPU不同,故每个处理器对于中断的处理方式不一样。Linux为了实现统一的中断处理,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。 struct irq_chip –片级的中断描述符 94struct irq_chip { 95 const char *name; 96 unsigned int (*startup)(unsigned int irq); 97 void (*shutdown)(unsigned int irq); 98 void (*enable)(unsigned int irq); 99 void (*disable)(unsigned int irq); 100 101 void (*ack)(unsigned int irq); 102 void (*mask)(unsigned int irq); 103 void (*mask_ack)(unsigned int irq); 104 void (*unmask)(unsigned int irq); 105 void (*eoi)(unsigned int irq); 106 107 void (*end)(unsigned int irq); 108 void (*set_affinity)(unsigned int irq, cpumask_t dest); 109 int (*retrigger)(unsigned int irq); 110 int (*set_type)(unsigned int irq, unsigned int flow_type); 111 int (*set_wake)(unsigned int irq, unsigned int on); 121 const char *typename; 122}; Name:用于/proc/interrupts Startup:默认为enable if NULL Shutdown:默认为 disable if NULL Enable:允许中断,默认为unmask if NULL Disable:禁止中断,默认为mask if NULL Ack:响应一个中断 Mask:mask 一个中断源,通常是关闭中断 mask_ack:响应并mask中断源 unmask:unmask中断源 set_type:设置中断触发方式IRQ_TYPE_LEVEL 大多数控制方法都是重复的,基本上只要有中断响应、中断屏蔽、中断开启、中断触发类型设置等方法就可以满足要求了。其他各种方法基本上和这些相同。 linux+v2.6.19/arch/arm/mach-at91rm9200/irq.c 提供了中断响应、打开、关闭、设置触发类型等底层方法的接口 static struct irq_chip at91_aic_chip = { .name = "AIC", .ack = at91_aic_mask_irq, .mask = at91_aic_mask_irq, .unmask = at91_aic_unmask_irq, .set_type = at91_aic_set_type, .set_wake = at91_aic_set_wake, }; 124/* 125 * Initialize the AIC interrupt controller. 126 */ 127void __init at91_aic_init(unsigned int priority[NR_AIC_IRQS]) 128{ 129 unsigned int i; 130 131 /* 132 * The IVR is used by macro get_irqnr_and_base to read and verify. 133 * The irq number is NR_AIC_IRQS when a spurious interrupt has occurred. 134 */ 135 for (i = 0; i < NR_AIC_IRQS; i++) { 136 /* Put irq number in Source Vector Register: */ 137 at91_sys_write(AT91_AIC_SVR(i), i); 138 /* Active Low interrupt, with the specified priority */ 139 at91_sys_write(AT91_AIC_SMR(i), AT91_AIC_SRCTYPE_LOW | priority[i]); 140 141 set_irq_chip(i, &at91_aic_chip); 142 set_irq_handler(i, do_level_IRQ); 143 set_irq_flags(i, IRQF_VALID | IRQF_PROBE); 144 145 /* Perform 8 End Of Interrupt Command to make sure AIC will not Lock out nIRQ */ 146 if (i < 8) 147 at91_sys_write(AT91_AIC_EOICR, 0); 148 } 149 150 /* 151 * Spurious Interrupt ID in Spurious Vector Register is NR_AIC_IRQS 152 * When there is no current interrupt, the IRQ Vector Register reads the value stored in AIC_SPU 153 */ 154 at91_sys_write(AT91_AIC_SPU, NR_AIC_IRQS); 155 156 /* No debugging in AIC: Debug (Protect) Control Register */ 157 at91_sys_write(AT91_AIC_DCR, 0); 158 159 /* Disable and clear all interrupts initially */ 160 at91_sys_write(AT91_AIC_IDCR, 0xFFFFFFFF); 161 at91_sys_write(AT91_AIC_ICCR, 0xFFFFFFFF); 162} 163 以下这些宏定义都是为保持兼容性而设置的,后续版本中将彻底删除 47#define do_level_IRQ handle_level_irq 48#define do_edge_IRQ handle_edge_irq 49#define do_simple_IRQ handle_simple_irq 50#define irqdesc irq_desc 51#define irqchip irq_chip 55#define SA_INTERRUPT IRQF_DISABLED 57#define SA_SHIRQ IRQF_SHARED 60 61#define SA_TRIGGER_LOW IRQF_TRIGGER_LOW 62#define SA_TRIGGER_HIGH IRQF_TRIGGER_HIGH 63#define SA_TRIGGER_FALLING IRQF_TRIGGER_FALLING 64#define SA_TRIGGER_RISING IRQF_TRIGGER_RISING 65#define SA_TRIGGER_MASK IRQF_TRIGGER_MASK linux/kernel/irq/chip.c handle_level_irq 1.3 中断服务例程描述符irqaction
在IRQ描述符中我们看到指针action的结构为irqaction,它是为多个设备能共享一条中断线而设置的一个数据结构,代表了每个注册中断对应的信息。在include/linux/interrupt.h中定义如下: 67typedef irqreturn_t (*irq_handler_t)(int, void *); 68 69struct irqaction { 70 irq_handler_t handler; 71 unsigned long flags; 72 cpumask_t mask; 73 const char *name; 74 void *dev_id; 75 struct irqaction *next; 76 int irq; 77 struct proc_dir_entry *dir; 78}; Handler:指向一个具体I/O设备的中断服务例程。这是允许多个设备共享同一中断线的关键域,中断线可以相同,但处理函数可以不一样。 Flags:用一组标志描述中断线与I/O设备之间的关系。 SA_INTERRUPT 中断处理程序必须以禁用中断来执行。此标志表明给定的中断处理程序是一个快速中断处理程序(fast interrupt handler)。过去,Linux将中断处理程序分为快速和慢速两种。那些可以迅速执行但调用频率可能会很高的中断服务程序,会被贴上这样的标签。通常这样做需要修改中断处理程序的行为,使它们能够尽可能快地执行。现在,加不加此标志的区别只剩下一条了:在本地处理器上,快速中断处理程序在禁止所有中断的情况下运行。这使得快速中断处理程序能够不受其他中断干扰,得以迅速执行。而默认情况下(没有这个标志),除了正运行的中断处理程序对应的那条中断线被屏蔽外,其他所有中断都是激活的。除了时钟中断外,绝大多数中断都不使用该标志。 SA_SHIRQ 此标志表明可以在多个中断处理程序之间共享中断线。在同一个给定线上注册的每个处理程序必须指定这个标志:否则,在每条线上只能有一个处理程序。各项该中断线的每一个例程都需要设置此标志。 Name:I/O设备名(通过读取/proc/interrupts文件,可以看到,在列出中断号时也显示设备名。) dev_id:对于共享中断,此特定值用来区分各中断。当一个中断处理程序需要释放时,dev_id将提供惟一的标志信息(cookie),以便从共享中断线的诸多中断处理程序中删除指定的那一个。如果没有这个参数,那么内核不可能知道在给定的中断线上到底要删除哪一个处理程序。 如果无需共享中断线,那么将该参数赋为空值(NULL)就可以了,但是,如果中断线是被共享的,那么就必须传递惟一的信息。另外,内核每次调用中断处理程序时,都会把这个指针传递给它。实践中往往会通过它传递驱动程序的设备结构:这个指针是惟一的,而且有可能在中断处理程序内及设备模式中被用到。 Next:指向irqaction描述符链表的下一个元素。共享同一中断线的每个硬件设备都有其对应的中断服务例程,链表中的每个元素就是对相应设备及中断服务例程的描述。 Irq:对应的中断号 dir:proc文件系统对应的入口 1.4 三者的关系
三个主要的数据结构包含了与 IRQ 相关的所有信息:hw_interrupt_type、irq_desc_t 和 irqaction,下图解释了它们之间是如何关联的。中断服务例程ISR是irqaction 的Handler成员。 中断的处理是一种面向对象的机制,通过三个数据结果实现了三层结构,底层是和具体硬件相关的中断处理响应等,中间层是统一的中断处理流程,最上层是特定的中断处理例程。 IRQ 结构之间的关系 本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sailor_8318/archive/2008/08/28/2841001.aspx
相关阅读 更多 +
排行榜 更多 +