三 呼应中止
首先在剖析源码之前,让咱们了解一些原理性的东西,咱们都知道在处理中止要保存当时现场状况,然后才干处理中止,处理完之后还要把现场状况康复过来才干回来到被中止的当地持续履行,这儿要阐明的是在指令跳转到中止向量的当地开端履行之前,CPU帮咱们做了哪些工作:
R14_irq =要履行的下条指令地址+ 4//这儿的下条指令是相关于被中止指令的下条。即回来地址
SPSR_irq = CPSR//保存的现场状况,r0到r12要由咱们软件来保存(假如需求的话)。
CPSR[4:0] = 0b10010//进入中止形式
CPSR[5] = 0//在ARM形式下履行(不是Thumb下)
CPSR[7] = 1//关掉IRQ中止,FIQ仍是开着
PC = 0Xffff0018/0x00000018//依据反常向量表的方位,跳转到特定的中止向量处去履行。
更详细的关于反常处理的细节可参阅<>
接下来咱们在来剖析watchdog发生中止后的处理流程:
当watchdog超不时将会发生中止,中止号便是IRQ_WDT,当发生中止时,体系将从跳转表中的中止方位开端运转,关于咱们这篇文章来说:是从0xffff0000 + 24处开端运转。 这个地址的指令是:
bvector_irq + stubs_offset
即直接跳转到vector_irq处去运转。这些都在中止初始化的时分剖析过了。
咱们来看vector_irq,它是经过宏vector_stub来界说的:
arch/arm/kernel/entry-armv.S:
/*
* Interrupt dispatcher
*/
vector_stubirq, IRQ_MODE, 4/*这是个宏界说*/
/*下面这些都是不同形式下的irq处理函数*/
.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
arch/arm/kernel/entry-armv.S:
.macrovector_stub, name, mode, correction=0
.align5
vector_/name:
.if /correction
sublr, lr, #/correction
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@
stmiasp, {r0, lr}@ save r0, lr
mrslr, spsr
strlr, [sp, #8]@ save spsr
@
@ Prepare for SVC32 mode.IRQs remain disabled.
@
mrsr0, cpsr
eorr0, r0, #(/mode ^ SVC_MODE)
msrspsr_cxsf, r0
@
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
movspc, lr@ branch to handler in SVC mode
.endm
这样翻开后vector_irq如下所示:
arch/arm/kernel/entry-armv.S:
vector_irq:
.if 4
@ lr保存的是被打断指令处地址+8的值,(看上面的剖析,由PC得到), 这儿-4则便是中止
@处理完后的回来地址,在中止处理完后该值会赋给PC
sublr, lr, #4
.endif
@
@ Save r0, lr_
@ (parent CPSR)
@ r0后边会用到所以要保存。
stmiasp, {r0, lr}@ save r0, lr,保存r0,lr到栈上,这儿的栈是中止形式下的。
mrslr, spsr@获取spsr的值,该值保存了被中止处履行环境的状况(参阅上面的剖析)
strlr, [sp, #8]@ save spsr, 保存到栈上
@
@ Prepare for SVC32 mode.IRQs remain disabled.
@
mrsr0, cpsr
eorr0, r0, #( IRQ_MODE ^ SVC_MODE)
msrspsr_cxsf, r0@把spsr设置成办理形式
@
@ the branch table must immediately follow this code
@
andlr, lr, #0x0f
movr0, sp
ldrlr, [pc, lr, lsl #2]
movspc, lr@ branch to handler in SVC mode @ pc = lr, cpsr = spsr
.endm
movs的意图目标假如是pc的话,则还会把spsr赋值给cpsr,上面咱们看到spsr被设成办理形式,因而这条句子往后的代码也就跑在了办理形式下。
此刻的栈状况如下:
S_FRAME_SIZE,S_PC在arch/arm/kernel/Asm-offsets.c:中界说
DEFINE(S_FRAME_SIZE,sizeof(struct pt_regs));
DEFINE(S_PC,offsetof(struct pt_regs, ARM_pc));
include/asm-arm/Ptrace.h:
struct pt_regs {
long uregs[18];
};
#define ARM_pcuregs[15]
,pt_regs中对应的便是上面栈上的18个寄存器,ARM_pc是pc寄存器寄存在这个数组中的偏移。
接着看get_thread_info, 它也是个宏,用来获取当时线程的地址。在我的一篇linux发动代码剖析里曾写过线程的界说方法:
include/linux/Sched.h:
union thread_union {
struct thread_info thread_info;/*线程特点*/
unsigned long stack[THREAD_SIZE/sizeof(long)];/*栈*/
};
由它界说的线程是8K字节对齐的, 并且在这8K的最低地址处寄存的便是thread_info目标,即该栈具有者线程的目标,而get_thread_info便是经过把sp低13位清0(8K鸿沟)来获取当时thread_info目标的地址。
arch/arm/kernel/entry-armv.S:
.macroget_thread_info, rd
mov/rd, sp, lsr #13
mov/rd, /rd, lsl #13
.endm
调用该宏后寄存器tsk里寄存的便是当时线程的地址了,tsk是哪个寄存器呢,咱们在看:
arch/arm/kernel/entry-header.S:
tsk.reqr9@ current thread_info
,tsk仅仅r9的别号罢了, 因而这时r9里保存的便是当时线程的地址。
咱们接着看irq_handler:
arch/arm/kernel/entry-armv.S:
.macroirq_handler
1:get_irqnr_and_base r0, r6, r5, lr@渠道相关,获取中止号
movner1, sp@假如r0(中止号)不等于0,则r1指向sp地点地址,即pt_regs目标地址(看上图)
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrnelr, 1b@假如r0(中止号)不等于0,lr(回来地址)等于标号1处,即
@ get_irqnr_and_base r0, r6, r5, lr的那行,即循环处理一切的中止。
bneasm_do_IRQ@处理该中止
#ifdef CONFIG_SMP
/*
* XXX
*
* this macro assumes that irqstat (r6) and base (r5) are
* preserved from get_irqnr_and_base above
*/
test_for_ipi r0, r6, r5, lr
movner0, sp
adrnelr, 1b
bnedo_IPI
#ifdef CONFIG_LOCAL_TIMERS
test_for_ltirq r0, r6, r5, lr
movner0, sp
adrnelr, 1b
bnedo_local_timer
#endif
#endif
.endm
get_irqnr_and_base是渠道相关的,这儿就不列出来了,关于s3c2410,代码在include/asm-arm/s3c2410/entry-macro.S里,该宏处理完后,r0 =中止号,接下来r1赋值为sp地址(pt_regs目标地址), 最终调用c函数asm_do_IRQ, r0, r1作为参数被传递进去。asm_do_IRQ()处理完后将回来到lr指向的地址处即上面汇编部分标号为1的地址处持续履行。
咱们把__irq_usr的汇编部分剖析完后再来剖析asm_do_IRQ()等c函数。
Arch/arm/kernel/entry-armv.S:
__irq_usr:
……
……
mov why, #0@ why = 0, why是r8的别号,
b ret_to_user@回来到用户形式下
咱们看ret_to_user
arch/arm/kernel/entry-common.S:
ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq@ disable interrupts@关中止,
ldrr1, [tsk, #TI_FLAGS] @获取thread_info中flags域的值
tstr1, #_TIF_WORK_MASK@判别task是否被堵塞
bnework_pending@依据需求进行进程的切换。
no_work_pending:
@ slow_restore_user_regs
ldrr1, [sp, #S_PSR]@ get calling cpsr获取被中止代码处的状况(cpsp)
ldrlr, [sp, #S_PC]!@ get pc获取回来地址(被中止代码的下条代码处的地址)
msrspsr_cxsf, r1@ save in spsr_svc, spsr里保存好被中止代码处的状况(cpsp)
ldmdbsp, {r0 – lr}^@ get calling r1 – lr从栈上获取用户态下的r0到lr的值
movr0, r0
addsp, sp, #S_FRAME_SIZE – S_PC@栈地址康复,防止多个中止后溢出
movspc, lr@ return & move spsr_svc into cpsr, 回来被中止代码处持续履行,并把spsr赋给cpsp,即康复被中止处的现场状况。这样CPU又能够从被中止的当地持续履行了,并且这个时分一切的寄存器值(r0到r12),包含状况寄存器值(cpsr)都是源码被中止时的值。
咱们趁便看下work_pending
arch/arm/kernel/entry-common.S:
work_pending:
tstr1, #_TIF_NEED_RESCHED@判别是否需求调度进程
bnework_resched@进程调度
tstr1, #_TIF_NOTIFY_RESUME | _TIF_SIGPENDING
beqno_work_pending@无需调度,回来
movr0, sp@ regs
movr2, why@ syscall
bldo_notify_resume
bret_slow_syscall@ Check work again
由该汇编可知,假如在用户形式下发生中止的话,在回来的时分,会依据需求进行进程调度,而从代码可知,假如中止发生在办理等内核形式下的话是不会进行进程调度的。
Ok,中止的流程大体便是这样的,下面咱们就开端剖析c函数里的中止流程。
先来看asm_do_IRQ
arch/arm/kernel/Irq.c:
/*
* do_IRQ handles all hardware IRQs.Decoded IRQs should not
* come via this function.Instead, they should provide their
* own handler
*/
asmlinkage void asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct irqdesc *desc = irq_desc + irq; /*获取中止描述符*/
/*
* Some hardware gives randomly wrong interrupts.Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)/*参数查看*/
desc = &bad_irq_desc;
irq_enter();
desc_handle_irq(irq, desc, regs);/*中止处理*/
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
}
该函数的调用desc_handle_irq()来持续处理中止。
include/asm-arm/mach/Irq.h:
/*
* Obsolete inline function for calling irq descriptor handlers.
*/
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc,
struct pt_regs *regs)
{
desc->handle_irq(irq, desc, regs);
}
调用中止描述符的handler_irq函数来处理该中止,关于IRQ_WDT便是do_edge_IRQ(前面剖析过)。
include/asm-arm/mach/Irq.h:
#define do_edge_IRQhandle_edge_irq
kernel/irq/Chip.c:
/
*handle_edge_irq – edge type IRQ handler
*@irq:the interrupt number
*@desc:the interrupt description structure for this irq
*@regs:pointer to a register structure
*
*Interrupt occures on the falling and/or rising edge of a hardware
*signal. The occurence is latched into the irq controller hardware
*and must be acked in order to be reenabled. After the ack another
*interrupt can happen on the same source even before the first one
*is handled by the assosiacted event handler. If this happens it
*might be necessary to disable (mask) the interrupt depending on the
*controller hardware. This requires to reenable the interrupt inside
*of the loop which handles the interrupts which have arrived while
*the handler was running. If all pending interrupts are handled, the
*loop is left.
*/
void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc, struct pt_regs *regs)
{
const unsigned int cpu = smp_processor_id();
spin_lock(&desc->lock);
desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
/*
* If were currently running this IRQ, or its disabled,
* we shouldnt process the IRQ. Mark it pending, handle
* the necessary masking and go out
*/
/*
*假如该中止正在处理或许该中止被disable掉了的话,就不处理该中止,并清掉pending
*寄存器里的相应位
*/
if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||
!desc->action)) {
desc->status |= (IRQ_PENDING | IRQ_MASKED);
mask_ack_irq(desc, irq);/*mask该中止,清pending标志位*/
goto out_unlock;
}
kstat_cpu(cpu).irqs[irq]++;/*计算中止数量*/
/* Start handling the irq */
/*开端处理中止,先清掉pending标志位*/
desc->chip->ack(irq);
/* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS;/*标上正在处理的符号*/
do {
struct irqaction *action = desc->action;/*获取该中止的action*/
irqreturn_t action_ret;
if (unlikely(!action)) {
desc->chip->mask(irq)/*假如没有注册action,则mask该中止*/;
goto out_unlock;
}
/*
* When another irq arrived while we were handling
* one, we could have masked the irq.
* Renable it, if it was not disabled in meantime.
*/
/*
*假如曾经被mask掉的话,在这儿把它翻开
*/
if (unlikely((desc->status &
(IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
(IRQ_PENDING | IRQ_MASKED))) {
desc->chip->unmask(irq); /*unmask该中止*/
desc->status &= ~IRQ_MASKED;
}
desc->status &= ~IRQ_PENDING;
spin_unlock(&desc->lock);
action_ret = handle_IRQ_event(irq, regs, action);/*处理中止事情*/
if (!noirqdebug)
note_interrupt(irq, desc, action_ret, regs);
spin_lock(&desc->lock);
/*假如有IRQ_PENDING状况,则阐明又有中止发生过,则持续履行*/
} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
desc->status &= ~IRQ_INPROGRESS;
out_unlock:
spin_unlock(&desc->lock);
}
该函数的大体功用都在函数体内解说出来了,这儿咱们对调用的每个函数在进行剖析。
先看mask_ack_irq
kernel/irq/Chip.c:
static inline void mask_ack_irq(struct irq_desc *desc, int irq)
{
if (desc->chip->mask_ack)/*关于IRQ_WDT, 该函数没界说*/
desc->chip->mask_ack(irq);
else {
desc->chip->mask(irq); /*关于IRQ_WDT,该函数便是s3c_irq_mask*/
desc->chip->ack(irq);/*关于IRQ_WDT,该函数便是s3c_irq_ack*/
}
}
能够看到它调用详细渠道相关的mask函数来处理该中止。
咱们来看s3c_irq_mask
arch/arm/mach-s3c2410/Irq.c:
static void
s3c_irq_mask(unsigned int irqno)
{
unsigned long mask;
irqno -= IRQ_EINT0;
mask = __raw_readl(S3C2410_INTMSK);
mask |= 1UL << irqno;/*mask掉对应的中止号*/
__raw_writel(mask, S3C2410_INTMSK);/*写MASK寄存器*/
}
改函数仅仅是把MASK寄存器中对应的中止mask掉,即不再呼应该中止
arch/arm/mach-s3c2410/Irq.c:
static inline void
s3c_irq_ack(unsigned int irqno)
{
unsigned long bitval = 1UL << (irqno - IRQ_EINT0);
/*铲除pending寄存器的相应位*/
__raw_writel(bitval, S3C2410_SRCPND);
__raw_writel(bitval, S3C2410_INTPND);
}
由上面这两个函数能够看出来mask_ack_irq的作用是先mask掉该中止,并铲除pending位,中止被mask掉后体系就不再呼应了, 而pending位被清掉阐明体系中该中止没有触发。一般在中止处理完后都要清pending位, 要不然体系会以为该中止又被触发了。
handle_edge_irq()里调用的unmask函数,其实便是翻开相应的中止,让体系呼应这个中止,代码就不列出来了。
接下来中止看handle_IRQ_event(),它才是真实的中止处理函数。
kernel/irq/handle.c:
/
* handle_IRQ_event – irq action chain handler
* @irq:the interrupt number
* @regs:pointer to a register structure
* @action:the interrupt action chain for this irq
*
* Handles the action chain of an irq event
*/
irqreturn_t handle_IRQ_event(unsigned int irq, struct pt_regs *regs,
struct irqaction *action)
{
irqreturn_t ret, retval = IRQ_NONE;
unsigned int status = 0;
handle_dynamic_tick(action);
/*下面这个if判别:当履行action操作时是否能够翻开中止*/
if (!(action->flags & IRQF_DISABLED))
local_irq_enable_in_hardirq();/*翻开中止*/
do {
/*
*中止handler,也便是咱们经过request_irq注册的中止函数,关于IRQ_WDT便是
* s3c2410wdt_irq
*/
ret = action->handler(irq, action->dev_id, regs);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next; /*记住吗,假如该中止能够同享的话,它就不为NULL*/
} while (action);
if (status & IRQF_SAMPLE_RANDOM)
add_interrupt_randomness(irq);
local_irq_disable();
return retval;
}
该函数首要便是调用了action的handler函数,也便是咱们用request_irq注册的中止例程。这儿需求留意的是:假如咱们注册中止的时分指明能够同享的话,则必须在咱们的中止例程里判别当时发生的中止是否便是咱们自己的中止,这能够经过传进来的参数来判别(该参数便是咱们注册时供给的)。
OK,到这儿整个中止的流程就大致剖析完了。