您的位置 首页 电源

ARM Linux中止机制之中止处理

//现在来看看中断初始化的另一个函数early_trap_init(),该函数在文件arch/arm/kernel/traps.c中实现。void__initearly_trap_init(vo…

//现在来看看中止初始化的另一个函数early_trap_init(),该函数在文件arch/arm/kernel/traps.c中完成。

void __init early_trap_init(void)
{

//CONFIG_VECTORS_BASE在autoconf.h中界说(该文件主动成生),值为0xffff0000,
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;

/* 反常向量表复制到 0x0000_0000(或 0xFFFF_0000) ,
反常处理程序的 stub 复制到 0x0000_0200(或 0xFFFF_0200) */
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));

/* 改写 Cache,批改反常向量表占有的页面的拜访权限*/

flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}

这个函数把界说在 arch/arm/kernel/entry-armv.S 中的反常向量表和反常处理程序的 stub 进行
重定位:反常向量表复制到 0xFFFF_0000,反常向量处理程序的 stub 复制到 0xFFFF_0200。
然后调用 modify_domain()批改了反常向量表所占有的页面的拜访权限,这使得用户态无法
拜访该页,只要核心态才能够拜访。

arm处理器产生反常时总会跳转到 0xFFFF_0000(设为“高端向量装备”时)处的反常向量
表,因而进行这个重定位作业。

反常向量表,在文件arch/arm/kernel/entry-armv.S 中

.equstubs_offset, __vectors_start + 0x200 – __stubs_start

.globl__vectors_start
__vectors_start:
swiSYS_ERROR0
bvector_und + stubs_offset//复位反常:
ldrpc, .LCvswi + stubs_offset//未界说指令反常:
bvector_pabt + stubs_offset//软件中止反常:
bvector_dabt + stubs_offset//数据反常:
bvector_addrexcptn + stubs_offset//保存:
bvector_irq + stubs_offset//一般中止反常:
bvector_fiq + stubs_offset//快速中止反常:

.globl__vectors_end
__vectors_end:

ARM 处理器产生反常(中止是一种反常)时,会跳转到反常向量表,在向量表中找到相应的反常,并跳转到

该反常处理程序处履行。

stubs_offset,界说为__vectors_start + 0x200 – __stubs_start。

在中止初始化函数early_trap_init()中向量表被拷到0xFFFF_0000处,反常处理程序段被拷到0xFFFF_0200处。

比方此刻产生中止反常bvector_irq + stubs_offset 将跳转到中止反常处理程序段去履行,由于vector_irq,

在反常处理程序段__stubs_start到__stubs_end之间此刻跳转的方位将是__vectors_start + 0x200 + vector_irq – __stubs_start处。

反常处理程序段如下:

当 ARM 处理器产生反常(中止是一种反常)时,会跳转到反常向量表,在向量表中找到相应的反常,并跳转到

该反常处理程序处履行,这些反常处理程序便是放在以下反常处理程序段中。

.globl__stubs_start
__stubs_start:

//vector_stub是一个宏,它代表有一段程序放在此处。irq, IRQ_MODE, 4是传递给宏vector_stub的参数。
vector_stubirq, IRQ_MODE, 4

//以下是跳转表,在宏vector_stub代表的程序段中要用到该表来查找程序要跳转的方位。

//假如在进入终中止时是用户方式,则调用__irq_usr例程,假如为体系方式,则调用__irq_svc,假如是其他方式,则阐明出错了,

//则调用__irq_invalid。

.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_stubdabt, ABT_MODE, 8

.。。。。。。

vector_stubpabt, ABT_MODE, 4

。。。。。。

vector_stubund, UND_MODE

。。。。。。

vector_fiq:
disable_fiq
subspc, lr, #4

vector_addrexcptn:
bvector_addrexcptn
.align5

.LCvswi:
.wordvector_swi

.globl__stubs_end
__stubs_end:

宏vector_stub代表的程序段如下:name, mode, correction存储传入的参数之

.macrovector_stub, name, mode, correction=0
.align5

vector_\name:
.if \correction
sublr, lr, #\correction//批改回来地址,也便是中止处理完之后要履行的指令的地址
.endif

@
@ Save r0, lr_ (parent PC) and spsr_
@ (parent CPSR)
@

///保存回来地址到仓库,由于很快要运用r0寄存器,所以也要保存r0。sp后没有!所以sp指向的方位并没有改动。

