linux中断系统那些事之----中断处理过程

linux中断系统那些事之----中断处理过程


以外部中断irq为例来说明,当外部硬件产生中断时,linux的处理过程。首先先说明当外部中断产生时,硬件处理器所做的工作如下:

R14_irq = address of next instruction to be executed + 4/*将寄存器lr_mode设置成返回地址,即为当前pc的值,因为pc是当前执行指令的下两条指令*/

       SPSR_irq = CPSR                /*保存处理器当前状态、中断屏蔽位以及各条件标志位*/

       CPSR[4:0] = 0b10010         /*设置当前程序状态寄存器CPSR中相应的位进入IRQ模式,注意cpsr是所有模式共享的*/

       CPSR[5] = 0                        /*在ARM状态执行*/

                                          /*CPSR[6] 不变*/

       CPSR[7] = 1                       /*禁止正常中断*/

       If high vectors configured then

              PC=0xFFFF0018          /*将程序计数器(PC)值设置成该异常中断的中断向量地址,从而跳转到相应的异常中断处理程序处执行,对于ARMv7向量表普遍中断是0xFFFF0018*/

       else

              PC=0x00000018     /*对于低向量*/

假设在用户空间时,产生了外部硬件中断,则这个时候的指令跳转流程如下:
__vectors_start:---------------〉在中断向量表被拷贝后,该地址就是0xffff0000.
 ARM( swi SYS_ERROR0 )
 THUMB( svc #0 )
 THUMB( nop )
W(b) vector_und + stubs_offset
W(ldr) pc, .LCvswi + stubs_offset
W(b) vector_pabt + stubs_offset
W(b) vector_dabt + stubs_offset
W(b) vector_addrexcptn + stubs_offset
W(b)vector_irq + stubs_offset----------〉当外部中断产生时,pc直接指向这个地址。
W(b) vector_fiq + stubs_offset
.globl __vectors_end
下面的vector_stub irq, IRQ_MODE, 4语句,展开就是 vector_irq, 所以上述语句跳转到如下语句执行:
__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)
.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 irq, IRQ_MODE, 4语句展开如下:
/*
 * Vector stubs.
 *
 * This code is copied to 0xffff0200 so we can use branches in the
 * vectors, rather than ldr's.  Note that this code must not
 * exceed 0x300 bytes.
 *
 * Common stub entry macro:
 *   Enter in IRQ mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
 *
 * SP points to a minimal amount of processor-private memory, the address
 * of which is copied into r0 for the mode specific abort handler.
 */
	.macro	vector_stub, name, mode, correction=0
	.align	5

