linux 中断与异常---源码分析(三)

概述
冷启动、热重启、非屏蔽中断异常入口始终为0xBFC00000,任何其他寄存器都改变不了
一些比较频繁发生的异常为了效率的考虑有专用的异常入口,如TLB refill
除此之外所有的其他异常共用一个异常入口,称为通用异常入口,专用异常入口和通用异常入口可通过寄存器改变

通用异常发生后, CPU 硬件设置 CAUSE寄存器的 ExcCode位, 就跳转到通用异常入口处, ExcCode 位段用来描述通用异常类型,共 5 位,故而可以描述 2^5 = 32 个异常类型。 位于通用异常入口处的是操作系统设置的一个简单的异常处理程序,它会取出 CAUSE 寄存器的 ExcCode 域(5 位,可以描述 32 个异常),用之索引一个通用异常处理表 (exception_handlers),并跳转到异常处理表项所指向的处理程序。

来自硬件的中断,CPU 会自动将 CAUSE 寄存器的 ExcCode 域(6:2)设为 0,其最终会执行 总的中断处理程序 handle_int。 ExcCode 位为 0 时,只是笼统地描述为中断,具体的是何种中断,还要借助 CAUSE 寄存器 的 IP 位(15:8, IP7-IP0)来描述。硬件中断出现时,CPU 会根据中断信号的来源,设置 CAUSE 之 IP 位。IP 位共 8 位,每位对应一个中断。

可以看到,一个硬件中断的流程应该是这样的(以键盘为例): 
1. 用户击键后,键盘控制器 8042 产生中断,通过 中断控制器在 CPU 的中断引脚上引起中断; 
2. CPU 自动设置 CAUSE 的 ExcCode 位为 0,并设置相应的IP位,并跳转到通用异常入口;
3. 位于通用异常入口处的简单异常处理程序,根据 ExcCode 的值索引 异常处理表 (exception_handlers),获取到 0 号异常的处理程序是 handle_int,并跳转过去;
4. handle_int 根据 CAUSE 之 IP 位的值跳转到中断控制器相关的中断处理函数;
5. 中断控制器的处理函数读取中断控制器的寄存器,通过简单的计算得到中断号,进而调 用 do_IRQ 进入相应的中断处理程序。

下面以broadcom CPU_BMIPS4380, linux-3.3.8分析为例:

ebase寄存器可用于改变异常向量入口,是 MIPS   III   R2 规范中被指定的
void __cpuinit bmips_ebase_setup (void)
{
    unsigned long new_ebase = ebase;

#if defined(CONFIG_CPU_BMIPS4350)
    /*
     * BMIPS4350 cannot relocate the normal vectors, but it
     * can relocate the BEV=1 vectors.  So CPU1 starts up at
     * the relocated BEV=1, IV=0 general exception vector @
     * 0xa000_0380.
     *
     * set_uncached_handler() is used here because:
     *  - CPU1 will run this from uncached space
     *  - None of the cacheflush functions are set up yet
     */
    set_uncached_handler(BMIPS_WARM_RESTART_VEC - CKSEG0,
        &bmips_smp_int_vec, 0x80);
    __sync();
    return;
#elif defined(CONFIG_CPU_BMIPS3300) || defined(CONFIG_CPU_BMIPS4380)
    /*
     * 0x8000_0000: reset/NMI (initially in kseg1)
     * 0x8000_0400: normal vectors
     */
    new_ebase = 0x80000400;
    bmips_set_reset_vec(0, RESET_FROM_KSEG0);
#elif defined(CONFIG_CPU_BMIPS5000)
    /*
     * 0x8000_0000: reset/NMI (initially in kseg1)
     * 0x8000_1000: normal vectors
     */
    new_ebase = 0x80001000;
    bmips_set_reset_vec(0, RESET_FROM_KSEG0);
    write_c0_ebase(new_ebase);
#else
    return;
#endif
    board_nmi_handler_setup = &bmips_nmi_handler_setup;
     ebase = new_ebase;
}

