SylixOS 缺页异常

本文详细解析ARM64架构中同步异常的处理流程,包括异常捕获、分类及响应机制,重点介绍了缺页中断的处理过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在arm64 中mmu访问错误会触发同步异常

在同步异常向量表中填入同步异常处理函数,同步异常函数会调用系统的archSyncExcHandle函数,可以看到此函数的第一个参数x0 是当前当前任务的TCB。x1 是ESR_EL1 的值,根据armv8手册此寄存器包含了一些异常的信息。

,特别是此寄存器中的EC 包含了同步错误的类型

根据不同类型的错误,采取不同的处理措施。

/*********************************************************************************************************
** 函数名称: archSyncExcHandle
** 功能描述: 处理 Sync 异常
** 输 入  : pregctx    上下文
**           uiExcType  异常类型
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
VOID  archSyncExcHandle (ARCH_REG_CTX  *pregctx, UINT32  uiExcType)
{
#define  ARM64_EXC_TYPE_UNKNOWN   10

    PLW_CLASS_TCB   ptcbCur;
    LW_VMM_ABORT    abtInfo;
    UINT            uiExcClass;
    UINT            uiExcISS;
    ULONG           ulAbortAddr;
    
    LW_TCB_GET_CUR(ptcbCur);
     
    uiExcClass  = (uiExcType >> 26) & 0x3f;
    uiExcISS    = uiExcType & 0x1ffffff;
    ulAbortAddr = pregctx->REG_ulPC;

    switch (uiExcClass) {

    case EXC_UNKNOWN_REASON:
    case EXC_TRAP_WFI_WFE:
    case EXC_EL3:
        abtInfo.VMABT_uiMethod = 0;
        abtInfo.VMABT_uiType   = ARM64_EXC_TYPE_UNKNOWN;                /*  未知错误                    */
        break;

    case EXC_TRAP_MCR_MRC_CO1111:
    case EXC_TRAP_MCRR_MRRC_CO1111:
    case EXC_TRAP_MCR_MRC_CO1110:
    case EXC_TRAP_LDC_STC:
    case EXC_TRAP_VMRS:
    case EXC_TRAP_MRRC_CO1110:
    case EXC_MSR_MRS_AARCH64:
        uiExcISS               = uiExcType & 0x1;
        abtInfo.VMABT_uiMethod = uiExcISS ? LW_VMM_ABORT_METHOD_READ : LW_VMM_ABORT_METHOD_WRITE;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_PERM;                /*  访问错误                    */
        break;

    case EXC_ACCESS_SIMD_FP:
    case EXC_TRAP_FP_AARCH32:
    case EXC_TRAP_FP_AARCH64:
        abtInfo.VMABT_uiMethod = LW_VMM_ABORT_METHOD_EXEC;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_FPE;                 /*  浮点错误                    */
#if LW_CFG_CPU_FPU_EN > 0
        if (archFpuUndHandle(ptcbCur) == ERROR_NONE) {                  /*  进行 FPU 指令探测           */
            return;
        }
