热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《LINUX3.0内核源代码分析》第二章:中断和异常【转】

转自:http:blog.chinaunix.netuid-25845340-id-2982887.html摘要:第二章主要讲述linux如何处理ARM

转自:http://blog.chinaunix.net/uid-25845340-id-2982887.html

摘要:第二章主要讲述linux如何处理ARM cortex A9多核处理器的中断、异常。介绍了中断向量表的入口、通用的中断处理代码、中断和软中断、延迟处理、中断异常的返回过程。

第二章内容较多,会分几个部分讲述。本部分主要讲进入、退出中断的过程,这部分代码涉及的都是汇编部分。

 

法律声明:《LINUX3.0内核源代码分析》系列文章由谢宝友(scxby@163.com)发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。

 

本连载文章并不是为了形成一本适合出版的书籍,而是为了向有一定内核基本的读者提供一些linux3.0源码分析。因此,请读者结合《深入理解LINUX内核》第三版阅读本连载。

 

由于我的主要工作不是BSP,对CPU体系结构不算太熟悉。如果非要说熟悉哪种CPU的话,应该是对MIPS熟悉一点。ARM方面纯粹是临阵磨枪,为了写本系列文章,前两个月临时看了一下相关书籍。如有不清楚或者错误的地方,敬请大家指出,先谢过了^-^。

 

请读者先看看《ARM嵌入式开发》第9章,对ARM的6种异常有所了解。并明白在进入中断和异常时,硬件都完成了哪些事情。

1.1.1      中断向量和简单中断处理

ARM中断向量表在entry-armv.S中,如下:

__vectors_start:

 ARM(          swi   SYS_ERROR0    )/* reset异常 */

 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/* 中断 */

    W(b)          vector_fiq + stubs_offset/* 快速中断 */

 

    .globl        __vectors_end

__vectors_end:

 

不象MIPS,ARM中断向量表中每一个中断向量只能存储一条指令,因此必须使用一条跳转指令,跳转到各自的处理程序。当然,为了提高快速中断的处理速度,可以将它的处理代码直接跟随在中断向量表后面。但是linux没有这样实现。

在这8个向量中, vector_addrexcptn和vector_fiq比较简单:

vector_fiq:

    disable_fiq        /* 简单的禁止fiq,这样,中断处理退回后,不会再次产生fiq中断了。也就是说,FIQ中断只可能产生一次。 */

    subs pc, lr, #4           /* lr指向当前异常地址+8的地方,这里将其减去4,即是退出异常时,要返回的地址。这里直接返回。 */

 

/*=============================================================================

 * Address exception handler

 *-----------------------------------------------------------------------------

 * These aren't too critical.

 * (they're not supposed to happen, and won't happen in 32-bit data mode).

 */

/**

 * 根据注释,这里是处理地址异常,它不但不重要,而且不大可能产生。因此就是一个死循环,将系统挂死在这里。

 * 根据《ARM嵌入式系统开发》所述,这是一个保留异常。可能真的不大可能发生。

 */

vector_addrexcptn:

    b       vector_addrexcptn

 

reset异常更简单,它仅仅是模拟调用一次SYS_ERROR0,但这应该是内核初始化完成之后,才这样简单。在flash上的复位异常是整个初始化的入口,应该非常复杂。