#define cpu_has_mips32r1        1
#define cpu_has_mips32r2        0
#define cpu_has_mips64r1        0
#define cpu_has_mips64r2        0
bmips4380属于mips32r1, ebase固定在 0x80000400,不可改变,bmips5000的ebase可以通过write_c0_ebase()设置协处理器来改变,这里ebase是一个全局变量,后面安装异常向量处理函数的时候会用到。

1.cache 错误异常
入口初始化,位于:
ifeq ($(CONFIG_BRCMSTB),y)
obj-y                += c-brcmstb.o cex-gen.o tlb-r4k.o
else
obj-$(CONFIG_CPU_MIPS32)    += c-r4k.o cex-gen.o tlb-r4k.o
endif

 [arch/mips/mm/c-brcmstb.c]
void __cpuinit r4k_cache_init(void)
{
    ...
    set_uncached_handler (0x100, &except_vec2_generic, 0x80);    //cache有专用异常入口
    ...
}
void __cpuinit set_uncached_handler(unsigned long offset, void *addr,
    unsigned long size)
{
    unsigned long uncached_ebase = CKSEG1ADDR(ebase) ;

    if (!addr)
        panic(panic_null_cerr);

    memcpy((void *)(uncached_ebase + offset), addr, size);
}

因为 cache 错误时可以 cache 的 KSEG0 段不能用了,则 cache 错误异常处理程序位于 KSEG1 之 0xA0000000 + 0x100 处,长度最大为 128 Bytes,异常处理程序为 except_vec2_generic,定义于:
[arch/mips/mm/cex-gen.S ]
LEAF(except_vec2_generic)
    .set    noreorder
    .set    noat
    .set    mips0
    /*
     * This is a very bad place to be.  Our cache error
     * detection has triggered.  If we have write-back data
     * in the cache, we may not be able to recover.  As a
     * first-order desperate measure, turn off KSEG0 cacheing.
     */
    mfc0    k0,CP0_CONFIG
    li    k1,~CONF_CM_CMASK
    and    k0,k0,k1
    ori    k0,k0,CONF_CM_UNCACHED
    mtc0    k0,CP0_CONFIG
    /* Give it a few cycles to sink in... */
    nop
    nop
    nop

    j    cache_parity_error
    nop
    END(except_vec2_generic)
关闭KSEG0的cache功能

2.TLB异常
tlb refill 异常处理程序,不像其他异常处理程序那样事先编写好,而是通过一些函数 动态生成 ,然后复制到对应的入口处的 至于为何采取这种方式,主要是因为要根据用户的配置生成适合各种 MIPS平台的 tlb_refill_handler , 由于要考虑的情况过多,使用通常的条件编译的方式已经不能满足需求。
位于:[arch/mips/kernel/tlb-r4k.c]
void __cpuinit tlb_init(void)
{
    ...
    build_tlb_refill_handler();
}
[arch/mips/kernel/tlbex.c]
void __cpuinit build_tlb_refill_handler(void)
{
    ...
    build_r4000_tlb_load_handler();
    build_r4000_tlb_store_handler();
    build_r4000_tlb_modify_handler();
    build_r4000_tlb_refill_handler();
}
u32 handle_tlbl[FASTPATH_SIZE] __cacheline_aligned;
u32 handle_tlbs[FASTPATH_SIZE] __cacheline_aligned;
u32 handle_tlbm[FASTPATH_SIZE] __cacheline_aligned;
static void __cpuinit build_r4000_tlb_load_handler(void)
{
    u32 *p = handle_tlbl;
    memset(handle_tlbl, 0, sizeof(handle_tlbl));
    wr = build_r4000_tlbchange_handler_head(&p, &l, &r);
    ...
    uasm_l_nopage_tlbl(&l, p);
    build_restore_work_registers(&p);
    uasm_i_j(&p, (unsigned long)tlb_do_page_fault_0 & 0x0fffffff);
    uasm_i_nop(&p);
    ...
}
其作用就是构造指令填充handle_tlbl数据区域。handle_tlbl,handle_tlbs,handle_tlbm的入口在通用异常中调用,见后面通用异常介绍。