#endif                                                                  /*  LW_CFG_CPU_FPU_EN > 0       */
        break;

    case EXC_ILLEGAL_EXEC:
    case EXC_PC_ALIGNMENT_FAULT:
    case EXC_SP_ALIGNMENT_FAULT:
        abtInfo.VMABT_uiMethod = LW_VMM_ABORT_METHOD_EXEC;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_BUS;                 /*  非对齐访问错误              */
        break;

    case EXC_SVC_AARCH32:
    case EXC_HVC_AARCH32:
    case EXC_SMC_AARCH32:
    case EXC_SVC_AARCH64:
    case EXC_HVC_AARCH64:
    case EXC_SMC_AARCH64:
       abtInfo.VMABT_uiMethod = LW_VMM_ABORT_METHOD_EXEC;
       abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_SYS;                  /*  系统调用错误                */
       break;

    case EXC_INSTRUCTION_ABORT_LO:
    case EXC_INSTRUCTION_ABORT:
        abtInfo.VMABT_uiMethod = LW_VMM_ABORT_METHOD_EXEC;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_UNDEF;               /*  指令错误                    */
        break;

    case EXC_DATA_ABORT_LO:
    case EXC_DATA_ABORT:
        ulAbortAddr            = arm64MmuAbtFaultAddr();
        abtInfo.VMABT_uiMethod = (uiExcISS & 0x40) ? LW_VMM_ABORT_METHOD_WRITE : LW_VMM_ABORT_METHOD_READ;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_MAP;                 /*  数据错误                    */
        if (((uiExcISS & 0xf) == 0xf) && (abtInfo.VMABT_uiMethod == LW_VMM_ABORT_METHOD_WRITE)) {
            abtInfo.VMABT_uiType = LW_VMM_ABORT_TYPE_PERM;              /*  权限错误                    */
        }
        break;

    case EXC_SERROR_INT:
        abtInfo.VMABT_uiMethod = 0;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_FATAL_ERROR;         /*  致命错误                    */
        break;

    case EXC_BREAKPOINT_LO:
    case EXC_BREAKPOINT:
    case EXC_SOFTWARE_STEP_LO:
    case EXC_SOFTWARE_STEP:
    case EXC_WATCHPOINT_LO:
    case EXC_WATCHPOINT:
    case EXC_BKPT_AARCH32:
    case EXC_VECTOR_CATCH_AARCH32:
    case EXC_BRK_AARCH64:
        abtInfo.VMABT_uiMethod = 0;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_BREAK;               /*  断点异常                   */

#if LW_CFG_GDB_EN > 0
        if (API_DtraceBreakTrap(ulAbortAddr, LW_TRAP_BRKPT)
            == ERROR_NONE) {                                            /*  进入调试接口断点处理        */
            return;
        }
#endif                                                                  /*  LW_CFG_GDB_EN > 0           */
        break;

    default:
        break;
    }
    
#if LW_CFG_CPU_EXC_HOOK_EN > 0
    if (bspCpuExcHook(ptcbCur, 
                      pregctx->REG_ulPC, 
                      ulAbortAddr,
                      abtInfo.VMABT_uiType, 
                      abtInfo.VMABT_uiMethod)) {
        return;
    }
#endif
  
    API_VmmAbortIsr(pregctx->REG_ulPC, ulAbortAddr, &abtInfo, ptcbCur);
}

 以上是archSyncExcHandle函数,分析此函数

   uiExcClass  = (uiExcType >> 26) & 0x3f;
    uiExcISS    = uiExcType & 0x1ffffff;
    ulAbortAddr = pregctx->REG_ulPC;

首先EC是从26位开始的所以当前,并且占用7位,所以当前uiExcClasss是单独将其取出来。当发生异常时arm寄存器已经被保存到了任务控制块tcb中这里获取PC就获取了地址。

错误的类型众多,我们只关心发生缺页中断时产生的错误

    case EXC_DATA_ABORT_LO:
    case EXC_DATA_ABORT:
        ulAbortAddr            = arm64MmuAbtFaultAddr();
        abtInfo.VMABT_uiMethod = (uiExcISS & 0x40) ? LW_VMM_ABORT_METHOD_WRITE : LW_VMM_ABORT_METHOD_READ;
        abtInfo.VMABT_uiType   = LW_VMM_ABORT_TYPE_MAP;                 /*  数据错误                    */
        if (((uiExcISS & 0xf) == 0xf) && (abtInfo.VMABT_uiMethod == LW_VMM_ABORT_METHOD_WRITE)) {
            abtInfo.VMABT_uiType = LW_VMM_ABORT_TYPE_PERM;              /*  权限错误                    */
        }
        break;

根据ESR_EL1寄存器的定义,这里的宏定义分别是

也就是手册中的 

根据手册中的说明此错误是真多mmu 错误和堆栈指针未对齐等 。具体类型需要根据ESR_EL1寄存中的ISS位进行判断,根据不同的指令错误,ISS位的含义也不相同,具体可以参考armv8手册。

在发生错误的时候访问异常,需要知道访问的地址

 ulAbortAddr            = arm64MmuAbtFaultAddr();
    
;/*********************************************************************************************************
;  MMU 异常地址获取
;*********************************************************************************************************/

FUNC_DEF(arm64MmuAbtFaultAddr)
    MRS     X0 , FAR_EL1
    RET
    FUNC_END()

    FILE_END()