ARM(   swi   SYS_ERROR0    )/* reset异常,简单的调用SYS_ERROR0系统调用即可 */

 THUMB(    svc   #0              )/* 应该不会运行到这里,呵呵,这仅仅是我的猜想 */

 THUMB(    nop                     )

 

8个中断异常入口,除系统调用异常外,都是使用b指令进行跳转。系统调用异常使用是这样的:

W(ldr)  pc, .LCvswi + stubs_offset

由于系统调用异常的代码编译在其他文件中,其入口地址与异常向量相隔较远,使用b指令无法跳转过去。 因此将其地址存放到LCvswi中,并从内存地址中加载其入口地址。这样,系统调用的速度稍微慢一点。

 

1.1.2      从汇编跳转到C代码

未定义指令异常、指令预取异常、数据访问中止异常、中断的处理代码分别是vector_und、vector_pabt、vector_dabt和vector_irq。这几个函数是由以下代码生成的:

vector_stub         irq, IRQ_MODE, 4

vector_stub         dabt, ABT_MODE, 8

vector_stub         pabt, ABT_MODE, 4

vector_stub         und, UND_MODE

 

我们以vector_stub    irq, IRQ_MODE, 4为例,看看vector_stub生成了什么代码:

 

     /**

      * 生成通用中断、异常处理代码的宏。

      * correction用于调整lr的值。这是因为进入异常时,pc指针是发生异常时的指针后面8个字节或者12个字节处。

      * 不同的异常需要跳转到不同的返回地址。有的需要重新执行指令,有的则需要跳到下一条指令处。

      */

    .macro     vector_stub, name, mode, correction=0

    /* 将异常入口强制进行32字节对齐,32字节是一个缓存行的大小。这应当是出于性能的考虑。 */

    .align        5

 

vector_\name:

    /* 需要调整返回值,则递减lr寄存器 */

    .if \correction 

    sub   lr, lr, #\correction

    .endif

 

    @

    @ Save r0, lr_ (parent PC) and spsr_

    @ (parent CPSR)

    @

    /**

     * 将r0,lr保存到堆栈中。这里并没有移动堆栈指针。

     * 这是因为:每种处理器模式都有自己的堆栈。接下来系统会切换到svc模式,将堆栈切换到每个任务的系统堆栈去。

     * 执行后,[sp] = r0, [sp+4]=lr,这里保存r0和lr是因为后面要使用这两个寄存器,即这两个寄存器会被破坏。

     * 请注意:中断和异常并不会保存所有寄存器。

     */

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

    /**

     * spsr是异常发生前的状态寄存器,退出异常后,需要根据它恢复现场,因此需要将它保存起来。

     * 首先将它装载到lr寄存器,再将它存储到[sp+8]处。

     */

    mrs  lr, spsr

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

 

    @

    @ Prepare for SVC32 mode.  IRQs remain disabled.

    @

    /**

     * 以下三句,是准备将处理器模式设置为SVC32模式。这样,当前堆栈也会切换到SVC32模式下的堆栈。

     */

    mrs  r0, cpsr

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

    msr  spsr_cxsf, r0

 

    @

    @ the branch table must immediately follow this code

    @

    /**

     * lr中保存了异常前的状态,与0x0f and后,可以得到异常前的处理器模式。

     */

    and  lr, lr, #0x0f

    /* 1f就是宏生成的代码后面的跳转表,这里根据异常前的处理器模式,决定跳转到哪一个处理代码 */

 THUMB(    adr   r0, 1f                           )

 THUMB(    ldr    lr, [r0, lr, lsl #2]         )

   /* sp是SVC32模式下的堆栈指针,这里将它移到r0中,就可以作为C函数的第一个参数,即C函数中的pt_regs参数 */

    mov r0, sp                

    /* pc指针此时指向了1f&#xff0c;即跳转表&#xff0c;因此将它加上lr<<2&#xff0c;就可以按处理器模式进行跳转了 */

 ARM(          ldr    lr, [pc, lr, lsl #2]         )

   /* 这条指令是从异常返回&#xff0c;由于我们修改了spsr寄存器&#xff0c;因此会进入SVC32模式&#xff0c;并不是真的从异常返回了 */

    movs         pc, lr                            &#64; branch to handler in SVC mode

ENDPROC(vector_\name)

 

    .align        2

    &#64; handler addresses follow this label

1:

    .endm

 

分析完vector_stub宏代码&#xff0c;我们再看看中断处理函数是如何生成的&#xff1a;

/**

 * 借助宏vector_stub生成vector_irq主体代码

 */

vector_stub     irq, IRQ_MODE, 4

              /**

              * 下面的跳转表必须紧跟在vector_stub宏后面&#xff0c;参见前文对vector_stub的分析。

               */

    /* 从用户态进入中断的处理函数 */

    .long         __irq_usr                   &#64;  0  (USR_26 / USR_32)

    /* 错误&#xff0c;不应该从FIQ状态进入IRQ状态 */

    .long         __irq_invalid                       &#64;  1  (FIQ_26 / FIQ_32)

    .long         __irq_invalid                       &#64;  2  (IRQ_26 / IRQ_32)

    /* 从SVC模式进入中断 */

    .long         __irq_svc                   &#64;  3  (SVC_26 / SVC_32)

    .long         __irq_invalid                       &#64;  4

    .long         __irq_invalid                       &#64;  5

    .long         __irq_invalid                       &#64;  6

    .long         __irq_invalid                       &#64;  7

    .long         __irq_invalid                       &#64;  8

    .long         __irq_invalid                       &#64;  9

    .long         __irq_invalid                       &#64;  a

    .long         __irq_invalid                       &#64;  b

    .long         __irq_invalid                       &#64;  c

    .long         __irq_invalid                       &#64;  d

    .long         __irq_invalid                       &#64;  e

         .long         __irq_invalid                       &#64;  f

 

接下来我们看看__irq_invalid&#xff0c;这段代码一般情况不应当被调用。

__irq_invalid:

    /**

     * 将所有寄存器保存到堆栈中&#xff0c;并将BAD_IRQ作为错误原因写入r1寄存器。

     */

    inv_entry BAD_IRQ

    /**

     * 跳转到通用错误处理

     */

    b       common_invalid

ENDPROC(__irq_invalid)

 

common_invalid代码如下&#xff1a;

common_invalid:

    /**

     * 如果需要栈帧&#xff0c;就将fp设置为0&#xff0c;这样在进行堆栈回溯时&#xff0c;就可以知道这里的堆栈是一个中断的栈帧了。

     */

    zero_fp

 

    /**

     * r0保存的是中断栈开始的地方&#xff0c;将中断前的r0-r2寄存器现场恢复到r4-r6中。

     */

    ldmia        r0, {r4 - r6}

    /**

     * 调整r0&#xff0c;使其指向中断现场的PC

     */

    add  r0, sp, #S_PC             &#64; here for interlock avoidance

    mov r7, #-1                         &#64;  ""   ""    ""        ""

    /**

     * 将中断前的r0存到sp中。

     */

    str    r4, [sp]               &#64; save preserved r0

    /* 这里没有看清楚&#xff0c;飘过。清楚的同学发一个邮件给我scxby&#64;163.com */

    stmia        r0, {r5 - r7}                 &#64; lr_,

                                         &#64; cpsr_, "old_r0"

 

    /* sp是SVC32模式上的堆栈地址&#xff0c;指向pt_regs&#xff0c;即中断前的寄存器现场 */

    mov r0, sp

    /**

     * 跳转到C处理函数&#xff0c;这里编译脚本应当有处理&#xff0c;这样才能确保bad_mode与当前指令相近。否则b指令跳不过去。

     */

    b       bad_mode

ENDPROC(__und_invalid)

 

1.1.1.1         从用户态进入中断

_irq_usr函数的第一步是保存用户态寄存器现场到svc32堆栈中&#xff0c;这是通过调用usr_enry来实现的&#xff1a;

    .macro     usr_entry

 UNWIND(.fnstart      )

 UNWIND(.cantunwind      )        &#64; don&#39;t unwind the user space

   /**

    * 将svc32堆栈指针向低地址方向移动一个pt_regs结构大小&#xff0c;用于保存寄存器现场。

    */

    sub   sp, sp, #S_FRAME_SIZE

    /**

     * 向svc32堆栈中保存寄存器现场。

     */

 ARM(          stmib        sp, {r1 - r12}     )

 THUMB(    stmia        sp, {r0 - r12}     )

 

    /**

     * r0是中断栈指针&#xff0c;从其中取出中断前的r0-r2现场放到r1-r4中。

     */

    ldmia        r0, {r1 - r3}

    add  r0, sp, #S_PC             &#64; here for interlock avoidance

    mov r4, #-1                         &#64;  ""  ""     ""        ""

 

    /**

     * 从中断栈中取出真实的r0存放到pt_regs->r0中。

     */

    str    r1, [sp]               &#64; save the "real" r0 copied

                                         &#64; from the exception stack

 

    &#64;

    &#64; We are now ready to fill in the remaining blanks on the stack:

    &#64;

    &#64;  r2 - lr_, already fixed up for correct return/restart

    &#64;  r3 - spsr_

    &#64;  r4 - orig_r0 (see pt_regs definition in ptrace.h)

    &#64;

    &#64; Also, separately save sp_usr and lr_usr

    &#64;

    /**

     * 将中断异常栈中取出中断前ARM_pc、ARM_cpsr保存到svc32栈中。

     */

    stmia        r0, {r2 - r4}

    /**

     * 将栈指针和lr压入栈

     */

 ARM(          stmdb       r0, {sp, lr}^                          )

 THUMB(    store_user_sp_lr r0, r1, S_SP - S_PC       )

 

    &#64;

    &#64; Enable the alignment trap while in kernel mode

    &#64;

    alignment_trap r0

 

    &#64;

    &#64; Clear FP to mark the first stack frame

    &#64;

    /**

     * 将fp设置为0&#xff0c;这样可以标示一个中断栈帧。

     */

    zero_fp

    .endm

 

中断处理的主要过程如下&#xff1a;

/**

 * 从用户态进入中断。

 */

__irq_usr:

    /**

     * 将寄存器现场保存起来。

     */

    usr_entry

    /**

     * 对低版本的ARM核来说&#xff0c;用户态无法实现原子比较交换。如果用户态在处理原子比较交换的过程中发生中断&#xff0c;需要特殊处理&#xff0c;略过。

     */

    kuser_cmpxchg_check

 

    /**

     * 如果打开了IRQSOFF_TRACER检测开关&#xff0c;则在这里记录下关中断的时间。这在实时系统中比较有用。

     * 请记住&#xff0c;系统运行到这里&#xff0c;仍然是处于关中断状态的。

     */

#ifdef CONFIG_IRQSOFF_TRACER

    bl      trace_hardirqs_off

#endif

 

    /**

     * 根据当前sp指针&#xff0c;将该指针最右边13位清0&#xff0c;获得当前任务的thread_info。

     */

    get_thread_info tsk

#ifdef CONFIG_PREEMPT

    /**

     * 递增任务的抢占计数

     */

    ldr    r8, [tsk, #TI_PREEMPT]            &#64; get preempt count

    add  r7, r8, #1                    &#64; increment it

    str    r7, [tsk, #TI_PREEMPT]

#endif

 

    irq_handler

#ifdef CONFIG_PREEMPT

    /**

     * 获得当前的抢占计数

     */

    ldr    r0, [tsk, #TI_PREEMPT]

    /**

     * 并将r8中的值保存回去。相当于将前一步递增的抢占计数减回去了。

     */

    str    r8, [tsk, #TI_PREEMPT]

    /**

     * r0,r7是调用irq_handler前后的抢占计数&#xff0c;这里进行比较&#xff0c;是防止驱动的ISR程序没有配对操作抢占计数导致系统错误。

     */

    teq   r0, r7

    /**

     * 如果抢占计数被破坏&#xff0c;则强制写入0.

     */

 ARM(          strne         r0, [r0, -r0]       )

 THUMB(    movne      r0, #0                 )

 THUMB(    strne         r0, [r0]      )

#endif

 

    /**

     * 从中断退回用户态。

     */

    mov why, #0

    b       ret_to_user_from_irq

 UNWIND(.fnend                  )

ENDPROC(__irq_usr)

 

在没有配置MULTI_IRQ_HANDLER 的情况下&#xff0c;irq_handler的逻辑很简单&#xff0c;就是简单的调用arch_irq_handler_default。

    .macro     arch_irq_handler_default

    get_irqnr_preamble r5, lr

    /**

     * 将中断号读取到r0寄存器。

     */

1:          get_irqnr_and_base r0, r6, r5, lr

    /**

     * 如果还存在中断&#xff0c;就将sp作为第二个参数&#xff0c;调用asm_do_IRQ。sp目前指向pt_regs。

     */

    movne      r1, sp

    &#64;

    &#64; routine called with r0 &#61; irq number, r1 &#61; struct pt_regs *

    &#64;

    /**

     * 这里将lr设置为get_irqnr_and_base的第二条指令&#xff0c;因为第二次循环时&#xff0c;不必执行其第一条指令(加载寄存器基址)

     */

    adrne        lr, BSYM(1b)

    /**

     * 将中断号、pt_regs(中断前的寄存器现场)传递给asm_do_IRQ。

     * 请注意&#xff0c;当asm_do_IRQ返回时&#xff0c;会返回到get_irqnr_and_base处&#xff0c;这里相当于是一个循环处理&#xff0c;直到所有中断都已经处理完毕才退出循环。

     */

    bne  asm_do_IRQ

 

#ifdef CONFIG_SMP

    /*

     * XXX

     *

     * this macro assumes that irqstat (r6) and base (r5) are

     * preserved from get_irqnr_and_base above

     */

    /**

     * 这里是从寄存器中读取ipi标志

     */

    ALT_SMP(test_for_ipi r0, r6, r5, lr)

    ALT_UP_B(9997f)

    movne      r1, sp

    /**

     * 同理&#xff0c;这里也是将返回地址设置为ALT_SMP的第二条指令&#xff0c;构造成一个循环。

     */

    adrne        lr, BSYM(1b)

    /**

     * 只要存在IPI就调用do_IPI&#xff0c;并循环直到处理完所有IPI。

     */

    bne  do_IPI

 

#ifdef CONFIG_LOCAL_TIMERS

    /**

     * 同理&#xff0c;这里循环处理多核系统中的本地时钟中断。

     */

    test_for_ltirq r0, r6, r5, lr

    movne      r0, sp

    adrne        lr, BSYM(1b)

    bne  do_local_timer

#endif

#endif

9997:

    .endm

 

至此&#xff0c;我们已经将汇编部分分析完毕。在跳转到C代码前&#xff0c;汇编代码会将中断前的现场保存到堆栈中&#xff0c;并形成一个pt_regs结构传给C函数。最终&#xff0c;会循环调用asm_do_IRQ、do_IPI、do_local_timer。仅仅在多核下&#xff0c;才可能处理IPI和local_timer。

 

1.1.1.1         退回用户态

从中断返回到用户态是由ret_to_user_from_irq进行处理的&#xff0c;在恢复寄存器现场前&#xff0c;需要处理抢占、检查信号等等。

/**

 * 从中断返回用户态&#xff0c;在软中断或者中断处理函数退出时&#xff0c;系统确保已经关闭了中断。

 */

ENTRY(ret_to_user_from_irq)

    /**

     * 从任务的TI_FLAGS标志判断是否需要处理抢占或者信号。

     */

    ldr    r1, [tsk, #TI_FLAGS]

    tst    r1, #_TIF_WORK_MASK

    /**

     * 处理抢占或者信号

     */

    bne  work_pending

    /**

     * 运行到这里&#xff0c;说明没有抢占或者信号需要处理&#xff0c;或者已经处理完毕&#xff0c;开始退回用户态了。

     */

no_work_pending:

    /**

     * 退回用户态时&#xff0c;必然会打开中断&#xff0c;因此这里记录下打开中断的事实&#xff0c;供调试用。

     */

#if defined(CONFIG_IRQSOFF_TRACER)

    asm_trace_hardirqs_on

#endif

    /* perform architecture specific actions before user return */

    /**

     * 在返回用户态前&#xff0c;处理各个体系结构的钩子&#xff0c;对我们分析的单板来说&#xff0c;没有钩子需要处理。

     */

    arch_ret_to_user r1, lr

 

    /**

     * 恢复寄存器现场&#xff0c;并切回用户态。

     */

    restore_user_regs fast &#61; 0, offset &#61; 0

ENDPROC(ret_to_user_from_irq)

 

在切换回用户态前&#xff0c;需要处理抢占和信号&#xff1a;

work_pending:

    /**

     * 检查任务的_TIF_NEED_RESCHED&#xff0c;如果置位&#xff0c;则说明需要处理任务抢占&#xff0c;在这里调度到高优先级任务。

     */

    tst    r1, #_TIF_NEED_RESCHED

    bne  work_resched

    /**

     * 接着处理信号。

     */

    tst    r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME

    /**

     * 没有信号需要处理&#xff0c;则跳转到no_work_pending并退回用户态。

     */

    beq  no_work_pending

    mov r0, sp                                    &#64; &#39;regs&#39;

    mov r2, why                                 &#64; &#39;syscall&#39;

    tst    r1, #_TIF_SIGPENDING             &#64; delivering a signal?

    movne      why, #0                                &#64; prevent further restarts

    /**

     * 这里处理信号

     */

    bl      do_notify_resume

    /**

     * 然后重新关中断并判断是否有更多任务需要处理。

     */

    b       ret_slow_syscall               &#64; Check work again

 

/**

 * 这里处理抢占&#xff0c;注意这里可以调用schedule&#xff0c;而不用调用preempt_schedule。这是有原因的。

 * 另外&#xff0c;这个标号也不能随意移到其他地方。因为调用schedule后&#xff0c;流程会转到ret_slow_syscall。

 * ret_slow_syscall上会关中断&#xff0c;然后将中断、异常返回流程重新处理一次。

 * 需要关中断的原因&#xff0c;是schedule函数会强制将中断打开。

 */

work_resched:

    bl      schedule

/*

 * "slow" syscall return path.  "why" tells us if this was a real syscall.

 */

ENTRY(ret_to_user)

ret_slow_syscall:

    disable_irq                                   &#64; disable interrupts

 

 

1.1.1.2         svc32模式进入中断

当中断嵌套或者中断打断系统调用等异常时&#xff0c;中断会从svc32模式进入中断。在开始中断处理前&#xff0c;系统仍然需要保存寄存器现场。这是通过调用宏svc_entry来实现的。

/**

 * 当从svc模式进入中断处理程序时&#xff0c;使用本宏保存寄存器现场到堆栈中&#xff0c;并形成pt_regs结构传递给C函数。

 */

    .macro     svc_entry, stack_hole&#61;0

 UNWIND(.fnstart               )

 UNWIND(.save {r0 - pc}              )

   /**

    * 将当前指针向低地址移动&#xff0c;以保存寄存器现场。这里减去4是为了将sp指向pt_regs中r1的位置。

    */

    sub   sp, sp, #(S_FRAME_SIZE &#43; \stack_hole - 4)

#ifdef CONFIG_THUMB2_KERNEL/* 新内核支持将内核编译为THUMB-2&#xff0c;以节省代码空间&#xff0c;我们的系统不支持这个功能 */

 SPFIX(         str    r0, [sp]     )        &#64; temporarily saved

 SPFIX(         mov r0, sp                 )

 SPFIX(         tst    r0, #4                 )        &#64; test original stack alignment

 SPFIX(         ldr    r0, [sp]     )        &#64; restored

#else

 SPFIX(         tst    sp, #4                 )

#endif

 SPFIX(         subeq       sp, sp, #4 )

 

   /**

    * 将r1-r12保存到堆栈中。

    */

    stmia        sp, {r1 - r12}

 

    /**

     * r0,sp,lr,spsr已经被汇编代码使用&#xff0c;因此需要根据r0从中断栈(我们目前正在使用的是svc栈)中取出

     */

    ldmia        r0, {r1 - r3}

    /**

     * r5指向pt_regs的ARM_sp即r13

     */

    add  r5, sp, #S_SP - 4        &#64; here for interlock avoidance

    mov r4, #-1                         &#64;  ""  ""      ""       ""

    /**

     * 将r0调整到刚进入宏的位置

     */

    add  r0, sp, #(S_FRAME_SIZE &#43; \stack_hole - 4)

 SPFIX(         addeq       r0, r0, #4 )

   /**

    * 保存r0&#xff0c;同时将sp向下调整4字节&#xff0c;现在sp指向pt_regs了。

    */

    str    r1, [sp, #-4]!              &#64; save the "real" r0 copied

                                         &#64; from the exception stack

 

    mov r1, lr

 

    &#64;

    &#64; We are now ready to fill in the remaining blanks on the stack:

    &#64;

    &#64;  r0 - sp_svc

    &#64;  r1 - lr_svc

    &#64;  r2 - lr_, already fixed up for correct return/restart

    &#64;  r3 - spsr_

    &#64;  r4 - orig_r0 (see pt_regs definition in ptrace.h)

    &#64;

   stmia        r5, {r0 - r4}/* 将中断栈中的数据保存到pt_regs */

    .endm

 

当不是从用户态进入中断时&#xff0c;中断处理代码要稍显复杂一点&#xff0c;主要是需要处理抢占&#xff1a;

/**

 * 从svc32模式进入中断的处理过程

 */

__irq_svc:

    /**

     * 首先保存寄存器现场。

     */

    svc_entry

 

    /**

     * 进入中断后&#xff0c;系统自动将中断关闭&#xff0c;这里调用trace_hardirqs_off记录下中断被关闭的事实&#xff0c;用于跟踪调试。

     */

#ifdef CONFIG_TRACE_IRQFLAGS

    bl      trace_hardirqs_off

#endif

#ifdef CONFIG_PREEMPT

    /**

     * 对可抢占内核来说&#xff0c;这里将任务的抢占计数加1&#xff0c;在整个中断处理过程中&#xff0c;进程都不能被抢占。

     */

    get_thread_info tsk

    ldr    r8, [tsk, #TI_PREEMPT]            &#64; get preempt count

    add  r7, r8, #1                    &#64; increment it

    str    r7, [tsk, #TI_PREEMPT]

#endif

 

    irq_handler

#ifdef CONFIG_PREEMPT

    /**

     * 恢复抢占计数。

     */

    str    r8, [tsk, #TI_PREEMPT]            &#64; restore preempt count

    /**

     * 将任务的TI_FLAGS标志加载到r0中&#xff0c;这样后面会根据r0判断_TIF_NEED_RESCHED&#xff0c;以处理任务抢占

     */

    ldr    r0, [tsk, #TI_FLAGS]                   &#64; get flags

    /**

     * 如果在进入中断前&#xff0c;系统处于系统调用状态&#xff0c;那么抢占计数就可能为0.

     * 这里比较抢占计数是否为0&#xff0c;如果为0&#xff0c;则进行抢占处理。

     */

    teq   r8, #0                                    &#64; if preempt count !&#61; 0

    /**

     * 如果系统关抢占了&#xff0c;那么强制针r0清0&#xff0c;这样就不可能调用svc_preempt

     */

    movne      r0, #0                                    &#64; force flags to 0

    /**

     * 如果系统没有关抢占&#xff0c;并且任务存在_TIF_NEED_RESCHED标志&#xff0c;则调用svc_preempt处理抢占。

     */

    tst    r0, #_TIF_NEED_RESCHED

    blne svc_preempt

#endif

    /* 在此中断处于关闭状态&#xff0c;从pt_regs中获得中断前的SPSR寄存器&#xff0c;接下来将会用这个寄存器恢复状态。 */

    ldr    r4, [sp, #S_PSR]                 &#64; irqs are already disabled

    /* 如果恢复状态后&#xff0c;将会打开中断&#xff0c;则调用trace_hardirqs_on进行跟踪 */

#ifdef CONFIG_TRACE_IRQFLAGS

    tst    r4, #PSR_I_BIT

    bleq trace_hardirqs_on

#endif

    /**

     * 退回svc32模式

     */

    svc_exit r4                                    &#64; return from exception

 UNWIND(.fnend                  )

ENDPROC(__irq_svc)

 

处理抢占的代码并不复杂&#xff0c;如下&#xff1a;

#ifdef CONFIG_PREEMPT

svc_preempt:

    mov r8, lr

    /**

     * 这里调用preempt_schedule_irq处理抢占调度&#xff0c;今后在分析调度时&#xff0c;将会详细介绍这个函数。

     */

1:          bl      preempt_schedule_irq             &#64; irq en/disable is done inside

    /**

     * preempt_schedule_irq返回时&#xff0c;会重新将中断关闭&#xff0c;这里加载TI_FLAGS标志是安全的。

     */

    ldr    r0, [tsk, #TI_FLAGS]                   &#64; get new tasks TI_FLAGS

    tst    r0, #_TIF_NEED_RESCHED

    /**

     * 如果任务没有抢占标志&#xff0c;那么退回上层继续处理&#xff0c;恢复寄存器现场&#xff0c;返回上层中断。

     */  

    moveq      pc, r8                                    &#64; go again

    /**

     * 否则表示任务再次被抢占&#xff0c;循环处理抢占。

     */

    b       1b

#endif

 

 

1.1.1.3         退回svc32模式

从中断退出的代码如下&#xff1a;

#ifndef CONFIG_THUMB2_KERNEL

    .macro     svc_exit, rpsr

    msr  spsr_cxsf, \rpsr /* 恢复rpsr */

#if defined(CONFIG_CPU_V6)

    /**

     * 恢复r0寄存器

     */

    ldr    r0, [sp]

    /**

     * 由于发生了中断&#xff0c;需要执行strex指令&#xff0c;这样上层中断中的spinlock会认为排它性装载失效&#xff0c;重启spinlock循环。

     * 在mips等体系结构中&#xff0c;这是由硬件完成的。可能ARM硬件不能完成这件事。

     */

    strex         r1, r2, [sp]                           &#64; clear the exclusive monitor

    /**

     * 恢复所有寄存器&#xff0c;并恢复cpsr。将处理器状态切回中断前。

     */

    ldmib        sp, {r1 - pc}^                       &#64; load r1 - pc, cpsr

#elif defined(CONFIG_CPU_32v6K)

    clrex                                               &#64; clear the exclusive monitor

    ldmia        sp, {r0 - pc}^                       &#64; load r0 - pc, cpsr

#else

    ldmia        sp, {r0 - pc}^                       &#64; load r0 - pc, cpsr

#endif

    .endm

 

至此&#xff0c;进入中断和退出中断的基本流程已经梳理完成。我们将在下一部分讲中断处理函数的C语言部分。包含ISR和软中断的处理。


转载于:https://www.cnblogs.com/sky-heaven/p/5321563.html


推荐阅读
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • 本文介绍了使用哈夫曼树实现文件压缩和解压的方法。首先对数据结构课程设计中的代码进行了分析,包括使用时间调用、常量定义和统计文件中各个字符时相关的结构体。然后讨论了哈夫曼树的实现原理和算法。最后介绍了文件压缩和解压的具体步骤,包括字符统计、构建哈夫曼树、生成编码表、编码和解码过程。通过实例演示了文件压缩和解压的效果。本文的内容对于理解哈夫曼树的实现原理和应用具有一定的参考价值。 ... [详细]
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
author-avatar
手机用户2502932807
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有