arm linux 中断机制,ARM Linux中断机制之中断处理

//现在来看看中断初始化的另一个函数early_trap_init(),该函数在文件arch/arm/kernel/traps.c中实现。 void __init early_trap_init(void) { //CONFIG_VECTORS_BASE在autoconf.h中定义(该文件自动成生),值为0xffff0000, unsigned long vectors = CONFIG_VE

//现在来看看中断初始化的另一个函数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 中

.equ stubs_offset, __vectors_start + 0x200 - __stubs_start

.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:

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

该异常处理程序处执行。

stubs_offset,定义为__vectors_start + 0x200 - __stubs_start。

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

比如此时发生中断异常b vector_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_stub irq, 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_stub dabt, ABT_MODE, 8

.。。。。。。

vector_stub pabt, ABT_MODE, 4

。。。。。。

vector_stub und, UND_MODE

。。。。。。

vector_fiq:

disable_fiq

subs pc, lr, #4

vector_addrexcptn:

b vector_addrexcptn

.align 5

.LCvswi:

.word vector_swi

.globl __stubs_end

__stubs_end:

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

.macro vector_stub, name, mode, correction=0

.align 5

vector_\name:

.if \correction

sub lr, lr, #\correction //修正返回地址,也就是中断处理完之后要执行的指令的地址

.endif

@

@ Save r0, lr_ (parent PC) and spsr_

@ (parent CPSR)

@

///保存返回地址到堆栈,因为很快要使用r0寄存器,所以也要保存r0。sp后没有!所以sp指向的位置并没有变化。

stmia sp, {r0, lr}  @ save r0, lr

mrs lr, spsr