使用FAR_EL1 寄存器获取异常地址。

此时异常地址和返回地址有已经获得,最后调用 

   API_VmmAbortIsr(pregctx->REG_ulPC, ulAbortAddr, &abtInfo, ptcbCur);

函数对当前的同步异常进行具体处理。

次函数使用上是下边函数的包装。

 API_VmmAbortIsrEx(ulRetAddr, ulAbortAddr, pabtInfo, ptcb, __vmmAbortShell);

以下是 API_VmmAbortIsrEx函数。

/*********************************************************************************************************
** 函数名称: API_VmmAbortIsrEx
** 功能描述: 当 MMU 产生访问失效时, 调用此函数(类似于中断服务函数)
** 输 入  : ulRetAddr     异常返回地址
**           ulAbortAddr   异常地址 (异常类型相关)
**           pabtInfo      异常类型
**           ptcb          出现异常的线程控制块 (不能为 NULL)
**           pfuncHandler  异常处理函数
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
LW_API 
VOID  API_VmmAbortIsrEx (addr_t          ulRetAddr, 
                         addr_t          ulAbortAddr, 
                         PLW_VMM_ABORT   pabtInfo, 
                         PLW_CLASS_TCB   ptcb,
                         VOIDFUNCPTR     pfuncHandler)
{
    PLW_VMM_ABORT_CTX    pabtctx;
    PLW_STACK            pstkFailShell;                                 /*  启动 fail shell 的堆栈点    */
    BYTE                *pucStkNow;                                     /*  记录还原堆栈点              */
    
    __vmmAbortFatalDetected(ulRetAddr, ulAbortAddr, pabtInfo, ptcb);    /*  致命错误探测                */
    
#if LW_CFG_VMM_EN > 0
    __vmmAbortStkOfDetected(ulRetAddr, ulAbortAddr, pabtInfo, ptcb);    /*  是否堆栈溢出                */
#endif                                                                  /*  LW_CFG_VMM_EN > 0           */
    
    __KERNEL_ENTER();                                                   /*  进入内核                    */
                                                                        /*  产生异常                    */
    pucStkNow = (BYTE *)archCtxStackEnd(&ptcb->TCB_archRegCtx);         /*  记录还原堆栈点              */
#if	CPU_STK_GROWTH == 0
    pucStkNow += sizeof(LW_STACK);                                      /*  向空栈方向移动一个堆栈空间  */
    pucStkNow  = (BYTE *)ROUND_UP(pucStkNow, ARCH_STK_ALIGN_SIZE);
    pabtctx    = (PLW_VMM_ABORT_CTX)pucStkNow;                          /*  记录 PAGE_FAIL_CTX 位置     */
    pucStkNow += __ABTCTX_SIZE_ALIGN;                                   /*  让出 PAGE_FAIL_CTX 空间     */
#else
    pucStkNow -= __ABTCTX_SIZE_ALIGN;                                   /*  让出 PAGE_FAIL_CTX 空间     */
    pucStkNow  = (BYTE *)ROUND_DOWN(pucStkNow, ARCH_STK_ALIGN_SIZE);
    pabtctx    = (PLW_VMM_ABORT_CTX)pucStkNow;                          /*  记录 PAGE_FAIL_CTX 位置     */
    pucStkNow -= sizeof(LW_STACK);                                      /*  向空栈方向移动一个堆栈空间  */
#endif
    
    pabtctx->ABTCTX_ptcb          = ptcb;
    pabtctx->ABTCTX_ulRetAddr     = ulRetAddr;                          /*  异常返回地址                */
    pabtctx->ABTCTX_ulAbortAddr   = ulAbortAddr;                        /*  异常地址 (异常类型相关)     */
    pabtctx->ABTCTX_abtInfo       = *pabtInfo;                          /*  异常类型                    */
    pabtctx->ABTCTX_archRegCtx    = ptcb->TCB_archRegCtx;
    pabtctx->ABTCTX_iLastErrno    = (errno_t)ptcb->TCB_ulLastError;
    pabtctx->ABTCTX_iKernelSpace  = __KERNEL_SPACE_GET2(ptcb);

    pstkFailShell = archTaskCtxCreate(&ptcb->TCB_archRegCtx,
                                      (PTHREAD_START_ROUTINE)pfuncHandler,
                                      (PVOID)pabtctx,
                                      (PLW_STACK)pucStkNow,
                                      0);                               /*  建立访问异常陷阱外壳环境    */

    archTaskCtxSetFp(pstkFailShell,
                     &ptcb->TCB_archRegCtx,
                     &pabtctx->ABTCTX_archRegCtx);                      /*  保存 fp, 使 callstack 正常  */

    _StackCheckGuard(ptcb);                                             /*  堆栈警戒检查                */
    
    __KERNEL_EXIT();                                                    /*  退出内核                    */