static u32 tlb_handler[128] __cpuinitdata;
static u32 final_handler[64] __cpuinitdata;

static void __cpuinit build_r4000_tlb_refill_handler(void)
{
    memset(tlb_handler, 0, sizeof(tlb_handler));
    memset(final_handler, 0, sizeof(final_handler));
    ...
     memcpy((void *)ebase, final_handler, 0x100);    //TLB refill有专用异常向量入口
}


3.通用异常处理程序
对于MIPS来说,一般外部中断只有一个入口(0x180),而且是很多异常共享这一个入口,所以需要额外的代码来判断异常的类型。确定是外部中断后,还需要根据IP位来dispatch中断。这就造成MIPS的interrupt latency比较长。所以在Release 2中引入了VINT和VEIC。

MIPSr2 architecture supports 3 interrupt modes. It provides interrupt compatibility mode, which behaves identical to MIPSr1. It also supports vectored interrupt (VI) mode, and permits the use of an external interrupt controller (EIC).

VI mode adds the ability to prioritize and vector interrupts to a handler dedicated to that interrupt. Each interrupt starts at the address according to the interrupt signal. It also allows us to configure the space between different entry points (VS of IntCtl register). In EIC mode, the six independent signals become a 6-bit binary number: Zero means no interrupt, others are 63 distinct interrupt codes.

An exception vector is where the exception handling starts. MIPS exceptions are handled either through the general exception vector (offset 0x180), or the special interrupt vector (0x200), based on the value of IV of Cause register.
可以设置intctl寄存器打开VINT/VEIC模式,默认是关闭的。

[arch/mips/kernel/traps.c]
 void __init trap_init() 
    if (cpu_has_veic || cpu_has_vint) {      //打开VEIC或者VINT
        unsigned long size = 0x200 + VECTORSPACING*64;
        ebase = (unsigned long)
            __alloc_bootmem(size, 1 << fls(size), 0);
    } else {
        ebase = CKSEG0;
        if (cpu_has_mips_r2)
            ebase += (read_c0_ebase() & 0x3ffff000);       // MIPSr2 provides an option to adjust ebase according to VA (bit 29:12) of EBase register.
    }

    if (board_ebase_setup)
        board_ebase_setup();

      per_cpu_trap_init();           //cache错误和TLB refill异常初始化
    ... 
        set_handler(0x180, &except_vec3_generic, 0x80);
    /*
     * Setup default vectors
     */
    for (i = 0; i <= 31; i++)
        set_except_vector(i, handle_reserved);          //安装 异常处理表
     ... 
    set_except_vector(0, rollback ? rollback_handle_int : handle_int );
    set_except_vector(1, handle_tlbm );
    set_except_vector(2, handle_tlbl );
    set_except_vector(3, handle_tlbs );

    set_except_vector(4, handle_adel);
    set_except_vector(5, handle_ades);

    set_except_vector(6, handle_ibe);
    set_except_vector(7, handle_dbe);

    set_except_vector(8, handle_sys );    //对应系统调用          
    set_except_vector(9, handle_bp);
    set_except_vector(10, rdhwr_noopt ? handle_ri :
              (cpu_has_vtag_icache ?
               handle_ri_rdhwr_vivt : handle_ri_rdhwr));
    set_except_vector(11, handle_cpu);
    set_except_vector(12, handle_ov);

    set_except_vector(13, handle_tr);
    ...
    if (board_nmi_handler_setup)        //安装非屏蔽中断
        board_nmi_handler_setup();
    ...
}
在上面我们看到,内核首先用 handle_reserved 填充整个exception_handlers,然 后依次各 MIPS CPU 的特点填充相应的处理函数。

