Latest Update:2010/10/12
1.概述
通过前文,我们已经知道了中断通常由上下两部分组成。在上部分,也就是中断处理程序,完成中断请求的响应以及完成那些对时间要求紧迫的工作;而在下部分,通常完成那些被推后的工作,因为这部分工作对时间的要求相对宽松一些。通过了解上下两部分的工作情况,可以更好的理解中断这个概念。从下半部分执行机制来看——不管是tasklet还是工作队列——这些推后的工作总是在上半部分被调用,然后交给内核在适当的时间来完成。那么,中断上部分具体是如何工作的?内核对中断是如何处理的?
在开始分析之前,需要说明的是接下来分析的属于内核对外设产生的中断的处理情况,异常的处理过程在本文的最后会有简单解释。
所有的中断的处理程序在init_IRQ函数中都被初始化为interrupt[i]。interrupt数组中每一项均指向一个代码片段:
pushl $n-256
/*省略部分代码*/
jmp common_interrupt
该代码片段除了将中断向量号压入堆栈,还会跳到一个公共处理程序common_interrup:
//在linux/arch/x86/kernel/entry_32.S
863 common_interrupt:
864 addl $-0x80,(%esp) /* Adjust vector into the [-256,-1] range * /
865 SAVE_ALL
866 TRACE_IRQS_OFF
867 movl %esp,%eax
868 call do_IRQ
869 jmp ret_from_intr
这段公共处理程序会将中断发生前的所有寄存器的值压入堆栈,也就是保存被中断任务的现场。然后调用do_IRQ函数,在do_IRQ函数中会调用(并非直接调用那么简单)到handle_IRQ_event函数,在此函数中会执行实际的中断服务例程。当中断服务例程执行完毕后,会返回到上面的那段汇编程序中,转入ret_from_intr代码段从中断返回。
在上述文字描述的基础上,可以通过下图进一步加深中断的处理过程:
从上面的概述中,我们可以很快定位我们未来要分析的函数:do_IRQ()和hand_IRQ_event();在分析上述函数之前,我们很有必要先分析一下关于中断的三个重要的数据结构。
2.struct irq_desc
在一开始我们分析中断的时候,谈及到了中断向量。在内核中,每个中断向量都有相应的有一个irq_desc结构体(稍早内核版本中为irq_desc_t)来描述一个中断向量(也就是中断源),具体代码如下:
31struct irq_desc;
32typedef void (*irq_flow_handler_t)(unsigned int irq,
33 struct irq_desc *desc);
175struct irq_desc {
176 unsigned int irq;
177 struct timer_rand_state *timer_rand_state;
178 unsigned int *kstat_irqs;
179#ifdef CONFIG_INTR_REMAP
180 struct irq_2_iommu *irq_2_iommu;
181#endif
182 irq_flow_handler_t handle_irq;
183 struct irq_chip *chip;
184 struct msi_desc *msi_desc;
185 void *handler_data;
186 void *chip_data;
187 struct irqaction *action;
188 unsigned int status;
189
190 unsigned int depth;
191 unsigned int wake_depth;
192 unsigned int irq_count;
193 unsigned long last_unhandled;
194 unsigned int irqs_unhandled;
195 raw_spinlock_t lock;
196#ifdef CONFIG_SMP
197 cpumask_var_t affinity;
198 const struct cpumask *affinity_hint;
199 unsigned int node;
200#ifdef CONFIG_GENERIC_PENDING_IRQ
201 cpumask_var_t pending_mask;
202#endif
203#endif
204 atomic_t threads_active;
205 wait_queue_head_t wait_for_threads;
206#ifdef CONFIG_PROC_FS
207 struct proc_dir_entry *dir;
208#endif
209 const char *name;
210}
216#ifndef CONFIG_SPARSE_IRQ
217extern struct irq_desc irq_desc[NR_IRQS];
218#endif
所有这样的描述符组织在一起形成irq_desc[NR_IRQS]数组。下面我们对上述结构体的部分字符进行解释;
irq:通过数据类型可知这便是这个描述符所对应的中断号;
handle_irq:指向该IRQ线的公共服务程序;
chip:它是一个struct irq_chip类型的指针,是中断控制器的描述符,与平台有关,下文有详细描述;
handler_data:用于handler_irq的参数;
chip_data:用于chip的参数;
action:一个struct irqaction类型的指针(下文有该结构的详细描述);它指向一个单链表,该单链表是由该中断线上所有中断服务程序(对应struct irqaction)所连接起来的;
status:描述中断线当前的状态;
depth:中断线被激活时,值为0;其值为正数时,表示被禁止的次数;
irq_count:记录该中断线发生中断的次数;
irqs_unhandled:该IRQ线上未处理中断发生的次数;
name: /proc/interrupts 中显示的中断名称;
3.struct irqaction
当多个设备共享一条IRQ线时,因为每个设备都要有各自的ISR。为了能够正确处理此条IRQ线上的中断处理程序(也就是区分每个设备),就需要我们使用irqaction结构体。在这个结构体中,会有专门的handler字段指向该设备的真正的ISR。共享同一条IRQ线上的多个这样的结构体会连接成了一个单链表,即所谓的中断请求队列。中断产生时,该IRQ线的中断请求队列上所有的ISR都会被依次调用,因此每个设备的ISR必须判断当前的中断是否是自己所属的设备产生的。irqaction结构具体的定义如下:
98typedef irqreturn_t (*irq_handler_t)(int, void *);
113struct irqaction {
114 irq_handler_t handler;
115 unsigned long flags;
116 const char *name;
117 void *dev_id;
118 struct irqaction *next;
119 int irq;
120 struct proc_dir_entry *dir;
121 irq_handler_t thread_fn;
122 struct task_struct *thread;
123 unsigned long thread_flags;
124};
handler:指向一个具体的硬件设备的中断服务例程,可以从此指针的类型发现与前文我们所定义的中断处理函数声明相同;
flags:对应request_irq函数中所传递的第三个参数,可取IRQF_DISABLED、IRQF_SAMPLE_RANDOM和IRQF_SHARED其中之一;
name:对应于request_irq函数中所传递的第四个参数,可通过/proc/interrupts文件查看到;
next:指向下一个irqaction结构体;
dev_id:对应于request_irq函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体;
irq:中断号
如果一个IRQ线上有中断请求,那么内核将依次次调用在该中断线上注册的每一个中断服务程序,但是并不是所有中断服务程序都被执行。一般硬件设备都会提供一个状态寄存器,以便中断服务程序进行检查是否应该为这个硬件服务。也就是说在整个中断请求队列中,最多会有一个ISR被执行,也就是该ISR对应的那个设备产生了中断请求时;不过当该IRQ线上某个设备未找到匹配的ISR时,那这个中断就不会被处理。此时irq_desc结构中的irqs_unhandled字段就会加1。
4.struct irq_chip
struct irq_chip是一个中断控制器的描述符。通常不同的体系结构就有一套自己的中断处理方式。内核为了统一的处理中断,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。这样的结构体就好比一个插板,我们可以使用各种插头。至于插板内部是如何实现的,使用者并不需要过于关心。
比如经典的中断控制器是2片级联的8259A,那么得15个irq_desc描述符,每一个描述符的irq_chip都指向描述8259A的i8259A_irq_type变量(arch/alpha/kernel/irq_i8259.c):
86struct irq_chip i8259a_irq_type = {
87 .name = "XT-PIC",
88 .startup = i8259a_startup_irq,
89 .shutdown = i8259a_disable_irq,
90 .enable = i8259a_enable_irq,
91 .disable = i8259a_disable_irq,
92 .ack = i8259a_mask_and_ack_irq,
93 .end = i8259a_end_irq,
94};
这一点类似于VFS中所采用的原理:通过struct file_operations提供统一的接口,每种文件系统都必须具体实现这个结构体中所提供的接口。struct irq_chip具体代码如下:
111struct irq_chip {
112 const char *name;
113 unsigned int (*startup)(unsigned int irq);
114 void (*shutdown)(unsigned int irq);
115 void (*enable)(unsigned int irq);
116 void (*disable)(unsigned int irq);
117
118 void (*ack)(unsigned int irq);
119 void (*mask)(unsigned int irq);
120 void (*mask_ack)(unsigned int irq);
121 void (*unmask)(unsigned int irq);
122 void (*eoi)(unsigned int irq);
123
124 void (*end)(unsigned int irq);
125 int (*set_affinity)(unsigned int irq,
126 const struct cpumask *dest);
127 int (*retrigger)(unsigned int irq);
128 int (*set_type)(unsigned int irq, unsigned int flow_type);
129 int (*set_wake)(unsigned int irq, unsigned int on);
130
131 void (*bus_lock)(unsigned int irq);
132 void (*bus_sync_unlock)(unsigned int irq);
133
134 /* Currently used only by UML, might disappear one day.*/
135#ifdef CONFIG_IRQ_RELEASE_METHOD
136 void (*release)(unsigned int irq, void *dev_id);
137#endif
138 /*
139 * For compatibility, ->typename is copied into ->name.
140 * Will disappear.
141 */
142 const char *typename;
143};
name:中断控制器的名字;
Startup:启动中断线;
Shutdown:关闭中断线;
Enable:允许中断;
Disable:禁止中断;
5.数据结构之间的关系
首先我们通过下图来了解上述三个数据结构的关系:
通过上述分析,我们可以大致的知道中断处理程序的调用过程:在do_IRQ函数中,通过对irq_desc结构体中handler_irq字段的引用,调用handler_irq所指向的公共服务程序;在这个公共服务程序中会调用hand_IRQ_event函数;在hand_IRQ_event函数中,通过对irqaction结构体中handler字段的引用最终调用我们所写的中断处理程序。
另外,通过上述分析,我们知道struct irq_chip描述了中断最底层的部分;而struct irqacton则描述最上层具体的中断处理函数;而与中断向量所对应的struct desc则类似一个中间层,将中断中的硬件相关的部分和软件相关的部分连接起来。
6.内核对异常的处理
与中断不同,各种异常都有固定的中断向量(0~31)以及固定的异常处理程序。因此,当异常发生时,将直接跳转到相应的服务程序中执行。