#if LW_CFG_CPU_FPU_EN > 0
    if (__ABTCTX_ABORT_TYPE(pabtctx) == LW_VMM_ABORT_TYPE_FPE) {
        if (ptcb->TCB_ulOption & LW_OPTION_THREAD_USED_FP) {            /*  如果为 FPU 异常             */
            __ARCH_FPU_SAVE(ptcb->TCB_pvStackFP);                       /*  需要保存当前 FPU CTX        */
        }
    }
#endif                                                                  /*  LW_CFG_CPU_FPU_EN > 0       */

#if LW_CFG_CPU_DSP_EN > 0
    if (__ABTCTX_ABORT_TYPE(pabtctx) == LW_VMM_ABORT_TYPE_DSPE) {
        if (ptcb->TCB_ulOption & LW_OPTION_THREAD_USED_DSP) {           /*  如果为 DSP 异常             */
            __ARCH_DSP_SAVE(ptcb->TCB_pvStackDSP);                      /*  需要保存当前 DSP CTX        */
        }
    }
#endif                                                                  /*  LW_CFG_CPU_DSP_EN > 0       */
}

首先获取了当前的任务的堆栈指针

pucStkNow = (BYTE *)archCtxStackEnd(&ptcb->TCB_archRegCtx);         /*  记录还原堆栈点              */

这里获取堆栈指针是要下边创建线程陷阱。

根据是低地址往高地址还是高地址往低地址,目前预留出异常返回结构体的空间

#if	CPU_STK_GROWTH == 0
    pucStkNow += sizeof(LW_STACK);                                      /*  向空栈方向移动一个堆栈空间  */
    pucStkNow  = (BYTE *)ROUND_UP(pucStkNow, ARCH_STK_ALIGN_SIZE);
    pabtctx    = (PLW_VMM_ABORT_CTX)pucStkNow;                          /*  记录 PAGE_FAIL_CTX 位置     */
    pucStkNow += __ABTCTX_SIZE_ALIGN;                                   /*  让出 PAGE_FAIL_CTX 空间     */
#else
    pucStkNow -= __ABTCTX_SIZE_ALIGN;                                   /*  让出 PAGE_FAIL_CTX 空间     */
    pucStkNow  = (BYTE *)ROUND_DOWN(pucStkNow, ARCH_STK_ALIGN_SIZE);
    pabtctx    = (PLW_VMM_ABORT_CTX)pucStkNow;                          /*  记录 PAGE_FAIL_CTX 位置     */
    pucStkNow -= sizeof(LW_STACK);                                      /*  向空栈方向移动一个堆栈空间  */
#endif

 然后对异常返回空间的值进行赋值

   
    pabtctx->ABTCTX_ptcb          = ptcb;
    pabtctx->ABTCTX_ulRetAddr     = ulRetAddr;                          /*  异常返回地址                */
    pabtctx->ABTCTX_ulAbortAddr   = ulAbortAddr;                        /*  异常地址 (异常类型相关)     */
    pabtctx->ABTCTX_abtInfo       = *pabtInfo;                          /*  异常类型                    */
    pabtctx->ABTCTX_archRegCtx    = ptcb->TCB_archRegCtx;
    pabtctx->ABTCTX_iLastErrno    = (errno_t)ptcb->TCB_ulLastError;
    pabtctx->ABTCTX_iKernelSpace  = __KERNEL_SPACE_GET2(ptcb);

然后创建任务