void __init set_handler(unsigned long offset, void *addr, unsigned long size)
{
    memcpy((void *)( ebase + offset), addr, size);
    local_flush_icache_range(ebase + offset, ebase + offset + size);
}

void __init *set_except_vector(int n, void *addr)
{
    unsigned long handler = (unsigned long) addr;
    unsigned long old_handler = exception_handlers [n];

     exception_handlers[n] = handler;
    if (n == 0 && cpu_has_divec) {   //如果有divec 除0异常, 那么中断必须要有专用的入口,这时hander放入在ebase+0x200处
        unsigned long jump_mask = ~((1 << 28) - 1);
        u32 *buf = (u32 *)(ebase + 0x200);
        unsigned int k0 = 26;
        if ((handler & jump_mask) == ((ebase + 0x200) & jump_mask)) {
            uasm_i_j(&buf, handler & ~jump_mask);
            uasm_i_nop(&buf);
        } else {
            UASM_i_LA(&buf, k0, handler);
            uasm_i_jr(&buf, k0);
            uasm_i_nop(&buf);
        }
        local_flush_icache_range(ebase + 0x200, (unsigned long)buf);
    }
    return (void *)old_handler;
}
static inline void __cpuinit bmips_nmi_handler_setup(void)
{
    bmips_wr_vec(BMIPS_NMI_RESET_VEC, &bmips_reset_nmi_vec,
        &bmips_reset_nmi_vec_end);
    bmips_wr_vec(BMIPS_WARM_RESTART_VEC, &bmips_smp_int_vec,
        &bmips_smp_int_vec_end);
}
handle_reserved() and handle_watch() are both built in arch/mips/kernel/genex.S as the followed:
1         BUILD_HANDLER adel ade ade silent               /* #4  */
2         BUILD_HANDLER ades ade ade silent               /* #5  */
3         BUILD_HANDLER ibe be cli silent                 /* #6  */
4         BUILD_HANDLER dbe be cli silent                 /* #7  */
5         BUILD_HANDLER bp bp sti silent                  /* #9  */
6         BUILD_HANDLER ri ri sti silent                  /* #10 */
7         BUILD_HANDLER cpu cpu sti silent                /* #11 */
8         BUILD_HANDLER ov ov sti silent                  /* #12 */
9         BUILD_HANDLER tr tr sti silent                  /* #13 */
10         BUILD_HANDLER fpe fpe fpe silent                /* #15 */
11         BUILD_HANDLER mdmx mdmx sti silent              /* #22 */
12 #ifdef  CONFIG_HARDWARE_WATCHPOINTS
13         /*
14          * For watch, interrupts will be enabled after the watch
15          * registers are read.
16          */
17         BUILD_HANDLER watch watch cli silent            /* #23 */
18 #else
19         BUILD_HANDLER watch watch sti verbose           /* #23 */
20 #endif
21         BUILD_HANDLER mcheck mcheck cli verbose         /* #24 */
22         BUILD_HANDLER mt mt sti silent                  /* #25 */
23         BUILD_HANDLER dsp dsp sti silent                /* #26 */
24         BUILD_HANDLER reserved reserved sti verbose     /* others */
Each call to BUILD_HANDLER will build two functions, handle_\exception and handle_\exception_int. Thus, you may look the symbol table for handle_adel(), handle_adel_int(), handle_ades(), handle_ades_int(), and so on.

How do these handlers get prepared and handle exceptions?
1         .macro  __BUILD_HANDLER exception handler clear verbose ext
2         .align  5
3         NESTED(handle_\exception, PT_SIZE, sp)
4         .set    noat
5         SAVE_ALL
6         FEXPORT(handle_\exception\ext)
7         __BUILD_clear_\clear
8         .set    at
9         __BUILD_\verbose \exception
10         move    a0, sp
11         PTR_LA  ra, ret_from_exception
12         j       do_\handler
13         END(handle_\exception)
14         .endm
15
16         .macro   BUILD_HANDLER exception handler clear verbose
17         __BUILD_HANDLER \exception \handler \clear \verbose _int
18         .endm

