Linux中断处理体系结构分析
Linux中断处理体系结构分析(一)
异常,就是可以打断CPU正常运行流程的一些事情,比如外部中断、未定义指令、试图修改只读的数据、执行swi指令(Software Interrupt Instruction ,软件中断指令)等。当这些事情发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。操作系统中经常通过异常来完成一些特定的功能。其中的中断也占有很大的一部分。例如下面的这几种情况:
- 当CPU执行未定义的机器指令时将触发“未定义指令异常”,操作系统可以利用这个特点使用一些自定义的机器指令,它们在异常处理函数中实现。
- 当用户程序试图读写的数据或执行的指令不在内存中时,也会触发一个“数据访问中止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存,然后重新执行被中断的程序,这样可以节省内存,还使得操作系统可以运行这类程序,它们使用的内存远大于实际的物理内存。
在原先的内核版本中,内核在start_kernel函数(源码在init/main.c中)中调用trap_init、init_IRQ两个函数来设置异常和处理函数。在Linux2.6.32.2的内核版本中(也许在之前的版本就有变化),trap_init函数的内容发生了变化,在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;
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);
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
memcpy((void *)KERN_RESTART_CODE, syscall_restart_code,
sizeof(syscall_restart_code));
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
698 void __init setup_arch(char **cmdline_p)
699 {
700 struct tag *tags = (struct tag *)&init_tags;
701 struct machine_desc *mdesc;
702 char *from = default_command_line;
...........
769 early_trap_init();
770 }
这样我们就明白了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);
vectors等于0xffff0000。地址__vectors_start ~ __vectors_end之间的代码就是异常向量,在arch/arm/kernel/entry-armv.S中定义,它们复制到地址0xffff0000处。异常向量的代码很简单,它们只是一些跳转指令。发生异常时,CPU自动执行这些指令,跳转去执行更复杂的代码,比如保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址__stubs_start ~__stubs_end之间,它们在arch/arm/kernel/entry-armv.S中定义。将它们复制到地址0xffff0000+0x200处。 异常向量、异常向量跳去执行的代码都是使用汇编写的,它们在arch/arm/kernel/entry-armv.S中。异常向量的代码如下,其中的“stubs_offset”用来重新定位跳转的位置(向量被复制到地址0xffff0000处,跳转的目的代码被复制到地址0xffff0000+0x200处)。
.equ stubs_offset, __vectors_start + 0x200 - __stubs_start
.globl __vectors_start
__vectors_start:
ARM( swi SYS_ERROR0 ) //复位是,CPU将执行这条指令
THUMB( svc #0 )
THUMB( nop )
W(b) vector_und + stubs_offset//未定义异常时,CPU将执行这条指令
W(ldr) pc, .LCvswi + stubs_offset//swi异常
W(b) vector_pabt + stubs_offset//指令预取中止
W(b) vector_dabt + stubs_offset//数据访问中止
W(b) vector_addrexcptn + stubs_offset//没有用到
W(b) vector_irq + stubs_offset//irq异常
W(b) vector_fiq + stubs_offset// fiq 异常
.globl __vectors_end
__vectors_end:
其中,vector_und、vector_pabt等表示要跳转去执行的代码。以vector_und为例,它仍在arch/arm/kernel/entry-armv.S中,通过vector_stub宏来定义,
vector_stub und, UND_MODE
.long __und_usr @ 0 (USR_26 / USR_32) 在用户模式执行了未定
义指令
.long __und_invalid @ 1 (FIQ_26 / FIQ_32)在FIQ模式执行了未定义指令
.long __und_invalid @ 2 (IRQ_26 / IRQ_32)在IRQ模式下执行了未定义指令
.long __und_svc @ 3 (SVC_26 / SVC_32)在管理模式下执行了未定义指令
.long __und_invalid @ 4
.long __und_invalid @ 5
.long __und_invalid @ 6
.long __und_invalid @ 7
.long __und_invalid @ 8
.long __und_invalid @ 9
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.long __und_invalid @ f
vector_stub是一个宏,它根据后面的参数"und,UND_MODE"定义了以“vector_und”为标号的一段代码。vector_stub宏的功能为:计算处理完异常后的返回地址、保存一引起寄存器(比如r0、lr、spsr),然后进行管理模式,最后根据被中断的工作模式调用下面的某个跳转分支。当发生异常时,CPU会根据异常的类型进入某个工作模式,但是很快vector_stub宏又会强制CPU进行管理模式,在管理模式下进行后续处理,这种方法简化了程序的设计,使得异常发生前的工作模式根毛是用户模式,要么是管理模式。
代码表示在各个工作模式下执行未定义指令时,发生异常的处理分支。比如__und_usr表示在用户模式下执行未定义指令时,所发生的未定义异常将由它来处理;__und_svc表示在管理模式下执行未定义指令时,所发生的未定义异常将由它来处理。在其他工作模式下不可能发生未定义指令异常,否则使用“__und_invalid”来处理错误。ARM架构CPU中使用4位数据来表示工作模式,所以共有16个跳转分支,目前只有7个工作模式。
不同的跳转分支只是在它们的入口下(比如保存被中断程序的寄存器)稍有差别,后续的处理大体相同,都在调用相应的C函数,比如未定义指令异常发生时,最终会调用C函数do_undefinstr来进行处理。各种异常C处理函数可以分为5类,它们分布在不同的文件中
(1)在arch/arm/kernel/traps.c中
未定义指令异常的C处理函数在这个文件中定义,总入口函数为do_undefinstr
(2)在arch/arm/mm/fault.c中
与内存访问相关的异常C处理函数在这个文件中定义,比如数据访问中止异常、指令预取中止异常。总入口函数为do_DataAbort、do_prefetchAbort。
(3)在arch/arm/mm/irq.c中
中断处理函数的在这个文件中定义,总入口函数为asm_do_IRQ,它调用其他文件注册的中断处理函数
(4)在arch/arm/kernel/calls.S
在这个文件中,swi异常的处理函数指针被组织成一个表格;swi指令机器码的位[23:0]被用来作为索引。这样,通过不同的swi index指令就可以调用不同的swi异常处理函数,它们被称为系统调用,比如sys_open、sys_read等。
(5)没有使用的异常
没有使用FIQ异常
trap_init函数搭建了各类异常的处理框架。当发生异常时,各种C处理函数会被调用。这些C函数还要进
一步细分异常发生的情况,分别调用更具体的处理函数。
2.init_IRQ函数分析
中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须、共用的中断(比如系统时钟中断、片内外设UART中断)外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建一个非常容易扩充的中断处理体系。
init_IRQ函数(代码在arch/arm/kernel/irq.c中)被用来初始化中断和处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数asm_do_IRQ就可以调用这些函数进行下一步处理。
Linux中断处理体系结构分析(二)
1.中断处理的体系结构
我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分。在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interrupt handler)或中断服务例程(interrupt service routine ,ISP).产生中断的每个设备都有一个相应的中断处理程序,中断处理程序通常不和特定的设备关联,而是和特定的中断关联的,也就是说,如果一个设备可以产生多种不同的中断,那么该就可以对应多个中断处理程序,相应的,该设备的驱动程序也就要准备多个这样的函数。在Linux内核中处理中断是分为上半部(top half),和下半部(bottom half)之分的。上半部只做有严格时限的工作,例如对接收到的中断进行应答或复位硬件,这些工作是在所有的中断被禁止的
情况下完成的,能够被允许稍后完成的工作会推迟到下半部去。要想了解上半部和下半部的机制可以阅读一下《Linux内核设计与实现》的第七章的内容。
Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称、中断状态、中断标记(比如中断类型、是否共享中断等),并提供了中断的低层硬件访问函数(清除、屏蔽、使能中断),提供了这个中断的处理函数入口,通过它可以调用用户注册的中断处理函数。
通过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 int (*startup)(unsigned int irq);//启动中断,如果不设置,缺省为“enable
void (*shutdown)(unsigned int irq);/*关闭中断,如果不设置,缺省为"disable"*/
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链表中去除不需要的项。
2.中断处理体系结构的初始化
init_IRQ函数被用来初始化中断处理体系结构,代码在arch/arm/kernel/irq.c中
153 void __init init_IRQ(void)
154 {
155 int irq;
156
157 for (irq = 0; irq < NR_IRQS; irq++)
158 irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
159
160 init_arch_irq();
161 }
157~~158行 初始化irq_desc结构数组中每一项的中断状态
第160行调用架构相关的中断初始化函数。对于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)
534 {
535 unsigned long pend;
536 unsigned long last;
537 int irqno;
538 int i;
........
637 for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
638 irqdbf("registering irq %d (extended s3c irq)\n", irqno);
639 set_irq_chip(irqno, &s3c_irqext_chip);
640 set_irq_handler(irqno, handle_edge_irq);
641 set_irq_flags(irqno, IRQF_VALID);
...............
655 for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
656 irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
657 set_irq_chip(irqno, &s3c_irq_uart1);
658 set_irq_handler(irqno, handle_level_irq);
659 set_irq_flags(irqno, IRQF_VALID);
660 }
..........
676 irqdbf("s3c2410: registered interrupt handlers\n");
677 }
678
在639行set_irq_chip函数的作用就是“irq_desc[irno].chip = &s3c_irqext_chip”,以后就可能通过irq_desc[irqno].chip结构中的函数指针设置这些外部中断的触发方式(电平触发,边沿触发),使能中断,禁止中断。
在640行设置这些中断的处理函数入口为handle_edge_irq,即“irq_desc[irqno].handle_irq =handle_edge_irq”.发生中断时,handle_edge_irq函数会调用用户注册的具体处理函数; 在641行设置中断标志为“IRQF_VALID”,表示可以使用它们。init_IRQ函数执行完后,irq_desc数组项的chip,handl_irq成员都被设置
2.2 用户注册中断处理函数的过程
用户驱动程序通过request_irq函数向内核注册中断处理函数,request_irq函数根据中断号找到irq_desc数组项,然后在它的action链表添加一个表项。原先的内核中requset_irq函数在kernel/irq/manage.c中定义,而现在2.6.32版本中,进行了改变,可以看这篇文章http://eeek.borgchat.net/lists/newbies/msg39146.html ,这里解释了,在2.6.32内核中我们可以看到找不到了request_irq函数的实现,而是用request_threaded_irq()函数给替换了。我们可以在inclue/linux/interrupt.h中找到这个函数的原型。
110 #ifdef CONFIG_GENERIC_HARDIRQS
111 extern int __must_check
112 request_threaded_irq(unsigned int irq, irq_handler_t handler,
113 irq_handler_t thread_fn,
114 unsigned long flags, const char *name, void *dev);
115
116 static inline int __must_check
117 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
118 const char *name, void *dev)
119 {
120 return request_threaded_irq(irq, handler, NULL, flags, name, dev);
121 }
123 extern void exit_irq_thread(void);
124 #else
126 extern int __must_check
127 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
128 const char *name, void *dev);
136 static inline int __must_check
137 request_threaded_irq(unsigned int irq, irq_handler_t handler,
138 irq_handler_t thread_fn,
139 unsigned long flags, const char *name, void *dev)
140 {
141 return request_irq(irq, handler, flags, name, dev);
142 }
143
144 static inline void exit_irq_thread(void) { }
145 #endif
其实具体的实现在request_threaded_irq函数中,也是在/kernel/irq/manage.c中定义,requset_threaded_irq函数首先使用这4个参数构造一个irqaction结构,然后调用setup_irq函数将它链入链表中,
1003 int request_threaded_irq(unsigned int irq, irq_handler_t handler,
1004 irq_handler_t thread_fn, unsigned long irqflags,
1005 const char *devname, void *dev_id)
.............
1056 action->handler = handler;
1057 action->thread_fn = thread_fn;
1058 action->flags = irqflags;
1059 action->name = devname;
1060 action->dev_id = dev_id;
1061
1062 chip_bus_lock(irq, desc);
1084 local_irq_restore(flags);
1085 enable_irq(irq);
...........
1088 return retval;
1089 }
1090 EXPORT_SYMBOL(request_threaded_irq);
setup_irq函数也是在kernel/irq.manage.c中定义,它完成如下3个主要功能
(1)将新建的irqaction结构链入irq_desc[irq]结构的action链表中,这有两种可能。
如果action链表为空,则直接链入,否则先判断新建的irqaction结构和链表中的irqaction结构所表示的中断类型是否一致,即是否都声明为"可共享的"(IRQF_SHARED)、是否都使用相同的触发方式,如果一致,则将新建的irqation结构链入
(2)设置irq_desc[irq]结构中chip成员的还没设置的指针,让它们指向一些默认函数
chip成员在init_IRQ函数初始化中断体系结构的时候已经设置了,这里只是设置其中还没设置的指针这通过irq_chip_set_defaults函数来完成,它在kernel/irq/chip.c中定义
296 void irq_chip_set_defaults(struct irq_chip *chip)
297 {
298 if (!chip->enable)
299 chip->enable = default_enable;//调用chip->unmask
300 if (!chip->disable)
301 chip->disable = default_disable;//此函数为空
302 if (!chip->startup)
303 chip->startup = default_startup;//调用chip->enable
310 if (!chip->shutdown)
311 chip->shutdown = chip->disable != default_disable ?
312 chip->disable : default_shutdown;
313 if (!chip->name)
314 chip->name = chip->typename;
315 if (!chip->end)
316 chip->end = dummy_irq_chip.end;
317 }
(4)启动中断
如果irq_desc[irq]结构中status成员没有被指明IRQ_NOAUTOEN(表示注册中断时不要使用中断),还要调用chip->startup或chip->enable来启动中断,所谓启动中断通常就是使用中断。一般情况下,只有那些“可以自动使能的”中断对应的irq_desc[irq].status才会被指明为IRQ_NOAUTOEN,所以,无论哪种情况,执行request_irq注册中断之后,这个中断就已经被使能了。
总结一下request_irq函数注册
(1)irq_des[irq]结构中的action链表中已经链入了用户注册的中断处理函数
(2)中断的触发方式已经被设好
(3)中断已经被使能
2.3 中断的处理过程
asm_do_IRQ是中断的C语言总入口函数,它在/arch/arm/kernel/irq.c中定义,
106 asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
107 {
108 struct pt_regs *old_regs = set_irq_regs(regs);
109
110 irq_enter();
111
112 /*
113 * Some hardware gives randomly wrong interrupts. Rather
114 * than crashing, do something sensible.
115 */
116 if (unlikely(irq >= NR_IRQS)) {
117 if (printk_ratelimit())
118 printk(KERN_WARNING "Bad IRQ%u\n", irq);
119 ack_bad_irq(irq);
120 } else {
121 generic_handle_irq(irq);
122 }
123
124 /* AT91 specific workaround */
125 irq_finish(irq);
126
127 irq_exit();
128 set_irq_regs(old_regs);
129 }
st1\:*{behavior:url(#ieooui) }
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成员函数
453 s3c_irq_demux_extint8(unsigned int irq,
454 struct irq_desc *desc)
455 {
456 unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); //EINT8-EINT23 发生时,相应位被置1
457 unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);//屏蔽寄存器
458
459 eintpnd &= ~eintmsk; //清除被屏蔽的位
460 eintpnd &= ~0xff; /* 清除低8位(EINT8对应位8)ignore lower irqs */
461
462 /* 循环处理所有子中断*/
463
464 while (eintpnd) {
465 irq = __ffs(eintpnd); //确定eintpnd中为1的最高位
466 eintpnd &= ~(1<<irq); //将此们清0
467
468 irq += (IRQ_EINT4 - 4);//重新计算中断号,前面计算出irq等于8时,中断号为
IRQ_EINT8
469 generic_handle_irq(irq);//调用这中断的真正的处理函数
470 }
471
472 }
void
(4)IRQ_EINT8--IRQ_EINT23这几个中断的处理函数入口,在init_IRQ函数初始化中断体系结构的时候已经被设置为handle_edge_irq函数,desc_handle_irq(irq,irq_desc+irq)就是调用这个函数,它在kernel/irq/chip.c中定义,它用来处理边沿触发的中断,
中断发生的次数统计
531 handle_edge_irq(unsigned int irq, struct irq_desc *desc)
532 {
533 spin_lock(&desc->lock);
534
535 desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
536
537 /*
538 * If we're currently running this IRQ, or its disabled,
539 * we shouldn't process the IRQ. Mark it pending, handle
540 * the necessary masking and go out
541 */
542 if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
543 !desc->action)) {
544 desc->status |= (IRQ_PENDING | IRQ_MASKED);
545 mask_ack_irq(desc, irq);
546 goto out_unlock;
547 }
548 kstat_incr_irqs_this_cpu(irq, desc);
549
550 /* Start handling the irq */
551 if (desc->chip->ack)
552 desc->chip->ack(irq);
553
554 /* Mark the IRQ currently in progress.*/
555 desc->status |= IRQ_INPROGRESS;
556
557 do {
558 struct irqaction *action = desc->action;
559 irqreturn_t action_ret;
560
561 if (unlikely(!action)) {
562 desc->chip->mask(irq);
563 goto out_unlock;
564 }
565
566 /*
567 * When another irq arrived while we were handling
568 * one, we could have masked the irq.
569 * Renable it, if it was not disabled in meantime.
570 */
571 if (unlikely((desc->status &
572 (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
573 (IRQ_PENDING | IRQ_MASKED))) {
574 desc->chip->unmask(irq);
575 desc->status &= ~IRQ_MASKED;
576 }
577
578 desc->status &= ~IRQ_PENDING;
579 spin_unlock(&desc->lock);
580 action_ret = handle_IRQ_event(irq, action);
581 if (!noirqdebug)
582 note_interrupt(irq, desc, action_ret);
583 spin_lock(&desc->lock);
584
585 } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
586
587 desc->status &= ~IRQ_INPROGRESS;
588 out_unlock:
589 spin_unlock(&desc->lock);
590 }
591
st1\:*{behavior:url(#ieooui) }
响应中断,通常是清除当前中断使得可以接收下一个中断,对于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中定义。
do {
379 trace_irq_handler_entry(irq, action);
380 ret = action->handler(irq, action->dev_id);//执行用户注册的中断处理函数
381 trace_irq_handler_exit(irq, action, ret);
382
383 switch (ret) {
384 case IRQ_WAKE_THREAD:
385 /*
386 * Set result to handled so the spurious check
387 * does not trigger.
388 */
389 ret = IRQ_HANDLED;
390
391 /*
392 * Catch drivers which return WAKE_THREAD but
393 * did not set up a thread function
394 */
395 if (unlikely(!action->thread_fn)) {
396 warn_no_thread(irq, action);
397 break;
398 }
399
400 /*
408 if (likely(!test_bit(IRQTF_DIED,
409 &action->thread_flags))) {
410 set_bit(IRQTF_RUNTHREAD, &action->thread_flags);
411 wake_up_process(action->thread);
412 }
413
414 /* Fall through to add to randomness */
415 case IRQ_HANDLED:
416 status |= action->flags;
417 break;
418
419 default:
420 break;
421 }
422
423 retval |= ret;
424 action = action->next; //下一个
425 } while (action);
用户注册的中断处理函数的参数为中断号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内核设计与实现》。
Linux中断处理体系结构分析的更多相关文章
- 字符设备驱动-----Linux中断处理体系结构
一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...
- 1.字符设备驱动------Linux中断处理体系结构
一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...
- linux中断处理原理分析
Tasklet作为一种新机制,显然可以承担更多的优点.正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行.在软中断时代,显然没有这种考虑.因此同一 ...
- Facebook的体系结构分析---外文转载
Facebook的体系结构分析---外文转载 From various readings and conversations I had, my understanding of Facebook's ...
- [国嵌攻略][119][Linux中断处理程序设计]
裸机中断: 1.中断统一入口. 2.注册中断处理程序. 3.根据中断源编号,调用中断处理程序. Linux中断 1.在entry-armv.S中的_irq_svc是中断统一入口. 2.获取产生中断源的 ...
- linux中断处理上下部分
一.linux中断处理为什么要分为上下部 1.1. 中断处理的上半部(top half,又叫顶半部)和处理的下半部(bottom half,又叫底半部) 1.1. linux中断处理不参与调度,故中断 ...
- Linux.中断处理.入口x86平台entry_32.S
Linux.中断处理.入口x86平台entry_32.S Linux.中断处理.入口x86平台entry_32.S 在保护模式下处理器是通过中断号和IDTR找到中断处理程序的入口地址的.IDTR存的是 ...
- Linux内核中断处理体系分析
前一篇博文中:linux内核初始化阶段通过early_trap_init()函数完毕了把异常向量复制到0xFFFF0000開始的地方,这些异常向量大部分指向通过vector_stub宏定义的那段代码. ...
- Linux 中断处理浅析
最近在研究异步消息处理, 突然想起linux内核的中断处理, 里面由始至终都贯穿着"重要的事马上做, 不重要的事推后做"的异步处理思想. 于是整理一下~ 第一阶段--获取中断号 每 ...
随机推荐
- WINDOWS下PHP 的pear DB的安装(本地环境:PHP5.4.15+Apache+mysql)
因为需要安装phpunit,要先装pear,网上的教程大多数是以双击go-pear.bat开始,但是我安装的php文件夹里压根没有这个文件. 经过几次搜索之后终于找到了办法. 解决步骤如下: 1.下载 ...
- Java注意的地方
oo: 单一原则(SRP) 开放封闭原则(OCP) 里氏替换原则(LSP) 依赖倒转原则(DIP) 接口分离原则(ISP) equals: 若两个对象equals为true,则他们的hashcode值 ...
- Android实现发短信与打电话的功能
//发短信 class SendMsgClickListener implements OnClickListener { public void onClick(View v) { //调用Andr ...
- How to achieve dialog with lookup control
How to create a dialog with the lookup as a control, the other control SalesId ItemId lookup is the ...
- Lua 常用的shell命令
lua作为一种小巧的脚本语言,其函数等动作可以使用shell命令进行运行和调试,以下是几个常用的shell命令.基本格式是 lua [选项参数] [脚本参数] (1)%lua 程序名.lua ...
- openerp经典收藏 对象的预定义方法(转载)
对象的预定义方法 原文:http://shine-it.net/index.php/topic,2159.15.html 每个OpenERP的对象都有一些预定义方法,这些方法定义在基类osv.osv中 ...
- 内部技术分享的 PPT
本文的基础是搞了一次内部的技术分享,在此也分享一下本次的PPT的一些内容.先列一下大概内容吧. EF-Code First API(WCF.WebAPI) Xaml MVVM AOP Xamarin. ...
- C#错误:The Controls collection cannot be modified
用 <%# %>这种写法是写在数据绑定控件中的,之所以用 <%= %>会出现The Controls collection cannot be modified because ...
- Ext学习-布局介绍
1.目标 了解ExtJS中的关于布局和组建的相关原理,并学习相关的布局方式 2.内容 1.布局和组件的相关原理 2.常见的布局方式 3.学习流程 1.首先应该学习一下布局和组件的相 ...
- 【转载】Spring加载resource时classpath*:与classpath:的区别
免责声明: 本文转自网络文章,转载此文章仅为个人收藏,分享知识,如有侵权,请联系博主进行删除. 原文作者:kyfxbl 原文地址: spring配置中classpath和cla ...