/*********************************************************************************************************
** 函数名称: archTaskCtxCreate
** 功能描述: 创建任务上下文
** 输 入  : pregctx        寄存器上下文
**           pfuncTask      任务入口
**           pvArg          入口参数
**           pstkTop        初始化堆栈起点
**           ulOpt          任务创建选项
** 输 出  : 初始化堆栈结束点
** 全局变量: 
** 调用模块: 
** 注  意  : 堆栈从高地址向低地址增长.
*********************************************************************************************************/
PLW_STACK  archTaskCtxCreate (ARCH_REG_CTX          *pregctx,
                              PTHREAD_START_ROUTINE  pfuncTask,
                              PVOID                  pvArg,
                              PLW_STACK              pstkTop, 
                              ULONG                  ulOpt)
{
    ARCH_FP_CTX  *pfpctx;
    ARCH_REG_T    ulPstate;
    INT           i;
    
    pstkTop = (PLW_STACK)ROUND_DOWN(pstkTop, ARCH_STK_ALIGN_SIZE);      /*  堆栈指针向下 16 字节对齐    */
    
    pfpctx  = (ARCH_FP_CTX *)((PCHAR)pstkTop - sizeof(ARCH_FP_CTX));
    
    pfpctx->FP_ulFp = (ARCH_REG_T)LW_NULL;
    pfpctx->FP_ulLr = (ARCH_REG_T)LW_NULL;
    
    ulPstate  = arm64GetNZCV() |                                        /*  获得当前 NZCV 寄存器        */
                arm64GetDAIF();                                         /*  获得当前 DAIF 寄存器        */
    ulPstate &= ~M_PSTATE_I;                                            /*  使能 IRQ                    */
    pregctx->REG_ulPstate = ulPstate;
    
    /*
     * 初始化寄存器上下文
     */
    for (i = 0; i < ARCH_GREG_NR; i++) {
        pregctx->REG_ulReg[i] = i;
    }
    
    pregctx->REG_ulSmallCtx = 1;                                        /*  小上下文                    */
    pregctx->REG_ulReg[0]   = (ARCH_REG_T)pvArg;
    pregctx->REG_ulLR       = (ARCH_REG_T)pfuncTask;
    pregctx->REG_ulPC       = (ARCH_REG_T)pfuncTask;
    pregctx->REG_ulSP       = (ARCH_REG_T)pfpctx;
    
    return  ((PLW_STACK)pfpctx);
}

 上面函数是在arm64 创建任务上下文,首先第一个参数将TCB控制块的上下文控制块,分配一个栈帧的空间,好执行完毕后返回,函授获取状态寄存器,给TCB中上下文控制块中的寄状态寄存器赋值,然后将参数赋值给Reg[0],需要执行的任务函数地址赋值给LR和PC。sp赋值单签的栈帧地址。


    archTaskCtxSetFp(pstkFailShell,
                     &ptcb->TCB_archRegCtx,
                     &pabtctx->ABTCTX_archRegCtx);                      /*  保存 fp, 使 callstack 正常  */

最后执行此函数就是将栈帧地址赋值给TCB上下文控制块中的栈帧变量。等待退出函数时tcb上下文控制块寄存器填写到arm寄存器中,然后开始执行__vmmAbortShell 任务。

