ULK第四章里清晰讲到“Linux完结了一种没有优先级的中止模型”,并且“Linux中止和反常都支撑嵌套”。这个我不太了解了,这两种说法都与我曾经的了解刚好相反,核对了原书,翻译没有错。
Linux中止体系终究是否支撑优先级,可否嵌套,中止号又是怎样来确认的,中止产生时又是怎样一步步履行到中止处理函数的。为了完全搞懂Linux中止体系,我决议从最原始材料动身,一探终究。(s3c2440+linux2.6.21)
先来看看ARM的硬件履行流程
反常是ARM处理器形式分类,ARM有七种运转形式USR,SYS,SVC,IRQ,FIQ,UND,ABT
五种反常形式:SVC,IRQ,FIQ,UND,ABT
中止形式是ARM反常形式之一(IRQ形式,FIQ形式),是一种异步事情,如外部按键产生中止,内部定时器产生中止,通讯数据口数据收发产生中止等。
1.当一个反常产生时,以FIQ为例,CPU切入FIQ形式时,
①将本来履行程序的下一条指令地址保存到LR中,便是将R14保存到R14_fiq里边。
②复制CPSR到SPSR_fiq。
③改动CPSR形式位的值,改到FIQ形式。
④改动PC值,将其指向相应的反常处理向量表。
脱离反常处理的时分,
①将LR(R14_fiq)赋给PC。
②将SPSR(SPSR_fiq)复制到CPSR。
③铲除中止制止标志(假设开端时置位了)。
ARM一般在某个固定地址中有一个反常向量表,比方0x0
当一个外部IRQ中止产生时
①处理器切换到IRQ形式
②PC跳到0x18处运转,由于这是IRQ的中止进口。
③经过0x18:LDR PC, IRQ_ADDR,跳转到相应的中止服务程序。这个中止服务程序就要确认中止源,每个中止源会有自己独立的中止服务程序。
④得到中止源,然后履行相应中止服务程序
⑤铲除中止标志,回来
这便是一个外部中止完好的履行流程了,下面以详细寄存器来更详细的了解ARM的中止机制。
假定ARM核有两个中止引脚,一根是irq pin,一根是fiq pin,正常状况下,ARM核仅仅机械地跟着PC指示去履行,当CPSR中的I位和F位都为1时,IRQ和FIQ都处于制止状态,这时分不管发什么信号,ARM都不会答理。
当I位或F位为0时,irq pin有中止信号过来时,ARM当时作业就会被打断,切换到IRQ形式,并且跳转到反常向量表的中止进口0x18,SRCPND中相应方位1,经过查看中止优先级寄存器以及屏蔽寄存器,确认中止源,INTPND相应方位1(经过裁定,只要一方位1),这进程由ARM主动完结。0x18寄存的是总的中止处理函数,在这个函数里,能够树立一个二级中止向量表,先铲除SRCPND相应位,然后依据中止源履行相应中止服务程序,铲除INTPND,回来。
及时铲除中止Pending寄存器的标志位是为了防止两个问题:①产生中止回来后,当即又被中止,不断的重复呼应②丢掉中止处理进程中产生的中止,回来后不呼应。
在某个IRQ中止程序履行进程中,有别的一个外部IRQ中止产生,会将SRCPND相应方位1,等该中止服务履行完,经过裁定决议下一个要呼应的中止。可是假设当产生的是FIQ,则保存当时IRQ的现场,嵌套呼应FIQ,FIQ服务程序履行完,再持续履行IRQ服务。那么当一个FIQ正在服务,产生别的一个FIQ,会怎样呢,答案是不会被打断,跟IRQ相同等当时中止服务完结,再裁定剩下需求相应的中止。
所以得出这样的定论:
①关于中止嵌套:IRQ形式只能被FIQ形式打断,FIQ形式下谁也打不断。
②关于优先级:ARM核对中止优先级,有清晰的可编程办理。
下面再来看看Linux对ARM是怎样处理的,记住一个条件:Linux对ARM的硬件特性能够取舍,但不可更改。
1.树立反常向量表:
体系从arch/arm/kernel/head.S的ENTRY(stext)开端履行,__lookup_processor_type查看处理器ID,__lookup_machine_type查看机器ID,__create_page_tables创立页表,发动MMU,然后由arch/arm/kernel/head_common.S跳到start_kernel()->trap_init()
点击(此处)折叠或翻开
- void __init trap_init(void)
- {
- unsigned long vectors=CONFIG_VECTORS_BASE;
- …
- 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);
- …
- }
- #define CONFIG_VECTORS_BASE 0xffff0000
CONFIG_VECTORS_BASE在autoconf.h界说,在ARMV4及V4T今后的大部分处理器中,中止向量表的方位能够有两个方位:一个是0,另一个是0xffff0000。能够经过CP15协处理器c1寄存器中V位(bit[13])操控。V和中止向量表的对应联络如下:
V=0~0x00000000~0x0000001C
V=1~0xffff0000~0xffff001C
__vectors_end至__vectors_start之间为反常向量表,坐落arch/arm/kernel/entry-armv.S
点击(此处)折叠或翻开
- .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 //一般中止反常
- b vector_fiq + stubs_offset//快速中止反常
- .globl __vectors_end
- __vectors_end:
stubs_offset值如下:
.equstubs_offset,__vectors_start+0x200-__stubs_start
stubs_offset是怎样确认的呢?(引证网络上的一段比较详细的解说)
当汇编器看到B指令后会把要跳转的标签转化为相关于当时PC的偏移量(±32M)写入指令码。从上面的代码能够看到中止向量表和stubs都产生了代码搬移,所以假设中止向量表中依然写成bvector_irq,那么实践履行的时分就无法跳转到搬移后的vector_irq处,由于指令码里写的是本来的偏移量,所以需求把指令码中的偏移量写成搬移后的。咱们把搬移前的中止向量表中的irq进口地址记irq_PC,它在中止向量表的偏移量便是irq_PC-vectors_start,vector_irq在stubs中的偏移量是vector_irq-stubs_start,这两个偏移量在搬移前后是不变的。搬移后vectors_start在0xffff0000处,而stubs_start在0xffff0200处,所以搬移后的vector_irq相关于中止向量中的中止进口地址的偏移量便是,200+vector_irq在stubs中的偏移量再减去中止进口在向量表中的偏移量,即200+vector_irq-stubs_start-irq_PC+vectors_start=(vector_irq-irq_PC)+vectors_start+200-stubs_start,关于括号内的值实践上便是中止向量表中写的vector_irq,减去irq_PC是由汇编器完结的,然后边的vectors_start+200-stubs_start就应该是stubs_offset,实践上在entry-armv.S中也是这样界说的。
2.中止呼应
当有外部中止产生时,跳转到反常向量表的“bvector_irq + stubs_offset //一般中止反常”
进入反常处理函数,跳转的进口方位arch\arm\kernel\entry-armv.S代码简略如下
点击(此处)折叠或翻开
- .globl __stubs_start
- __stubs_start:
- /*
- * 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)
- vector_stub dabt, ABT_MODE, 8
- vector_stub pabt, ABT_MODE, 4
- vector_stub und, UND_MODE
- /*
- * Undefined FIQs
- */
- vector_fiq:
- disable_fiq
- subs pc, lr, #4
- vector_addrexcptn:
- b vector_addrexcptn
vector_stub是个函数调用宏,依据中止前的作业形式决议进入__irq_usr,__irq_svc。这儿入__irq_svc,一起看到这儿FIQ产生时,体系未做任何处理,直接回来,即Linux没有供给对FIQ的支撑,持续跟进代码
点击(此处)折叠或翻开
- __irq_svc:
- svc_entry
- …
- irq_handler
svc_entry是一个宏,首要完结了将SVC形式下的寄存器、中止回来地址保存到仓库中。然后进入最中心的中止呼应函数irq_handler,irq_handler完结进程arch\arm\kernel\entry-armv.S
点击(此处)折叠或翻开
- .macro irq_handler
- get_irqnr_preamble r5, lr
- 1: get_irqnr_and_base r0, r6, r5, lr @判别中止号,经过R0回来,3.5节有完结进程
- movne r1, sp
- @
- @ routine called with r0 = irq number, r1 = struct pt_regs *
- @
- adrne lr, 1b
- bne asm_do_IRQ @进入中止处理。
- ……
- .endm
get_irqnr_and_base中止号判别进程,include/asm/arch-s3c2410/entry-macro.s
点击(此处)折叠或翻开
- .macro get_irqnr_and_base, irqnr, irqstat, base, tmp
- mov \base, #S3C24XX_VA_IRQ
- @@ try the interrupt offset register, since it is there
- ldr \irqstat, [ \base, #INTPND ]
- teq \irqstat, #0
- beq 1002f
- ldr \irqnr, [ \base, #INTOFFSET ] @经过判别INTOFFSET寄存器得到中止方位
- …
- @@ we have the value
- 1001:
- adds \irqnr, \irqnr, #IRQ_EINT0 @加上中止号的基准数值,得到终究的中止号,留意:此刻没有考虑子中止的详细状况。IRQ_EINT0在include/asm/arch-s3c2410/irqs.h中界说.从这儿能够看出,中止号的详细值是有渠道相关的代码决议的,和硬件中止挂起寄存器中的中止号是不等的。
- 1002:
- @@ exit here, Z flag unset if IRQ
- .endm
asm_do_IRQ完结进程,arch/arm/kernel/irq.c
点击(此处)折叠或翻开
- asmlinkage void asm_do_IRQ(unsignedintirq,struct pt_regs*regs)
- {
- struct pt_regs*old_regs=set_irq_regs(regs);
- struct irq_desc*desc=irq_desc+irq;//依据中止号,找到呼应的irq_desc
- /*
- *Some hardware gives randomly wrong interrupts.Rather
- *than crashing,dosomething sensible.
- */
- if(irq>=NR_IRQS)
- desc=&bad_irq_desc;
- irq_enter();
- desc_handle_irq(irq,desc);//依据irq和desc进入中止处理
- /*AT91 specific workaround*/
- irq_finish(irq);
- irq_exit();
- set_irq_regs(old_regs);
- }
- static inline void desc_handle_irq(unsignedintirq,struct irq_desc*desc)
- {
- desc->handle_irq(irq,desc);//中止处理
- }
上述asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)运用了asmlinkage标识。那么这个标识的意义怎样了解呢?
该符号界说在kernel/include/linux/linkage.h中,如下所示:
#include //各个详细处理器在此文件中界说asmlinkage
点击(此处)折叠或翻开
- #ifdef __cplusplus
- #define CPP_ASMLINKAGE extern”C”
- #else
- #define CPP_ASMLINKAGE
- #endif
- #ifndef asmlinkage//假设曾经没有界说asmlinkage
- #define asmlinkage CPP_ASMLINKAGE
- #endif
关于ARM处理器的,没有界说asmlinkage,所以没有意义(不要认为参数是从仓库传递的,关于ARM渠道来说仍是契合ATPCS进程调用规范,经过寄存器传递的)。
但关于X86处理器的中是这样界说的:
#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
表明函数的参数传递是经过仓库完结的。
中止处理进程代码就跟到这了,那么最终一个问题desc->handle_irq(irq, desc);是怎样跟咱们注册的中止函数相关联的呢?再从中止模型注册下手:
中止相关的数据结构:在include/asm/arch/irq.h中界说。
irq_desc[]是一个指向irq_desc_t结构的数组,irq_desc_t结构是各个设备中止服务例程的描述符。Irq_desc_t结构体中的成员action指向该中止号对应的irqaction结构体链表。Irqaction结构体界说在include/linux/interrupt.h中,如下:
点击(此处)折叠或翻开
- truct irqaction{
- irq_handler_t handler;//中止处理函数,注册时供给
- unsigned long flags;//中止标志,注册时供给
- cpumask_t mask;//中止掩码
- constchar*name;//中止称号
- void*dev_id;//设备id,本文后边部分介绍中止同享时会详细阐明这个参数的效果
- struct irqaction*next;//假设有中止同享,则持续履行,
- intirq;//中止号,注册时供给
- struct proc_dir_entry*dir;//指向IRQn相关的/proc/irq/n目录的描述符
- };
在注册中止号为irq的中止服务程序时,体系会依据注册参数封装相应的irqaction结构体。并把中止号为irq的irqaction结构体写入irq_desc [irq]->action。这样就把设备的中止请求号与该设备的中止服务例程irqaction联络在一起了。当CPU接收到中止请求后,就能够依据中止号经过irq_desc []找到该设备的中止服务程序。
3.中止同享的处理模型
同享中止的不同设备的iqraction结构体都会添加进该中止号对应的irq_desc结构体的action成员所指向的irqaction链表内。当内核产生中止时,它会顺次调用该链表内一切的handler函数。因而,若驱动程序需求运用同享中止机制,其中止处理函数有必要有才能辨认是否是自己的硬件产生了中止。一般是经过读取该硬件设备供给的中止flag标志位进行判别。也便是说不是任何设备都能够做为中止同享源的,它有必要能够经过的它的中止flag判别出是否产生了中止。
中止同享的注册办法是:
int request_irq(unsigned int irq, irq_handler_t handler,IRQF_SHARED, const char *devname, void *dev_id)
许多威望材料中都说到,中止同享注册时的注册函数中的dev_id参数是必不可少的,并且dev_id的值有必要仅有。那么这儿供给仅有的dev_id值的终究是做什么用的?
依据咱们前面中止模型的常识,能够看动身生中止时,内核并不判别终究是同享中止线上的哪个设备产生了中止,它会循环履行一切该中止线上注册的中止处理函数(即irqaction->handler函数)。因而irqaction->handler函数有职责辨认出是否是自己的硬件设备产生了中止,然后再履行该中止处理函数。一般是经过读取该硬件设备供给的中止flag标志位进行判别。那已然kernel循环履行该中止线上注册的一切irqaction->handler函数,把辨认终究是哪个硬件设备产生了中止这件事交给中止处理函数自身去做,那request_irq的dev_id参数终究是做什么用的?
许多材料中都主张将设备结构指针作为dev_id参数。在中止到来时,敏捷地依据硬件寄存器中的信息对比传入的dev_id参数判别是否是本设备的中止,若不是,应敏捷回来。这样的说法没有问题,也是咱们编程时都遵从的办法。但事实上并不能够阐明为什么中止同享有必要要设置dev_id。
下面解说一下dev_id参数为什么有必要的,并且是有必要仅有的。
当调用free_irq刊出中止处理函数时(一般卸载驱动时其中止处理函数也会被刊出掉),由于dev_id是仅有的,所以能够经过它来判别从同享中止线上的多个中止处理程序中删去指定的一个。假设没有这个参数,那么kernel不可能知道给定的中止线上终究要删去哪一个处理程序。
刊出函数界说在Kernel/irq/manage.c中界说:
void free_irq(unsigned int irq, void *dev_id)
4.S3C2410子中止的注册的完结
前面判别中止号的办法,能够看到仅仅经过S3C2410中止操控器中的INTOFFSET寄存器来判别的。关于INTPND中的EINT4_7、EINT8_23、INT_UART0、INT_ADC等带有子中止的向量,INTOFFSET无法判别出详细的中止号。渠道留给咱们的注册办法如下:
在include/asm/arch/irqs.h中有相似如下界说:
点击(此处)折叠或翻开
- /*interrupts generated from the external interrupts sources*/
- #define IRQ_EINT4 S3C2410_IRQ(32)/*48*/
- #define IRQ_EINT5 S3C2410_IRQ(33)
- #define IRQ_EINT6 S3C2410_IRQ(34)
- #define IRQ_EINT7 S3C2410_IRQ(35)
- #define IRQ_EINT8 S3C2410_IRQ(36)
- #define IRQ_EINT9 S3C2410_IRQ(37)
- #define IRQ_EINT10 S3C2410_IRQ(38)
- #define IRQ_EINT11 S3C2410_IRQ(39)
- #define IRQ_EINT12 S3C2410_IRQ(40)
- #define IRQ_EINT13 S3C2410_IRQ(41)
- #define IRQ_EINT14 S3C2410_IRQ(42)
- #define IRQ_EINT15 S3C2410_IRQ(43)
- #define IRQ_EINT16 S3C2410_IRQ(44)
- #define IRQ_EINT17 S3C2410_IRQ(45)
- #define IRQ_EINT18 S3C2410_IRQ(46)
- #define IRQ_EINT19 S3C2410_IRQ(47)
- #define IRQ_EINT20 S3C2410_IRQ(48)/*64*/
- #define IRQ_EINT21 S3C2410_IRQ(49)
- #define IRQ_EINT22 S3C2410_IRQ(50)
- #define IRQ_EINT23 S3C2410_IRQ(51)
能够看到渠道为每种子中止都界说了中止号,假设你想完结EINT10的中止注册,直接依照IRQ_EINT10这个中止号注册都能够了。那么渠道代码是怎样完结这部分中止注册的呢?
5.S3C2410子中止注册问题的处理
点击(此处)折叠或翻开
- /*arch/arm/plat-s3c24xx/irq.c*/
- void __init s3c24xx_init_irq(void)
- {……
- 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);
- ……
- }
渠道在初始化时会调用到s3c24xx_init_irq,在此函数中完结了对EINT4_7、EINT8_23、INT_UART0、INT_ADC等中止的注册。下面看看这些带有子中止的中止号对应的处理函数的内容。以IRQ_EINT4t7为例,其它状况相似。
点击(此处)折叠或翻开
- /*arch/arm/plat-s3c24xx/irq.c*/
- s3c_irq_demux_extint4t7(unsignedintirq,
- struct irq_desc*desc)
- {
- unsigned long eintpnd=__raw_readl(S3C24XX_EINTPEND);
- unsigned long eintmsk=__raw_readl(S3C24XX_EINTMASK);
- eintpnd&=~eintmsk;
- eintpnd&=0xff;/*only lower irqs*/
- /*eintpnd中能够有多个位一起置1,这一点和intpnd的只能有1个方位1是不相同的*/
- while(eintpnd){//循环履行一切置位的子中止
- irq=__ffs(eintpnd);//算出第一个不为0的位,相似arm v5后的clz前导0的效果
- eintpnd&=~(1<
- irq+=(IRQ_EINT4-4);//算出对应的中止号
- desc_handle_irq(irq,irq_desc+irq);//履行对应子中止的注册函数
- }
- }
从上面的函数能够看出子中止是怎样注册及被调用到的。有人可能会问为何不在include/asm/arch-s3c2410/entry-macro.s文件中get_irqnr_and_base函数判别中止号时,直接算出对应的子中止号,就能够直接找到子中止处理了呢?
原因是: get_irqnr_and_base是渠道给体系供给的函数,关于多个子中止一起置位的状况无法经过一个值回来(由于子中止中,如eintpnd是能够多个位一起置位的))。而intpnd则没有这个问题。
至此,关于s3c2440/10+linux2.6得出以下定论:
①不支撑中止嵌套(由于FIQ不支撑)
②有清晰中止优先级(可编程)
③中止号是依据硬件特性固定的,riq号经过某种转化得到与寄存器相应位,一般在irqs.h文件界说
中止的用法见Ldd3的笔记:http://blog.chinaunix.net/uid-24708340-id-3035617.html