str lr, [sp, #8]  @ save spsr

// 向上增长的栈。 div.section1 { page: section1; }

// 此时的这个栈是中断模式下的栈,ARM下中断模式下和系统模式下的

// 栈是不同的。虽然ARM提供了七个模式,但Linux只使用了两个,一

// 个是用户模式,另一个为系统模式,所以这个栈只是一个临时性的栈。

/*

在arch/arm/include/asm/ptrace.h中有处理器的七种工作模式的定义

#define USR_MODE 0x00000010

#define FIQ_MODE 0x00000011

#define IRQ_MODE 0x00000012

#define SVC_MODE 0x00000013

#define ABT_MODE 0x00000017

#define UND_MODE 0x0000001b

#define SYSTEM_MODE 0x0000001f

*/

mrs r0, cpsr

eor r0, r0, #(\mode ^ SVC_MODE)

msr spsr_cxsf, r0 把spsr设置为管理模式。//对spsr的所有控制为进行写操作,将r0的值全部注入spsr

@

@ the branch table must immediately follow this code

@

//and lr, lr, #0x0f //  这条指令之后lr中位spsr的低4位,上面跳转表有16项就是对应这16个状态

//mov r0, sp //用r0保存堆栈指针的地址

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

//将跳转表中对应的地址条目存入lr。因为跳转表中每一个条目都是4个字节long,所以此处左移两位

ldr lr, [pc, lr, lsl #2]

movs pc, lr   @ branch to handler in SVC mode//程序跳转。

ENDPROC(vector_\name)

.endm

在此我们以在用户空间发生中断异常为例,即程序跳转到__irq_usr处。

.align 5

__irq_usr:

usr_entry   //usr_entry是一个宏代表一段程序插入此处,宏usr_entry所代表的程序段将在下面分析        (1)

kuser_cmpxchg_check

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_off

#endif

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

/*

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

struct thread_info {

struct task_struct *task;  /* main task structure */

unsigned long  flags;

struct exec_domain *exec_domain; /* execution domain */

int   preempt_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() 才会被调用,其检查

是否需要进行进程切换,需要的话就切换。

*/

ldr r8, [tsk, #TI_PREEMPT]  //获取preempt_count

add r7, r8, #1   @ increment it //将该成员加一

str r7, [tsk, #TI_PREEMPT] //间改变后的值存入preempt_count

#endif

irq_handler  //调用中断操作函数,irq_handler是一个宏,在后续描述                               (3)

#ifdef CONFIG_PREEMPT

ldr r0, [tsk, #TI_PREEMPT]

str r8, [tsk, #TI_PREEMPT]

teq r0, r7

strne r0, [r0, -r0]

#endif

#ifdef CONFIG_TRACE_IRQFLAGS

bl trace_hardirqs_on

#endif

mov why, #0  //why在文件arch/arm/kernel/entry-header.S中定义为r8。:why .req r8

b ret_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)

.macro usr_entry

UNWIND(.fnstart )

UNWIND(.cantunwind ) @ don't unwind the user space

//S_FRAME_SIZE在文件arch\arm\kernel\asm-offsets.c中定义表示 寄存器结构体pt_regs的大小结构体

//pt_regs中有 r0~cpsr 18个寄存器即72个字节。

sub sp, sp, #S_FRAME_SIZE  //为寄存器pt_regs结构体建立堆栈空间,让堆栈指针sp 指向r0 。

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

//所以sp指向的堆栈位置没有变,一直指向用于存储r0的存储空间。

stmib sp, {r1 - r12}

//将中断前r0,lr,spsr的值取出存放在r1 - r3中,此时的r0是作为堆栈的sp在使用的。

//它的值是指向中断前r0的值在堆栈中存放的位置。在寄存器结构体pt_regs在堆栈中的位置上面。

ldmia r0, {r1 - r3}

//S_PC即是pt_regs中的PC寄存器位置,让r0指向该位置。虽然S_PC还没有存入堆栈但它在堆栈中的位置存在

add r0, sp, #S_PC

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

str r1, [sp]  //r1中存放的是中断前r0的值,此时将该值存入堆栈,上面已解释过在堆栈中流出r0的位置的问题。

//此时r2-r4存放的是中断前的lr, spsr的值和无效之。

//此时将这些值存入pt_regs中寄存器在堆栈中对应的位置,即此时将中断前的lr, spsr的值和无效之

//存入寄存器结构体pt_regs的ARM_pc ,ARM_cpsr,ARM_ORIG_r0中。

stmia r0, {r2 - r4}

stmdb r0, {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_cpsr uregs[16]

#define ARM_pc  uregs[15]

#define ARM_lr  uregs[14]

#define ARM_sp  uregs[13]

#define ARM_ip  uregs[12]

#define ARM_fp  uregs[11]

#define ARM_r10  uregs[10]

#define ARM_r9  uregs[9]

#define ARM_r8  uregs[8]

#define ARM_r7  uregs[7]

#define ARM_r6  uregs[6]

#define ARM_r5  uregs[5]

#define ARM_r4  uregs[4]

#define ARM_r3  uregs[3]

#define ARM_r2  uregs[2]

#define ARM_r1  uregs[1]

#define ARM_r0  uregs[0]

#define ARM_ORIG_r0 uregs[17]

(2)

//宏macro get_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_SIZE  8192

*/

.macro get_thread_info, rd

mov \rd, sp, lsr #13

mov \rd, \rd, lsl #13

.endm

(3)

//宏irq_handler文件arch/arm/kernel/entry-armv.S中定义:

.macro irq_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

movne r1, sp

@

@ routine called with r0 = irq number, r1 = struct pt_regs *

@

adrne lr, 1b

/*

// 通过上面的宏get_irqnr_and_base为调用asm_do_IRQ准备了参数中断号。

于是调用asm_do_IRQ来处理中断。函数asm_do_IRQ()是中断处理函数的C语言入口。此函数将在后续讨论。

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

*/

bne asm_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 = 中断号。

.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 ]

mov \tmp, #1

tst \irqstat, \tmp, lsl \irqnr

bne 1001f

@@ 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    //禁止中断

ldr r1, [tsk, #TI_FLAGS]  //获取线程结构体thread_union的flags成员

tst r1, #_TIF_WORK_MASK  //判断task是否被阻塞

bne work_pending     //根据需要进行进程的切换,该段代码在下面讲述。

no_work_pending:   //不需要进程切换

/* perform architecture specific actions before user return */

arch_ret_to_user r1, lr

@ slow_restore_user_regs

ldr r1, [sp, #S_PSR]  @ get calling cpsr

ldr lr, [sp, #S_PC]!  @ get pc

msr spsr_cxsf, r1   @ save in spsr_svc spsr里保存好被中断代码处的状态(cpsp)

ldmdb sp, {r0 - lr}^   //恢复中断前寄存器的值恢复到各个寄存器。

mov r0, r0

add sp, sp, #S_FRAME_SIZE - S_PC

movs pc, lr    //返回用户态

ENDPROC(ret_to_user)

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

work_pending:

tst r1, #_TIF_NEED_RESCHED //判断是否需要调度进程

bne work_resched   // 进程调度

tst r1, #_TIF_SIGPENDING

beq no_work_pending //无需调度,返回

mov r0, sp    @ 'regs'

mov r2, why    @ 'syscall'

bl do_notify_resume

b ret_slow_syscall  @ Check work again

work_resched:

bl schedule  //调用进程切换函数。

这里只讲了在用户模式下的中断处理,在内核模式下的处理方式也大抵相仿,就不再赘言了。

中断处理函数的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

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值