/*********************************************************************************************************
** 函数名称: __vmmAbortShell
** 功能描述: 当 MMU 产生访问失效时, 线程执行陷阱函数.
** 输 入  : pabtctx    page fail 上下文
** 输 出  : NONE
** 全局变量: 
** 调用模块: 
*********************************************************************************************************/
static VOID  __vmmAbortShell (PLW_VMM_ABORT_CTX  pabtctx)
{
             INTREG                 iregInterLevel;
             addr_t                 ulAbortAddr = pabtctx->ABTCTX_ulAbortAddr;
             ULONG                  ulFlag;
             
    REGISTER PLW_VMM_PAGE           pvmpageVirtual;
    REGISTER PLW_VMM_PAGE           pvmpagePhysical;
    REGISTER PLW_VMM_PAGE_PRIVATE   pvmpagep;
             
             ULONG                  ulAllocPageNum;
             BOOL                   bSwapNeedLoad;
             
             addr_t                 ulVirtualPageAlign;
             ULONG                  ulError;
             INT                    iRet;
             PLW_CLASS_TCB          ptcbCur;                            /*  当前任务控制块              */

    if (__KERNEL_ISENTER()) {
        __vmmAbortDump(pabtctx);                                        /*  打印关键信息                */
        __vmmAbortKill(pabtctx);
        goto    __abort_return;
    }
    
    LW_TCB_GET_CUR_SAFE(ptcbCur);
    
    __VMM_LOCK();
    _K_vmmStatus.VMMS_i64AbortCounter++;
    
    if ((__ABTCTX_ABORT_TYPE(pabtctx) != LW_VMM_ABORT_TYPE_MAP) &&
        (__ABTCTX_ABORT_TYPE(pabtctx) != LW_VMM_ABORT_TYPE_PERM)) {
        __VMM_UNLOCK();
        __vmmAbortAccess(pabtctx);                                      /*  访问异常情况                */
        goto    __abort_return;                                         /*  不会运行到这里              */
    }
    
    pvmpageVirtual = __vmmAbortPageGet(ulAbortAddr);                    /*  获得对应虚拟内存控制块      */
    if (pvmpageVirtual) {
        pvmpagep = (PLW_VMM_PAGE_PRIVATE)pvmpageVirtual->PAGE_pvAreaCb;
    } else {
        __VMM_UNLOCK();
        __vmmAbortAccess(pabtctx);                                      /*  访问异常                    */
        goto    __abort_return;                                         /*  不会运行到这里              */
    }
    
    _K_vmmStatus.VMMS_i64PageFailCounter++;                             /*  缺页中断次数++              */
    ptcbCur->TCB_i64PageFailCounter++;                                  /*  缺页中断次数++              */
    
    ulVirtualPageAlign = ulAbortAddr & LW_CFG_VMM_PAGE_MASK;            /*  获得访问地址页边界          */
    
    ulError = __vmmLibGetFlag(ulVirtualPageAlign, &ulFlag);             /*  获得异常地址的物理页面属性  */
    if (ulError == ERROR_NONE) {                                        /*  存在物理页面正常            */
        if (__ABTCTX_ABORT_TYPE(pabtctx) == LW_VMM_ABORT_TYPE_MAP) {    /*  MAP 类型错误                */
            __VMM_UNLOCK();
            goto    __abort_return;                                     /*  页表已经正常, 可以访问      */
        }
                                                                        /*  写入异常                    */
        if (__ABTCTX_ABORT_METHOD(pabtctx) == LW_VMM_ABORT_METHOD_WRITE) {
            if (__vmmAbortWriteProtect(pvmpageVirtual,                  /*  尝试 copy-on-write 处理     */
                                       ulVirtualPageAlign,
                                       ulError) == ERROR_NONE) {        /*  进入写保护处理              */
                __VMM_UNLOCK();
                goto    __abort_return;
            }
        }
        
        __VMM_UNLOCK();
        __vmmAbortAccess(pabtctx);                                      /*  非法内存访问                */
        goto    __abort_return;                                         /*  不会运行到这里              */
    
    } else {                                                            /*  映射错误, 没有物理页面存在  */
        if (pvmpagep->PAGEP_iFlags & LW_VMM_SHARED_CHANGE) {            /*  共享区间                    */
            if (__vmmAbortShare(pvmpageVirtual, 
                                pvmpagep,
                                ulVirtualPageAlign) == ERROR_NONE) {    /*  尝试共享                    */
                __VMM_UNLOCK();
                goto    __abort_return;
            }
        }
        
        ulAllocPageNum  = __PAGEFAIL_ALLOC_PAGE_NUM;                    /*  缺页中断分配的内存页面个数  */
        
        pvmpagePhysical = __vmmAbortNewPage(ulAllocPageNum);            /*  分配物理页面                */
        if (pvmpagePhysical == LW_NULL) {
            __VMM_UNLOCK();
            
            __ABTCTX_ABORT_TYPE(pabtctx) = LW_VMM_ABORT_TYPE_NOINFO;    /*  缺少物理页面                */
            printk(KERN_CRIT "kernel no more physical page.\n");        /*  系统无法分配物理页面        */
            __vmmAbortKill(pabtctx);
            goto    __abort_return;
        }
        
        bSwapNeedLoad = __vmmPageSwapIsNeedLoad(__PAGEFAIL_CUR_PID,
                                                ulVirtualPageAlign);    /*  检查是否需要 load swap 数据 */
        if (bSwapNeedLoad) {
            iRet = __vmmAbortSwapPage(pvmpagePhysical, 
                                      ulVirtualPageAlign,
                                      ulAllocPageNum);                  /*  进行页面交换处理            */
        
        } else {
            iRet = __vmmAbortNoPage(pvmpagePhysical, 
                                    pvmpagep,
                                    ulVirtualPageAlign, 
                                    ulAllocPageNum);                    /*  进行页面填充处理            */
        }
        
        if (iRet != ERROR_NONE) {
            __vmmPhysicalPageFree(pvmpagePhysical);
            __VMM_UNLOCK();
            __vmmAbortKill(pabtctx);
            goto    __abort_return;
        }
    }
    
    ulError = __vmmLibPageMap(pvmpagePhysical->PAGE_ulPageAddr,         /*  与分配时相同的属性          */
                              ulVirtualPageAlign,
                              ulAllocPageNum, 
                              pvmpageVirtual->PAGE_ulFlags);            /*  映射指定的虚拟地址          */
    if (ulError) {
        _K_vmmStatus.VMMS_i64MapErrCounter++;
        __vmmPhysicalPageFree(pvmpagePhysical);
        __VMM_UNLOCK();

        printk(KERN_CRIT "kernel physical page map error.\n");          /*  系统无法映射物理页面        */
        __vmmAbortKill(pabtctx);
        goto    __abort_return;
    }
    
    pvmpagePhysical->PAGE_ulMapPageAddr = ulVirtualPageAlign;
    pvmpagePhysical->PAGE_ulFlags       = pvmpageVirtual->PAGE_ulFlags;
    
    __pageLink(pvmpageVirtual, pvmpagePhysical);                        /*  建立连接关系                */
    __VMM_UNLOCK();
    
__abort_return:
    __KERNEL_SPACE_SET(pabtctx->ABTCTX_iKernelSpace);                   /*  恢复成进入之前的状态        */
    errno = pabtctx->ABTCTX_iLastErrno;                                 /*  恢复之前的 errno            */
    
    iregInterLevel = KN_INT_DISABLE();                                  /*  关闭当前 CPU 中断           */
    KN_SMP_MB();
    archSigCtxLoad(&pabtctx->ABTCTX_archRegCtx);                        /*  从 page fail 上下文中返回   */
    KN_INT_ENABLE(iregInterLevel);                                      /*  运行不到这里                */
}