except_vec3_generic定义于: [arch/mips/kernel/genex.S]
/*
* General exception vector for all other CPUs.
*
* Be careful when changing this, it has to be at most 128 bytes
* to fit into space reserved for the exception handler.
*/
NESTED(except_vec3_generic, 0, sp)
    .set    push
    .set    noat
#if R5432_CP0_INTERRUPT_WAR
    mfc0    k0, CP0_INDEX
#endif
     mfc0    k1, CP0_CAUSE
    andi    k1, k1, 0x7c
#ifdef CONFIG_64BIT
    dsll    k1, k1, 1
#endif
    PTR_L    k0, exception_handlers(k1)
    jr    k0
    .set    pop
    END(except_vec3_generic)
这段程序完成的功能为:取 cause 寄存器之 ExcCode 值,然后跳转到 exception_handlers+ExcCode*4 处,注意 ExcCode 为 cause 寄存器的位 6:2,因 此 CAUSE & 0x7c 就是 ExcCode*4。

4.中断处理
handle_int定义位于:[arch/mips/kernel/genex.S]
NESTED(handle_int, PT_SIZE, sp)
    SAVE_ALL                // 保存上下文
    CLI                          
    TRACE_IRQS_OFF

    LONG_L    s0, TI_REGS($28)    //asm-offsets.s:373:->TI_REGS 72 offsetof(struct thread_info, regs)     # , 即 s0=*($28+72)
    LONG_S    sp, TI_REGS($28)    // *($28+72)=sp
    PTR_LA    ra, ret_from_irq       //从irq返回
    j     plat_irq_dispatch
    END(handle_int)

  SAVE_ALL用于保存上下文,定义位于:[arch/mips/include/asm/stackframe.h]
        .macro    SAVE_ALL
        SAVE_SOME
        SAVE_AT
        SAVE_TEMP
        SAVE_STATIC
        .endm

        .macro    SAVE_SOME
        .set    push
        .set    noat
        .set    reorder
        mfc0    k0, CP0_STATUS
        sll    k0, 3        /* extract cu0 bit */
        .set    noreorder
        bltz    k0, 8f      //在内核模式下cp0肯定可用,在用户态模式下,cp0不可用,因此可以根据CU0 coprocessor  usability 来判断是否在用户态模式,如果为1,则在内核态
         move    k1, sp  //在slot中的指令肯定会被执行
        .set    reorder
        /* Called from user mode, new stack. */
        get_saved_sp    //从用户模式转到内核模式,需要切换栈,将内核态帧顶保存到k1中
8:        move    k0, sp    //将当前sp的位置保存到k0中,即k0=sp
        PTR_SUBU sp, k1, PT_SIZE    //sp=k1-PT_SIZE   
        LONG_S    k0, PT_R29(sp)     //asm-offsets.s:->PT_R29 140 offsetof(struct pt_regs, regs[29])     #,  即  *(sp+140)=k0,保存中断发生时栈顶的位置
        LONG_S    $3, PT_R3(sp)
        /*
         * You might think that you don't need to save $0,
         * but the FPU emulator and gdb remote debug stub
         * need it to operate correctly
         */
        LONG_S    $0, PT_R0(sp)
        mfc0    v1, CP0_STATUS
        LONG_S    $2, PT_R2(sp)
        ...
      .endm

        .macro    get_saved_sp    /* Uniprocessor variation */          //单处理器版本
        lui    k1, %hi(kernelsp)              //unsigned long kernelsp[NR_CPUS];   取kernelsp变量地址的高位部分存储到k1中
        LONG_L    k1, %lo(kernelsp)(k1)   //  取 kernelsp变量地址的低位部分合并K1中的高位部分形成内存寻址地址,即将kernelsp[0]保存到k1中
        .endm