vector_\name:
	.if \correction
	sub	lr, lr, #\correction  //因为硬件处理器是将当前指令的下两条指令的地址存储在lr寄存器中,所以这里需要减4,让他指向被中断指令的下一条,这样当中断被恢复时,可以继续被中断的指令继续执行。
	.endif			      //需要注意的是,这个时候的lr寄存器,已经是irq模式下的私有寄存器了,在中断产生时,硬件处理器已经自动为他赋了值。

	@
	@ Save r0, lr_ (parent PC) and spsr_
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr//保存r0和lr寄存器,即被中断的下一条指令
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr

	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.//准备从中断模式切换到管理模式,不同的模式,对应各自不同的堆栈。
	@
	mrs	r0, cpsr    
	eor	r0, r0, #(\mode ^ SVC_MODE | PSR_ISETSTATE)
	msr	spsr_cxsf, r0

	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f           //获取被中断前,处理器所处的模式
 THUMB(	adr	r0, 1f			)
 THUMB(	ldr	lr, [r0, lr, lsl #2]	)
	mov	r0, sp			//让r0寄存器指向中断模式下堆栈的基地址
 ARM(	ldr	lr, [pc, lr, lsl #2]	)
	movs	pc, lr			@ branch to handler in SVC mode,同时将中断模式下的spsr_irq(irq私有的)赋值给cpsr(该寄存器所有模式共享)
ENDPROC(vector_\name)

此时中断模式下的私有栈sp的存储情况如下:(注意这个时候的sp是中断模式下的堆栈sp),并且这个时候r0寄存器中,保存有sp的指针值,由于r0已经被保存到堆栈,所以可以放心被使用
linux中断系统那些事之----中断处理过程_第1张图片
根据被中断时,处理器模式的不同,分别跳转到__irq_usr和__irq_svc两个分支。
linux中断系统那些事之----中断处理过程_第2张图片
在这里我们以__irq_usr为例来说明:
__irq_usr:
	usr_entry       //进行中断前的硬件上下文的保存
	kuser_cmpxchg_check
	irq_handler
	get_thread_info tsk//获取被中断的用户进程或内核线程所对应的内核栈所对应的thread info结构。
	mov	why, #0
	b	ret_to_user_from_irq//恢复被中断时的上下文,然后继续被中断的进程或线程的执行
 UNWIND(.fnend		)
ENDPROC(__irq_usr)

usr_entry展开如下:

	.macro	usr_entry
 UNWIND(.fnstart	)
 UNWIND(.cantunwind	)	@ don't unwind the user space
	sub	sp, sp, #S_FRAME_SIZE   // #S_FRAME_SIZE的值为72
 ARM(	stmib	sp, {r1 - r12}	)      //尽管当前是处于管理模式,但由于svc和usr的r0-r12是公共的,所以相当于保存用户模式的r1-r12寄存器
 THUMB(	stmia	sp, {r0 - r12}	)

	ldmia	r0, {r3 - r5}          //将之前保存在中断模式堆栈中的r0_usr,lr,spsr分别存储到r3-r5中
	add	r0, sp, #S_PC		@ here for interlock avoidance #S_PC=60
	mov	r6, #-1			@  ""  ""     ""        ""

	str	r3, [sp]		@ save the "real" r0 copied
					@ from the exception stack

	@
	@ We are now ready to fill in the remaining blanks on the stack:
	@
	@  r4 - lr_, already fixed up for correct return/restart
	@  r5 - spsr_
	@  r6 - orig_r0 (see pt_regs definition in ptrace.h)
	@
	@ Also, separately save sp_usr and lr_usr
	@
	stmia	r0, {r4 - r6}
 ARM(	stmdb	r0, {sp, lr}^			)//保存用户模式下的sp_usr,lr_usr
 THUMB(	store_user_sp_lr r0, r1, S_SP - S_PC	)

	@
	@ Enable the alignment trap while in kernel mode
	@
	alignment_trap r0

	@
	@ Clear FP to mark the first stack frame
	@
	zero_fp

#ifdef CONFIG_IRQSOFF_TRACER
	bl	trace_hardirqs_off
#endif
	.endm

至此,用户模式下所有的寄存器都被正确保存了,并且处理器模式中irq模式成功切换到管理模式,并且sp这个时候是指向保存r0_usr寄存器值得地方。此时的管理模式的内核栈分布如下:

linux中断系统那些事之----中断处理过程_第3张图片
需要说明的是:上图中的lr_irq即为用户模式下被中断指令的下一条指令,spsr_irq即为用户模式下被中断时的cpsr寄存器。
在这里说明下,中断时寄存器的保存是有固定的顺序的,他们顺序即如下所示:
cpsr(r16)
pc(r15)
lr(r14)
sp(r13)
r12(ip)
r11(fp)
r10
r9
r8
r7
r6
r5
r4
r3
r2
r1
r0

上图中的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_pc     uregs[15]

呵呵,pt_regs中对应的就是上面栈上的18个寄存器,ARM_pc是pc寄存器存放在这个数组中的偏移。



接着看get_thread_info, 它也是个宏,用来获取当前线程的地址。他的结构体定义如下:

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:

    .macro  get_thread_info, rd

    mov /rd, sp, lsr #13

    mov /rd, /rd, lsl #13

    .endm

调用该宏后寄存器tsk里存放的就是当前线程的地址了, tsk是哪个寄存器呢,呵呵我们在看:

arch/arm/kernel/entry-header.S:

tsk .req    r9      @ current thread_info

呵呵,tsk只是r9的别名而已, 因此这时r9里保存的就是当前线程的地址。



为了将汇编部分讲完,我们继续研究ret_to_user_from_irq函数,该函数展开后,如下:
ENTRY(ret_to_user_from_irq)
	ldr	r1, [tsk, #TI_FLAGS] //tsk如上所述,是r9寄存器的别名,并且是指向thread_info结构体的
	tst	r1, #_TIF_WORK_MASK  //检测是否有待处理的任务
	bne	work_pending
no_work_pending:
#if defined(CONFIG_IRQSOFF_TRACER)
	asm_trace_hardirqs_on
#endif
	/* perform architecture specific actions before user return */
	arch_ret_to_user r1, lr    //针对arm,是dummy的

	restore_user_regs fast = 0, offset = 0//恢复之前用户模式时被中断时所保存的寄存器上下文
ENDPROC(ret_to_user_from_irq)

restore_user_regs展开如下: 
	.macro	restore_user_regs, fast = 0, offset = 0
	ldr	r1, [sp, #\offset + S_PSR]	@ get calling cpsr 即为被中断时,处理器的cpsr值
	ldr	lr, [sp, #\offset + S_PC]!	@ get pc         即为被中断指令的,下一条指令
	msr	spsr_cxsf, r1			@ save in spsr_svc  //将r1赋值给管理模式下的spsr_svc,这样在movs时,会自动将该值赋值为cpsr
#if defined(CONFIG_CPU_V6)
	strex	r1, r2, [sp]			@ clear the exclusive monitor
#elif defined(CONFIG_CPU_32v6K)
	clrex					@ clear the exclusive monitor
#endif
	.if	\fast
	ldmdb	sp, {r1 - lr}^			@ get calling r1 - lr
	.else
	ldmdb	sp, {r0 - lr}^			@ get calling r0 - lr,将保存在内核栈中的r0到r14恢复到用户模式中的寄存器
	.endif
	mov	r0, r0				@ ARMv5T and earlier require a nop
						@ after ldm {}^
	add	sp, sp, #S_FRAME_SIZE - S_PC    //恢复内核栈到中断产生之前的位置。
	movs	pc, lr				@ return & move spsr_svc into cpsr
	.endm


至此中断汇编部分已经全部处理完成。

最后摘录部门网上经典的问题解答:

问题1:vector_irq已经是异常、中断处理的入口函数了,为什么还要加stubs_offset?(  b    vector_irq + stubs_offset)

答:(1)内核刚启动时(head.S文件)通过设置CP15的c1寄存器已经确定了异常向量表的起始地址(例如0xffff0000),因此需要把已经写好的内核代码中的异常向量表考到0xffff0000处,只有这样在发生异常时内核才能正确的处理异常。

(2)从上面代码看出向量表和stubs(中断处理函数)都发生了搬移,如果还用b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成搬移后的。至于为什么搬移后的地址是vector_irq+stubs_offset,请参考我的上篇blog:linux中断系统那些事之----中断初始化过程

问题2:为什么在异常向量表中,用b指令跳转而不是用ldr绝对跳转?

答:因为使用b指令跳转比绝对跳转(ldr pc,XXXX)效率高,正因为效率高,所以把__stubs_start~__stubs_end之间的代码考到了0xffff0200起始处。

注意:

因为b跳转指令只能在+/-32MB之内跳转,所以必须拷贝到0xffff0000附近。

b指令是相对于当前PC的跳转,当汇编器看到 B 指令后会把要跳转的标签转化为相对于当前PC的偏移量写入指令码。

问题3:为什么首先进入head.S开始执行?

答:内核源代码顶层目录下的Makefile制定了vmlinux生成规则:

# vmlinux image - includingupdated kernel symbols

vmlinux: $(vmlinux-lds)$(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o)FORCE

其中$(vmlinux-lds)是编译连接脚本,对于ARM平台,就是arch/arm/kernel/vmlinux-lds文件。vmlinux-init也在顶层Makefile中定义:

vmlinux-init := $(head-y)$(init-y)

head-y 在arch/arm/Makefile中定义:

head-y:=arch/arm/kernel/head$(MMUEX T).o arch/arm/kernel/init_task.o

ifeq ($(CONFIG_MMU),)

MMUEXT := -nommu

endif

对于有MMU的处理器,MMUEXT为空白字符串,所以arch/arm/kernel/head.O 是第一个连接的文件,而这个文件是由arch/arm/kernel/head.S编译产生成的。

综合以上分析,可以得出结论,非压缩ARM Linux内核的入口点在arch/arm/kernel/head.s中。

问题4: 中断为什么必须进入svc模式?

一个最重要原因是:

如果一个中断模式(例如从usr进入irq模式,在irq模式中)中重新允许了中断,并且在这个中断例程中使用了BL指令调用子程序,BL指令会自动将子程序返回地址保存到当前模式的sp(即r14_irq)中,这个地址随后会被在当前模式下产生的中断所破坏,因为产生中断时CPU会将当前模式的PC保存到r14_irq,这样就把刚刚保存的子程序返回地址冲掉。为了避免这种情况,中断例程应该切换到SVC或者系统模式,这样的话,BL指令可以使用r14_svc来保存子程序的返回地址。

问题5:为什么跳转表中有的用了b指令跳转,而有的用了ldr  px,xxxx?

         W(b)         vector_und+ stubs_offset

         W(ldr)      pc, .LCvswi + stubs_offset

         W(b)         vector_pabt+ stubs_offset

         W(b)         vector_dabt+ stubs_offset

         W(b)         vector_addrexcptn+ stubs_offset

         W(b)         vector_irq+ stubs_offset      

         W(b)         vector_fiq+ stubs_offset

 

.LCvswi:

         .word       vector_swi

由于系统调用异常的代码编译在其他文件中,其入口地址与异常向量相隔较远,使用b指令无法跳转过去(b指令只能相对当前pc跳转32M范围)。因此将其地址存放到LCvswi中,并从内存地址中加载其入口地址,原理与其他调用是一样的。这也就是为什么系统调用的速度稍微慢一点的原因。

问题6:为什么ARM能处理中断?

因为ARM架构的CPU有一个机制,只要中断产生了,CPU就会根据中断类型自动跳转到某个特定的地址(即中断向量表中的某个地址)。如下表所示,既是中断向量表。



 ARM中断向量表及地址

问题7:什么是High vector?

A:在Linux3.1.0,arch/arm/include/asm/system.hline121 有定义如下:

#if __LINUX_ARM_ARCH__ >=4

#define vectors_high()  (cr_alignment & CR_V)

#else

#define vectors_high()  (0)

#endif

意思就是,如果使用的ARM架构大于等于4,则定义vectors_high()=cr_alignment&CR_V,该值就等于0xffff0000

在Linux3.1.0,arch/arm/include/asm/system.hline33有定义如下:

#define CR_V   (1 << 13)       /* Vectors relocated to 0xffff0000 */

 

arm下规定,在0x00000000或0xffff0000的地址处必须存放一张跳转表。

问题8:中断向量表是如何存放到0x00000000或0xffff0000地址的?

A:Uboot执行结束后会把Linux内核拷贝到内存中开始执行,linux内核执行的第一条指令是linux/arch/arm/kernel/head.S,此文件中执行一些参数设置等操作后跳入linux/init/main.c文件的start_kernel函数,此函数调用一系列初始化函数,其中trip_init()函数实现向量表的设定操作。

 



你可能感兴趣的:(linux中断系统,linux,interruption,handler)