首先获取发生异常页面的属性

ULONG  __vmmLibGetFlag (addr_t  ulVirtualAddr, ULONG  *pulFlag)
{
    PLW_MMU_CONTEXT     pmmuctx = __vmmGetCurCtx();
    ULONG               ulFlag;

    ulFlag = __VMM_MMU_FLAG_GET(pmmuctx, ulVirtualAddr);
    if (pulFlag) {
        *pulFlag = ulFlag;
    }
    
    if (ulFlag & LW_VMM_FLAG_VALID) {
        return  (ERROR_NONE);
    
    } else {
        _ErrorHandle(ERROR_VMM_PAGE_INVAL);
        return  (ERROR_VMM_PAGE_INVAL);
    }
}

 此函数会查证映射是否有效,其实是调用的mmu函数的的属性get函数,在arm64 中对应

static ULONG  arm64MmuFlagGet (PLW_MMU_CONTEXT  pmmuctx, addr_t  ulAddr)
{
    LW_PGD_TRANSENTRY  *p_pgdentry = arm64MmuPgdOffset(pmmuctx, ulAddr);/*  获取一级描述符              */
    INT                 iDescType;
    ULONG               ulFlag = 0;

    UINT8               ucGuard;                                        /*  严格的权限检查              */
    UINT8               ucXN;                                           /*  可执行权限标志              */
    UINT8               ucPXN;                                          /*  特权可执行权限标志          */
    UINT8               ucCon;                                          /*  Contiguous 标志             */
    UINT8               ucnG;                                           /*  nG 标志                     */
    UINT8               ucAF;                                           /*  是否拥有访问权限标志        */
    UINT8               ucSH;                                           /*  共享权限标志                */
    UINT8               ucAP;                                           /*  是否可写权限标志            */
    UINT8               ucNS;                                           /*  Non-Secure 标志             */
    UINT8               ucAIn;

    iDescType = (*p_pgdentry) & 0x03;                                   /*  获得一级页表类型            */
    if (iDescType == ARM64_PGD_TYPE_BLOCK) {                            /*  基于段的映射                */
       return  (LW_VMM_FLAG_UNVALID);

    } else if (iDescType == ARM64_PGD_TYPE_TABLE) {                     /*  基于四级页表映射            */
        LW_PMD_TRANSENTRY  *p_pmdentry = arm64MmuPmdOffset((LW_PMD_TRANSENTRY *)p_pgdentry,
                                                           ulAddr);     /*  获取二级描述符              */
        if (arm64MmuPmdIsOk(*p_pmdentry)) {
            LW_PTS_TRANSENTRY  *p_ptsentry = arm64MmuPtsOffset((LW_PTS_TRANSENTRY *)p_pmdentry,
                                                               ulAddr);
                                                                        /*  获取三级描述符              */
            if (arm64MmuPtsIsOk(*p_ptsentry)) {                                                      
                LW_PTE_TRANSENTRY  *p_pteentry = arm64MmuPteOffset((LW_PTE_TRANSENTRY *)p_ptsentry,
                                                                   ulAddr);
                                                                        /*  获取四级描述符              */
                if (arm64MmuPteIsOk(*p_pteentry)) {
                    UINT64  u64Descriptor = (UINT64)(*p_pteentry);

                    ucGuard = (UINT8)((u64Descriptor & ARM64_PTE_GUARD_MASK) >> ARM64_PTE_GUARD_SHIFT);
                    ucXN    = (UINT8)((u64Descriptor & ARM64_PTE_UXN_MASK)   >> ARM64_PTE_UXN_SHIFT);
                    ucPXN   = (UINT8)((u64Descriptor & ARM64_PTE_PXN_MASK)   >> ARM64_PTE_PXN_SHIFT);
                    ucCon   = (UINT8)((u64Descriptor & ARM64_PTE_CONT_MASK)  >> ARM64_PTE_CONT_SHIFT);
                    ucnG    = (UINT8)((u64Descriptor & ARM64_PTE_NG_MASK)    >> ARM64_PTE_NG_SHIFT);
                    ucAF    = (UINT8)((u64Descriptor & ARM64_PTE_AF_MASK)    >> ARM64_PTE_AF_SHIFT);
                    ucSH    = (UINT8)((u64Descriptor & ARM64_PTE_SH_MASK)    >> ARM64_PTE_SH_SHIFT);
                    ucAP    = (UINT8)((u64Descriptor & ARM64_PTE_AP_MASK)    >> ARM64_PTE_AP_SHIFT);
                    ucNS    = (UINT8)((u64Descriptor & ARM64_PTE_NS_MASK)    >> ARM64_PTE_NS_SHIFT);
                    ucAIn   = (UINT8)((u64Descriptor & ARM64_PTE_AIN_MASK)   >> ARM64_PTE_AIN_SHIFT);

                    arm64MmuAttr2Flags(ucGuard, ucXN, ucPXN, ucCon, ucnG,
                                       ucAF, ucSH, ucAP, ucNS, ucAIn, &ulFlag);

                    return  (ulFlag);
                }
            }
        }
    }

    return  (LW_VMM_FLAG_UNVALID);
}

可以看出,当pmd或者pts,pts中不存在此虚拟地址时返回无映射,此时在__vmmAbortShell函数中走else分支

首选是检查是否有共享页面。 共享页面是mmap 函数中用到。这里先不讨论共享页面。

不需要共享页面时,首先分配物理页面,然后将物理页面和虚拟页面建立映射。

在最后页面映射完成后,需要重新返回发生缺页中断的线程调用加载函数

在之前创建陷阱时已经将进程的上下文信息保存在pabtctx中,所以此时将上下文恢复到寄存器中,继续原来程序执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值