为何会将内核栈保存在kernelsp中呢?这就得看看进程切换的过程:
#define switch_to(prev, next, last)                    \
do {                                    \
    __mips_mt_fpaff_switch_to(prev);                \
    if (cpu_has_dsp)                        \
        __save_dsp(prev);                    \
    __clear_software_ll_bit();                    \
    (last) = resume(prev, next, task_thread_info(next));        \
} while (0)

resume函数的实现位于:[arch/mips/kernel/r4k_switch.S],这里只解释相关的部分:
/*
* task_struct *resume(task_struct *prev, task_struct *next,
*                     struct thread_info *next_ti)
*/
    .align    5
    LEAF(resume)
       ...
    move    $28, a2     //a2就是这里的next_ti
    ...
    PTR_ADDU    t0, $28, _THREAD_SIZE - 32    //t0=$28+_THREAD_SIZE-32

     set_saved_sp    t0, t1, t2
    ...
    END(resume)

        .macro    set_saved_sp stackp temp temp2
        LONG_S    \stackp, kernelsp
        .endm
这样kernelsp[0]就指向了当前进程内核态的栈顶,这里都只说明了单处理器的情况。

关于CLI (clear interrupt):
        .macro    CLI
        mfc0    t0, CP0_STATUS
        li    t1, ST0_CU0 | STATMASK              //#define STATMASK 0x1f
        or    t0, t1
        xori    t0, STATMASK
        mtc0    t0, CP0_STATUS
关于status寄存器的低位含义:
#define ST0_IE            0x00000001
#define ST0_EXL            0x00000002
#define ST0_ERL            0x00000004
#define ST0_KSU            0x00000018
#  define KSU_USER        0x00000010               //用户模式
#  define KSU_SUPERVISOR    0x00000008        //管理员模式
#  define KSU_KERNEL        0x00000000            //内核模式

所以CLI指令的作用: KSU[4:3]:00 内核模式   ERL[2]:0  EXL[1]:0  IE[0]:0  ,因为EPC已经通过SAVE_ALL保存在栈上了,所以可以清ERL和EXL,进入内核模式,关闭中断

        .macro    STI        //set  interrupt
        mfc0    t0, CP0_STATUS
        li    t1, ST0_CU0 | STATMASK
        or    t0, t1
        xori    t0, STATMASK & ~1
        mtc0    t0, CP0_STATUS
STI指令的作用: KSU[4:3]:00 内核模式   ERL[2]:0  EXL[1]:0  IE[0]:1  ,因为EPC已经通过 SAVE_ALL保存在栈上了,所以可以清ERL和EXL,进入内核模式,打开中断

local_irq_save(flags);    //关闭本地中断,并将原来的中断标志保存在flags变量中
#define local_irq_save(flags)                    \
    do {                            \
        raw_local_irq_save(flags);            \
    } while (0)
#define raw_local_irq_save(flags)            \
    do {                        \
        typecheck(unsigned long, flags);    \
        flags = arch_local_irq_save();        \
    } while (0)
static inline unsigned long arch_local_irq_save(void)
{
    unsigned long flags;
    asm volatile("arch_local_irq_save\t%0"
             : "=r" (flags)
             : /* no inputs */
             : "memory");
    return flags;
}
__asm__(
    "    .macro    arch_local_irq_save result            \n"
    "    .set    push                        \n"
    "    .set    reorder                        \n"
    "    .set    noat                        \n"
    "     di    \\result                    \n"
    "    andi    \\result, 1                    \n"
    "    irq_disable_hazard                    \n"
    "    .set    pop                        \n"
    "    .endm                            \n");