stmiasp, {r0, lr}@ save r0, lr

mrslr, spsr
strlr, [sp, #8]@ save spsr

// 向上增加的栈。

// 此刻的这个栈是中止方式下的栈,ARM下中止方式下和体系方式下的

// 栈是不同的。尽管ARM供给了七个方式,但Linux只运用了两个,一

// 个是用户方式,另一个为体系方式,所以这个栈仅仅一个临时性的栈。

/*

在arch/arm/include/asm/ptrace.h中有处理器的七种作业方式的界说

#define USR_MODE0x00000010
#define FIQ_MODE0x00000011
#define IRQ_MODE0x00000012
#define SVC_MODE0x00000013
#define ABT_MODE0x00000017
#define UND_MODE0x0000001b
#define SYSTEM_MODE0x0000001f

*/
mrsr0, cpsr
eorr0, r0, #(\mode ^ SVC_MODE)
msrspsr_cxsf, r0////把spsr设置为管理方式。//对spsr的一切控制为进行写操作,将r0的值悉数注入spsr

@
@ the branch table must immediately follow this code
@
//andlr, lr, #0x0f// 这条指令之后lr中位spsr的低4位,上面跳转表有16项便是对应这16个状况
//movr0, sp//用r0保存仓库指针的地址

//在对这段程序剖析时要记住这段程序是以宏vector_stub的方式放在跳转表前面的。

//将跳转表中对应的地址条目存入lr。由于跳转表中每一个条目都是4个字节long,所以此处左移两位
ldrlr, [pc, lr, lsl #2]

movspc, lr@ branch to handler in SVC mode//程序跳转。
ENDPROC(vector_\name)
.endm

在此咱们以在用户空间产生中止反常为例,即程序跳转到__irq_usr处。

.align5
__irq_usr:
usr_entry//usr_entry是一个宏代表一段程序刺进此处,宏usr_entry所代表的程序段将在下面剖析(1)

kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS
bltrace_hardirqs_off
#endif

//接着看get_thread_info, 它也是个宏,用来获取当时线程的地址。也将在后续剖析。tsk寄存的是线程结构体的地址。

/*

线程结构体原型如下在文件include/linux/sched.h中

struct thread_info {
struct task_struct*task;/* main task structure */
unsigned longflags;
struct exec_domain*exec_domain;/* execution domain */
intpreempt_count;/* 0 => preemptable, <0 => BUG */
__u32 cpu; /* should always be 0 on m68k */
struct restart_block restart_block;
};

*/
get_thread_info tsk(2)
#ifdef CONFIG_PREEMPT

//TI_PREEMPT在文件arch\arm\kernel\asm-offsets.c中界说是线程结构体thread_info 的成员preempt_count在

//结构体thread_info中的偏移

/*

内核态可掠夺内核,只要在 preempt_count 为 0 时, schedule() 才会被调用,其查看
是否需求进行进程切换,需求的话就切换。

*/
ldrr8, [tsk, #TI_PREEMPT]//获取preempt_count
addr7, r8, #1@ increment it//将该成员加一
strr7, [tsk, #TI_PREEMPT]//间改动后的值存入preempt_count
#endif

irq_handler//调用中止操作函数,irq_handler是一个宏,在后续描绘(3)
#ifdef CONFIG_PREEMPT
ldrr0, [tsk, #TI_PREEMPT]
strr8, [tsk, #TI_PREEMPT]
teqr0, r7
strner0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
bltrace_hardirqs_on
#endif

movwhy, #0//why在文件arch/arm/kernel/entry-header.S中界说为r8。:why.reqr8
bret_to_user//回来到用户态,该宏在文件 linux/arch/arm/kernel/entry-common.S中界说。(4)
UNWIND(.fnend)
ENDPROC(__irq_usr)

下面别离对上面四处宏进行剖析。(usr_entry,get_thread_info tsk,irq_handler,ret_to_user)

(1)

.macrousr_entry
UNWIND(.fnstart)
UNWIND(.cantunwind)@ dont unwind the user space

//S_FRAME_SIZE在文件arch\arm\kernel\asm-offsets.c中界说表明 寄存器结构体pt_regs的巨细结构体

//pt_regs中有 r0~cpsr 18个寄存器即72个字节。
subsp, sp, #S_FRAME_SIZE//为寄存器pt_regs结构体树立仓库空间,让仓库指针sp 指向r0 。

//stmib为存储前加,所以此处留出了用于存储r0的空间,将r1 – r12存入仓库。sp后没加!

//所以sp指向的仓库方位没有变,一向指向用于存储r0的存储空间。

stmibsp, {r1 – r12}

//将中止前r0,lr,spsr的值取出寄存在r1 – r3中,此刻的r0是作为仓库的sp在运用的。

//它的值是指向中止前r0的值在仓库中寄存的方位。在寄存器结构体pt_regs在仓库中的方位上面。

ldmiar0, {r1 – r3}

//S_PC便是pt_regs中的PC寄存器方位,让r0指向该方位。尽管S_PC还没有存入仓库但它在仓库中的方位存在
addr0, sp, #S_PC

movr4, #-1//在r4中放入一个无效值。

strr1, [sp]//r1中寄存的是中止前r0的值,此刻将该值存入仓库,上面已解说过在仓库中流出r0的方位的问题。

//此刻r2-r4寄存的是中止前的lr, spsr的值和无效之。

//此刻将这些值存入pt_regs中寄存器在仓库中对应的方位,即此刻将中止前的lr, spsr的值和无效之

//存入寄存器结构体pt_regs的ARM_pc,ARM_cpsr,ARM_ORIG_r0中。
stmiar0, {r2 – r4}
stmdbr0, {sp, lr}^//stmdb是递减取值,将ARM_lr,ARM_sp存入lr,sp中。

alignment_trap r0

//宏zero_fp在文件arch/arm/kernel/entry-header.S中界说,清零fp。
zero_fp
.endm

上面的说到的struct pt_regs,在include/asm/ptrace.h中界说

struct pt_regs {
long uregs[18];
};

#define ARM_cpsruregs[16]
#define ARM_pcuregs[15]
#define ARM_lruregs[14]
#define ARM_spuregs[13]
#define ARM_ipuregs[12]
#define ARM_fpuregs[11]
#define ARM_r10uregs[10]
#define ARM_r9uregs[9]
#define ARM_r8uregs[8]
#define ARM_r7uregs[7]
#define ARM_r6uregs[6]
#define ARM_r5uregs[5]
#define ARM_r4uregs[4]
#define ARM_r3uregs[3]
#define ARM_r2uregs[2]
#define ARM_r1uregs[1]
#define ARM_r0uregs[0]
#define ARM_ORIG_r0uregs[17]

(2)

//宏macroget_thread_info在文件arch/arm/kernel/entry-header.S中界说。用来获取当时线程的地址。

/*

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目标的地址。

THREAD_SIZE在文件arch/arm/include/asm/thread_info.h中界说:#define THREAD_SIZE8192

*/

.macroget_thread_info, rd
mov\rd, sp, lsr #13
mov\rd, \rd, lsl #13
.endm

(3)

//宏irq_handler文件arch/arm/kernel/entry-armv.S中界说:

.macroirq_handler

//宏get_irqnr_preamble是一个空操作,在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S中界说
get_irqnr_preamble r5, lr

//宏get_irqnr_and_base经过读取寄存器INTPND来取得中止号。在该宏中获取的一些参量将存于这些寄存器中r0, r6, r5, lr。

//宏get_irqnr_and_base界说在文件 arch/arm/mach-s3c2410/include/mach/entry-macro.S,这个宏后续讲到。
1:get_irqnr_and_base r0, r6, r5, lr
movner1, sp
@
@ routine called with r0 = irq number, r1 = struct pt_regs *
@
adrnelr, 1b

/*

// 经过上面的宏get_irqnr_and_base为调用asm_do_IRQ预备了参数中止号。

所以调用asm_do_IRQ来处理中止。函数asm_do_IRQ()是中止处理函数的C言语进口。此函数将在后续评论。

函数asm_do_IRQ()在文件linux/arch/arm/kernel/irq.c中完成。

*/
bneasm_do_IRQ

#ifdef CONFIG_SMP
。。。。。。

#endif

.endm

get_irqnr_and_base是渠道相关的,这个宏查询ISPR(IRQ挂起中止服务寄存器,当有需求处理的中止时,这个寄存器的相应位会置位,恣意时间,最多一个位会置位),计算出的中止号放在irqnr指定的寄存器中;这个宏在不同的ARM芯片上是不一样的,这个宏首要效果在于便是取得产生中止的中止号,关于s3c2440,代码在arch/arm/mach-s3c2410/include/entry-macro.S里,该宏处理完后,r0 = 中止号。

.macroget_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
beq1002f
ldr\irqnr, [ \base, #INTOFFSET ]
mov\tmp, #1
tst\irqstat, \tmp, lsl \irqnr
bne1001f

@@ the number specified is not a valid irq, so try
@@ and work it out for ourselves

mov\irqnr, #0@@ start here

@@ work out which irq (if any) we got

movs\tmp, \irqstat, lsl#16
addeq\irqnr, \irqnr, #16
moveq\irqstat, \irqstat, lsr#16
tst\irqstat, #0xff
addeq\irqnr, \irqnr, #8
moveq\irqstat, \irqstat, lsr#8
tst\irqstat, #0xf
addeq\irqnr, \irqnr, #4
moveq\irqstat, \irqstat, lsr#4
tst\irqstat, #0x3
addeq\irqnr, \irqnr, #2
moveq\irqstat, \irqstat, lsr#2
tst\irqstat, #0x1
addeq\irqnr, \irqnr, #1

@@ we have the value
1001:
adds\irqnr, \irqnr, #IRQ_EINT0
1002:
@@ exit here, Z flag unset if IRQ

.endm

(4)

宏ret_to_user在文件arch/arm/kernel/entry-common.S下界说:

ENTRY(ret_to_user)
ret_slow_syscall:
disable_irq//制止中止
ldrr1, [tsk, #TI_FLAGS]//获取线程结构体thread_union的flags成员
tstr1, #_TIF_WORK_MASK//判别task是否被堵塞
bnework_pending //依据需求进行进程的切换,该段代码在下面叙述。
no_work_pending://不需求进程切换
/* perform architecture specific actions before user return */
arch_ret_to_user r1, lr

@ slow_restore_user_regs
ldrr1, [sp, #S_PSR]@ get calling cpsr
ldrlr, [sp, #S_PC]!@ get pc
msrspsr_cxsf, r1@ save in spsr_svc //// spsr里保存好被中止代码处的状况(cpsp)
ldmdbsp, {r0 – lr}^//康复中止前寄存器的值康复到各个寄存器。
movr0, r0
addsp, sp, #S_FRAME_SIZE – S_PC
movspc, lr//回来用户态
ENDPROC(ret_to_user)

在arch/arm/kernel/entry-common.S中

work_pending:
tstr1, #_TIF_NEED_RESCHED//判别是否需求调度进程
bnework_resched//进程调度
tstr1, #_TIF_SIGPENDING
beqno_work_pending//无需调度,回来
movr0, sp@ regs
movr2, why@ syscall
bldo_notify_resume
bret_slow_syscall@ Check work again

work_resched:
blschedule//调用进程切换函数。

这儿只讲了在用户方式下的中止处理,在内核方式下的处理方式也大略相仿,就不再赘言了。

中止处理函数的C言语进口

asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);

irq_enter();//进入中止上下文

if (irq >= NR_IRQS)
handle_bad_irq(irq, &bad_irq_desc);
else
generic_handle_irq(irq);//依据中止号获取中止描绘结构体,并调用其中止处理函数。

irq_finish(irq);//退出中止上下文

irq_exit();
set_irq_regs(old_regs);
}

//函数generic_handle_irq()是函数generic_handle_irq_desc()的包装。

static inline void generic_handle_irq(unsigned int irq)
{
generic_handle_irq_desc(irq, irq_to_desc(irq));
}

/*

假如完成了上层中止处理函数desc->handle_irq就调用它,实际上在中止处理函数s3c24xx_init_irq()中已为每一个

中止线分配了一个上层中止处理函数。

假如desc->handle_irq为空就调用通用中止处理函数__do_IRQ(irq);,在干函数中调用了函数handle_IRQ_event(),

在函数handle_IRQ_event()中履行了该条中止线上的每一个中止例程。

*/

static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
desc->handle_irq(irq, desc);
#else
if (likely(desc->handle_irq))
desc->handle_irq(irq, desc);
else
__do_IRQ(irq);
#endif
}

声明:本文内容来自网络转载或用户投稿,文章版权归原作者和原出处所有。文中观点,不代表本站立场。若有侵权请联系本站删除(kf@86ic.com)https://www.86ic.net/dianyuan/277161.html

为您推荐

联系我们

联系我们

在线咨询: QQ交谈

邮箱: kf@86ic.com

关注微信
微信扫一扫关注我们

微信扫一扫关注我们

返回顶部