di指令用于disable interrupt,清除status寄存器的IE位,并将原来的值保存在指定的寄存器中
local_irq_restore(flags);    //恢复保存在flags变量中的中断状态

[arch/mips/brcmstb/irq.c]
asmlinkage void plat_irq_dispatch(struct pt_regs *regs)
{
    unsigned int pend = ((read_c0_cause() & read_c0_status()) >> 8) & 0xff;
    unsigned int shift;

    while ((shift = ffs(pend)) != 0) {
        shift--;
        pend ^= 1 << shift;
        if (shift == 2)
            brcm_mips_int2_dispatch(regs);         //处理器间中断
#ifdef CONFIG_SMP
        else if (unlikely(shift == 3))
            brcm_mips_int3_dispatch(regs);
#endif
        else
             do_IRQ (MIPS_CPU_IRQ_BASE + shift);      //由IP得到对应的shift
    }
}
void __irq_entry do_IRQ(unsigned int irq)
{
    irq_enter();
    check_stack_overflow();
    if (!smtc_handle_on_other_cpu(irq))
        generic_handle_irq(irq);
    irq_exit();
}
int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc (irq);

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);
    return 0;
}
irq_desc是在request_irq()中安装的。

从中断返回的过程:
[arch/mips/kernel/entry.S]
FEXPORT(ret_from_irq)        
    LONG_S    s0, TI_REGS($28)        //恢复之前保存在s0中的值
FEXPORT(__ret_from_irq)
    LONG_L    t0, PT_STATUS(sp)        # returning to kernel mode?     // t0= *(sp+152), 异常发生后status寄存器的值保存在栈中这个位置
    andi    t0, t0, KU_USER     //#define    KU_USER 0x10               t0= t0 & 0x10,   判断中断发生时是否在用户态
    beqz    t0, resume_kernel

#define resume_kernel    restore_all    //返回到内核态

resume_userspace:                            //返回到用户态
    local_irq_disable        # make sure we dont miss an
                    # interrupt setting need_resched
                    # between sampling and return
    LONG_L    a2, TI_FLAGS($28)     # current->work   struct thread_info {  ...; unsigned long        flags;...}
    andi    t0, a2, _TIF_WORK_MASK    # (ignoring syscall_trace)  
    bnez    t0, work_pending        //判断当前进程是否有pending的工作要做,包括调度标志和pending的信号
    j    restore_all

work_pending:
    andi    t0, a2, _TIF_NEED_RESCHED # a2 is preloaded with TI_FLAGS    //判断是否需要调度
    beqz    t0, work_notifysig
work_resched:        //进行调度
    jal    schedule

work_notifysig:                # deal with pending signals and
                    # notify-resume requests
    move    a0, sp
    li    a1, 0
    jal     do_notify_resume    # a2 already loaded          //处理pending的signal,该函数定义在:arch/mips/kernel/signal.c
    j    resume_userspace

其中RESTORE_ALL为了恢复上下文:
        .macro    RESTORE_ALL
        RESTORE_TEMP
        RESTORE_STATIC
        RESTORE_AT
        RESTORE_SOME
        RESTORE_SP
        .endm

        .macro    RESTORE_SOME
        mfc0    a0, CP0_STATUS
        ori    a0, STATMASK
        xori    a0, STATMASK
        mtc0    a0, CP0_STATUS    //取出当前status寄存器值
        li    v1, 0xff00
        and    a0, v1     //保留第二个byte
        LONG_L    v0, PT_STATUS(sp)   //取出栈中保留的异常发生时的status值
        nor    v1, $0, v1      // v1=~(0|v1)
        and    v0, v1      //清除第二个byte,其他byte维持不变
        or    v0, a0        
        mtc0    v0, CP0_STATUS            //这段代码的意思就是从栈中恢复保留的status的值,但是维持第二个byte的内容不变

5.系统调用
系统调用异常处理 handle_sys,打算另开一篇再分析。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值