首页 > 其他分享 >3.2 页面异常-1

3.2 页面异常-1

时间:2024-11-02 10:17:31浏览次数:3  
标签:Status STATUS 3.2 Address PageOp 异常 AddressSpace 页面

系列文章目录

文章目录


3.2 页面异常

前面曾经提到,在为交割而分配一个区间时,区块的类型变成了MEM_COMMIT,此后这个区间就可以被访问了。但是,所谓交割也只是逻辑上、“账面”上的操作,而并未落实到实际的物理页面映射。于是,用户空间的程序在系统调用成功返回后就可能访问这个区间,然而一访问就因缺页而发生了页面异常,因为此时实际上尚未建立起物理页面的映射。
发生页面异常时,内核走过一个类似于中断的过程,因异常的原因为页面异常而进入页面异常处理程序 MmAccessFault(),这个函数的地位类似于中断处理程序。

MmAccessFault()


NTSTATUS
NTAPI
MmAccessFault(IN BOOLEAN StoreInstruction,
              IN PVOID Address,
              IN KPROCESSOR_MODE Mode,
              IN PVOID TrapInformation)
{
    /* Cute little hack for ROS */
    if ((ULONG_PTR)Address >= (ULONG_PTR)MmSystemRangeStart)
    {
        //地址落在系统空间
        /* Check for an invalid page directory in kernel mode */
        if (Mmi386MakeKernelPageTableGlobal(Address))
        {   //也许是因为当前进程的系统空间映射与全局的内核映射不尽一致,更新即可
            /* All is well with the world */
            return STATUS_SUCCESS;
        }
    }

    /* Keep same old ReactOS Behaviour */
    if (StoreInstruction)
    {
        /* Call access fault *///因越权访问而引起
        return MmpAccessFault(Mode, (ULONG_PTR)Address, TrapInformation ? FALSE : TRUE);
    }
    else
    {
        /* Call not present *///因缺页而引起
        return MmNotPresentFault(Mode, (ULONG_PTR)Address, TrapInformation ? FALSE : TRUE);
    }
}

这个函数是由底层的异常响应程序调用的,异常响应程序的作用类似于中断响应程序。当发生某种异常时(页面异常是14号常),CPU首先进入相应的异常响应程序,在那里再根据具体的情况调用不同的处理程序。

参数 Storelnstruction是个表示异常原因的布尔量,为0表示缺页,非0表示越权。该信息来自发生异常时CPU自动压入堆栈的出错代码。发生异常时,CPU一方面根据异常的种类产生中断1异常向量,使控制转入相应的异常响应程序入口,另一方面在保存返回断点的同时将一个进一步说明异常原因的出错代码压入堆栈。

般而言,页面异常多半发生于用户空间,发生在系统空间的可能性也有,因为Windows允许倒出系统空间的部分页面。其中有一种情况,表面看来是个问题,实际上却不是。我们知道,当内核的映射出现变动时,该变动首先反映在全局的内核映射表中,然后才反映到当前进程的映射表中。如果在这中间访问了刚发生变动而尚未来得及反映到当前进程的页面映射表中的页面,就可能发生异常。但是此时只要依据全局的内核映射表更新当前进程的映射表,就可以重试引起异常的访问了这里的 Mmi386MakeKernelPageTableGlobal()就用来检查是否属于这种情况,并(就所涉及的页面)更新当前进程的映射表。这个函数返回TRUE表示天下本无事,所以这里就直接成功返回了,这是一种优化。

发生页面异常的原因大体上可以分成两大类。一类是由于违反了页面的保护模式,这是因访问权限不足而引起的:另一类是因缺页而引起的。这里分别提供了两个函数加以处理,我们只看其中之一MmNotPresentFault(),这是对于缺页的处理。

MmNotPresentFault()

[MmAccessFault()>MmNotPresentFault()]


NTSTATUS
NTAPI
MmNotPresentFault(KPROCESSOR_MODE Mode,
                           ULONG_PTR Address,
                           BOOLEAN FromMdl)
{
   PMADDRESS_SPACE AddressSpace;
   MEMORY_AREA* MemoryArea;
   NTSTATUS Status;
   BOOLEAN Locked = FromMdl;
   PFN_TYPE Pfn;

  
   /*
    * Find the memory area for the faulting address
    */
   if (Address >= (ULONG_PTR)MmSystemRangeStart)//页面异常发生于系统空间
   {
      /*
       * Check permissions
       */
      if (Mode != KernelMode)
      {
	 CPRINT("Address: %x\n", Address);
         return(STATUS_ACCESS_VIOLATION);
      }
      AddressSpace = MmGetKernelAddressSpace();//采用内核地址空间
   }
   else/*页面异常发生于用户空间*/
   {
      AddressSpace = (PMADDRESS_SPACE)&(PsGetCurrentProcess())->VadRoot;//当前进程的用户空间
   }

   if (!FromMdl)
   {
      MmLockAddressSpace(AddressSpace);
   }

   /*
    * Call the memory area specific fault handler
    */
   do
   {
      MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, (PVOID)Address);
      if (MemoryArea == NULL || MemoryArea->DeleteInProgress)
      {//区间尚未分配,或者正在被删除
         if (!FromMdl)
         {
            MmUnlockAddressSpace(AddressSpace);
         }
         return (STATUS_ACCESS_VIOLATION);
      }

      switch (MemoryArea->Type)//区间已分配,根据区间的类型采取措施
      {
         case MEMORY_AREA_PAGED_POOL:
            {
               Status = MmCommitPagedPoolAddress((PVOID)Address, Locked);
               break;
            }

         case MEMORY_AREA_SYSTEM:
            Status = STATUS_ACCESS_VIOLATION;
            break;

         case MEMORY_AREA_SECTION_VIEW:
            Status = MmNotPresentFaultSectionView(AddressSpace,
                                                  MemoryArea,
                                                  (PVOID)Address,
                                                  Locked);
            break;

         case MEMORY_AREA_VIRTUAL_MEMORY:
         case MEMORY_AREA_PEB_OR_TEB:
            Status = MmNotPresentFaultVirtualMemory(AddressSpace,
                                                    MemoryArea,
                                                    (PVOID)Address,
                                                    Locked);
            break;

         case MEMORY_AREA_SHARED_DATA:
	    Pfn = MmSharedDataPagePhysicalAddress.QuadPart >> PAGE_SHIFT;
            Status =
               MmCreateVirtualMapping(PsGetCurrentProcess(),
                                      (PVOID)PAGE_ROUND_DOWN(Address),
                                      PAGE_READONLY,
                                      &Pfn,
                                      1);
            break;

         default:
            Status = STATUS_ACCESS_VIOLATION;
            break;
      }
   }
   while (Status == STATUS_MM_RESTART_OPERATION);

   DPRINT("Completed page fault handling\n");
   if (!FromMdl)
   {
      MmUnlockAddressSpace(AddressSpace);
   }
   return(Status);
}

参数Address表明引起异常的内存单元地址(而不是引起异常的指令所在的地址),如果这个(虚存)地址在系统空间,就通过MmGetKernelAddressSpace()获取代表系统空间的数据结构(指针)在用户空间则从当前进程的数据结构获取其用户空间的结构指针。

然后,通过 MmLocateMemoryAreaByAddress()在该空间中寻找所属的虚存区间。如果找不到,就说明这个地址根本就不在任何已经分配的区间之内,所以这是一次属于越界访问的“硬伤”。

而如果找到了这个地址所属的区间,那就要看该区间的类型了。对于一般的虚存区间,即类型为MEMORY_AREA_VIRTUAL_MEMORY的区间,所做的反应是通过MmNotPresentFaultVirtualMemory()加以处理。

MmNotPresentFaultVirtualMemory()

[MmNotPresentFault()>MmNotPresentFaultVirtualMemory()]


NTSTATUS
NTAPI
MmNotPresentFaultVirtualMemory(PMADDRESS_SPACE AddressSpace,
                               MEMORY_AREA* MemoryArea,
                               PVOID Address,
                               BOOLEAN Locked)
/*
 * FUNCTION: Move data into memory to satisfy a page not present fault
 * ARGUMENTS:
 *      AddressSpace = Address space within which the fault occurred
 *      MemoryArea = The memory area within which the fault occurred
 *      Address = The absolute address of fault
 * RETURNS: Status
 * NOTES: This function is called with the address space lock held.
 */
{
   PFN_TYPE Page;
   NTSTATUS Status;
   PMM_REGION Region;
   PMM_PAGEOP PageOp;

   /*
    * There is a window between taking the page fault and locking the
    * address space when another thread could load the page so we check
    * that.
    */
   if (MmIsPagePresent(NULL, Address))//页面已经在位
   {
      if (Locked)
      {
         MmLockPage(MmGetPfnForProcess(NULL, Address));
      }
      return(STATUS_SUCCESS);
   }

   /*
    * Check for the virtual memory area being deleted.
    */
   if (MemoryArea->DeleteInProgress)
   {
      return(STATUS_UNSUCCESSFUL);
   }

   /*
    * Get the segment corresponding to the virtual address //找到所属的区块
    */
   Region = MmFindRegion(MemoryArea->StartingAddress,
                         &MemoryArea->Data.VirtualMemoryData.RegionListHead,
                         Address, NULL);
   if (Region->Type == MEM_RESERVE || Region->Protect == PAGE_NOACCESS)
   {//尚未COMMIT或不让访问的页面当然不应该被访问
      return(STATUS_ACCESS_VIOLATION);
   }

   /*
    * Get or create a page operation
    * //创建或者获取一个已由别的线程创建的PageOp
    */
   PageOp = MmGetPageOp(MemoryArea, AddressSpace->Process->UniqueProcessId,
                        (PVOID)PAGE_ROUND_DOWN(Address), NULL, 0,
                        MM_PAGEOP_PAGEIN, FALSE);
   if (PageOp == NULL)
   {
      DPRINT1("MmGetPageOp failed");
      KEBUGCHECK(0);
   }

   /*
    * Check if someone else is already handling this fault, if so wait
    * for them
    */
   if (PageOp->Thread != PsGetCurrentThread())
   {//别的线程已在进行同样的页面操作
      MmUnlockAddressSpace(AddressSpace);
      Status = KeWaitForSingleObject(&PageOp->CompletionEvent,
                                     0,
                                     KernelMode,
                                     FALSE,
                                     NULL);//等待其完成
      /*
      * Check for various strange conditions
      */
      if (Status != STATUS_SUCCESS)
      {
         DPRINT1("Failed to wait for page op\n");
         KEBUGCHECK(0);
      }
      if (PageOp->Status == STATUS_PENDING)
      {
         DPRINT1("Woke for page op before completion\n");
         KEBUGCHECK(0);
      }
      /*
      * If this wasn't a pagein then we need to restart the handling
      */
      if (PageOp->OpType != MM_PAGEOP_PAGEIN)
      {//当前操作不是MM_PAGEOP_PAGEIN,重新加以处理
         MmLockAddressSpace(AddressSpace);
         KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
         MmReleasePageOp(PageOp);
         return(STATUS_MM_RESTART_OPERATION);
      }
      /*
      * If the thread handling this fault has failed then we don't retry
      */
      if (!NT_SUCCESS(PageOp->Status))
      {
         MmLockAddressSpace(AddressSpace);
         KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
         Status = PageOp->Status;
         MmReleasePageOp(PageOp);
         return(Status);
      }
      MmLockAddressSpace(AddressSpace);
      if (Locked)
      {
         MmLockPage(MmGetPfnForProcess(NULL, Address));
      }
      KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
      MmReleasePageOp(PageOp);//别的线程已经完成了同样的操作
      return(STATUS_SUCCESS);
   }
}

在同一个内存区间之内,还可以有多个不同的区块,其中有的可能已经交割兑现了,有的可能还只是保留了但未交割,所以这里还要通过 MmFindRegion()从中找到具体的区块。显然,只有已经交割的区块才可以被访问。所以,如果具体区块的类型(其实是状态)是MEM_RESERVE或者PAGE_NOACCESS,就没有什么办法可以补救了,所以直接失败返回,由系统的出错处理机制即“结构化异常处理”机制去采取进一步的措施。

确认了地址 Address所在的区块可以被访问之后,就通过 MmGetPageOp()获取或登记一个类型为MM_PAGEOP_PAGEIN的MM_PAGEOP数据结构PageOp。之所以要使用这样的数据结构,是因为不同的线程有可能要启动针对同一页面的操作(例如两个线程先后访问同一个不在位的页面),采用 MM_PAGEOP数据结构及其队列可以使后来的线程发现已经有别的线程在进行针对同一页面的操作。这样一来可以把相同的操作合并在一起,二来也可以避免互相干扰。所以,如果发现已经有别的线程在进行针对同一页面的操作,就需要先通过 KeWaitForSingleObiect()等待其完成。
我们暂时岔开来看一下MmGetPageOp()的代码:

MmGetPageOp()

[MmNotPresentFault() > MmNotPresentFaultVirtualMemory() > MmGetPageOp()]

PMM_PAGEOP
NTAPI
MmGetPageOp(PMEMORY_AREA MArea, HANDLE Pid, PVOID Address,
            PMM_SECTION_SEGMENT Segment, ULONG Offset, ULONG OpType, BOOLEAN First)
.....................
   /*
    * Calcuate the hash value for pageop structure //计算一个Hash值
    */
   if (MArea->Type == MEMORY_AREA_SECTION_VIEW)
   {
      Hash = (((ULONG_PTR)Segment) | (((ULONG_PTR)Offset) / PAGE_SIZE));
   }
   else
   {
      Hash = (((ULONG_PTR)Pid) | (((ULONG_PTR)Address) / PAGE_SIZE));
   }
   Hash = Hash % PAGEOP_HASH_TABLE_SIZE;

   KeAcquireSpinLock(&MmPageOpHashTableLock, &oldIrql);

   /*
    * Check for an existing pageop structure
    */
   PageOp = MmPageOpHashTable[Hash];
   while (PageOp != NULL)
   {
      if (MArea->Type == MEMORY_AREA_SECTION_VIEW)
      {
         if (PageOp->Segment == Segment &&
               PageOp->Offset == Offset)
         {
            break;
         }
      }
      else
      {
         if (PageOp->Pid == Pid &&
               PageOp->Address == Address)
         {
            break;
         }
      }
      PageOp = PageOp->Next;
   }

   /*
    * If we found an existing pageop then increment the reference count
    * and return it.
    */
   if (PageOp != NULL)
   {
      if (First)
      {
         PageOp = NULL;
      }
      else
      {
         PageOp->ReferenceCount++;
      }
      KeReleaseSpinLock(&MmPageOpHashTableLock, oldIrql);
      return(PageOp);
   }

   /*
    * Otherwise add a new pageop.
    */
   PageOp = ExAllocateFromNPagedLookasideList(&MmPageOpLookasideList);
   if (PageOp == NULL)
   {
      KeReleaseSpinLock(&MmPageOpHashTableLock, oldIrql);
      KEBUGCHECK(0);
      return(NULL);
   }

   if (MArea->Type != MEMORY_AREA_SECTION_VIEW)
   {
      PageOp->Pid = Pid;
      PageOp->Address = Address;
   }
   else
   {
      PageOp->Segment = Segment;
      PageOp->Offset = Offset;
   }
   PageOp->ReferenceCount = 1;
   PageOp->Next = MmPageOpHashTable[Hash];
   PageOp->Hash = Hash;
   PageOp->Thread = PsGetCurrentThread();
   PageOp->Abandoned = FALSE;
   PageOp->Status = STATUS_PENDING;
   PageOp->OpType = OpType;
   PageOp->MArea = MArea;
   KeInitializeEvent(&PageOp->CompletionEvent, NotificationEvent, FALSE);
   MmPageOpHashTable[Hash] = PageOp;
   (void)InterlockedIncrementUL(&MArea->PageOpCount);

   KeReleaseSpinLock(&MmPageOpHashTableLock, oldIrql);
   return(PageOp);
}

参数 Address已与页面边界对齐,因为前面的实参是PAGE_ROUND_DOWN(Address)。所以这儿的参数 Address 是页面的起始地址。
内核中有一组杂凑(Hash)的页面操作(请求)队列,每当要进行页面操作时就创建一个MM_PAGEOP数据结构并将其挂入某个杂凑队列,表示此项操作正在进行之中。所以,如果根据杂凑值在队列中找到了特征相符的数据结构,就说明己有针对同一个页面的操作在进行中,需要等待其完成,所以返回指向这个数据结构的指针。要是找不到,就分配一个MM_PAGEOP数据结构,加以初始化之后将其挂入杂凑队列,同样也返回指向这个数据结构的指针。
回到 MmNotPresentFaultVirtualMemory()的代码。如果从 MmGetPageOp()返回的结构指针PageOp表明该页面操作的启动者并非当前线程,就说明别的线程已经启动了针对同一个页面的操作,所以通过KeWaitForSingleObject()等待该页面操作完成。但是,针对同一页面的操作未必就是同样的操作。如果所完成的操作不是MM_PAGEOP_PAGEIN,就说明这里所需的操作尚未完成,同志仍需努力。怎么努力呢?就是出错返回,返回出错代码STATUS_MM_RESTART_OPERATION。这样,上一层函数MmNotPresentFault()中的 do{}while()循环就会以相同的参数再次调用MmNotPresentFaultVirtualMemory(),这一次可能就不存在由别的线程启动的针对同一个页面的操作了。如果还存在也不要紧,只不过是又一轮循环而己。反之,如果刚完成的这个操作恰好也是MM_PAGEOP_PAGEIN,那就实际上合二为一了,目的已经达到,可以成功返回了。
如果没有别的线程在进行针对同一页面的操作,则当前线程就是该页面操作的启动者,所以当前线程承担着完成此项操作的责任,我们继续往下看:

[MmNotPresentFault()>MmNotPresentFaultVirtualMemory()]
/*
    * Try to allocate a page//分配物理内存页面,不等待
    */
   Status = MmRequestPageMemoryConsumer(MC_USER, FALSE, &Page);
   if (Status == STATUS_NO_MEMORY)
   {//分配物理内存页面,等行
      MmUnlockAddressSpace(AddressSpace);
      Status = MmRequestPageMemoryConsumer(MC_USER, TRUE, &Page);
      MmLockAddressSpace(AddressSpace);
   }
   if (!NT_SUCCESS(Status))
   {
      DPRINT1("MmRequestPageMemoryConsumer failed, status = %x\n", Status);
      KEBUGCHECK(0);
   }

   /*
    * Handle swapped out pages.
    */
   if (MmIsPageSwapEntry(NULL, Address))
   {
      SWAPENTRY SwapEntry;
	  //从页面映射表中读出相应的PTE,将其(右移一位)转换成SwapEntry,并将PTE清0
      MmDeletePageFileMapping(AddressSpace->Process, Address, &SwapEntry);
      Status = MmReadFromSwapPage(SwapEntry, Page);//读入倒换页面
      if (!NT_SUCCESS(Status))
      {
         KEBUGCHECK(0);
      }
      MmSetSavedSwapEntryPage(Page, SwapEntry);
   }

   /*
    * Set the page. If we fail because we are out of memory then
    * try again
    */
   Status = MmCreateVirtualMapping(AddressSpace->Process,
                                   (PVOID)PAGE_ROUND_DOWN(Address),
                                   Region->Protect,
                                   &Page,
                                   1);//建立物理页面的映射
   while (Status == STATUS_NO_MEMORY)
   {
      MmUnlockAddressSpace(AddressSpace);
      Status = MmCreateVirtualMapping(AddressSpace->Process,
                                      Address,
                                      Region->Protect,
                                      &Page,
                                      1);
      MmLockAddressSpace(AddressSpace);
   }
   if (!NT_SUCCESS(Status))
   {
      DPRINT1("MmCreateVirtualMapping failed, not out of memory\n");
      KEBUGCHECK(0);
      return(Status);
   }

   /*
    * Add the page to the process's working set
    */
   MmInsertRmap(Page, AddressSpace->Process, (PVOID)PAGE_ROUND_DOWN(Address));

   /*
    * Finish the operation
    */
   if (Locked)
   {
      MmLockPage(Page);
   }
   PageOp->Status = STATUS_SUCCESS;
   KeSetEvent(&PageOp->CompletionEvent, IO_NO_INCREMENT, FALSE);
   MmReleasePageOp(PageOp);
   return(STATUS_SUCCESS);

内核函数 MmRequestPageMemoryConsumer()的作用是分配一个物理页面,如果内核中已经没有空闲的物理页面,就得把已经有较长时间没有得到访问的页面换出到页面倒换文件,腾出一些物理页面,然后再来分配。这个函数的第二个参数为真表示可以等待、即等待换出页面以腾出一些空闲的物理页面。这里的第一次 MmRequestPageMemoryConsumer()是不等待的,如果失败就再来一次,但这一次只好等待了。
获得了空闲的物理页面之后,当然需要在虚存页面与物理页面之间建立起映射,但是这里又有两种不同的情况。一种情况是,在发生本次页面异常之前相应的页面映射表项是0,说明这是全新的映射,此时所需的是一个空白的物理页面,所以只需建立映射就行了。另一种情况是相应的页面映射表项非0,说明原来是有物理页面的,只是其内容已经倒换出去,此时需要先从倒换文件中读入该页面的映像,然后才能建立映射。函数MmIsPageSwapEntry()就是用于这个判断的,这个函数返回非0表示已经有页面映像在倒换文件中
对于已被倒换出去的页面,代码中先通过 MmDeletePageFileMapping()从相应的页面映射表项中获取“倒换描述项”,即倒换页面所在的文件和位置。如前所述,当页面不在内存中时,相应页面表项 PTE的最低位为0,而其余31位就用来描述作为后备的倒换页面所在的文件和位置。获得了这些信息以后,就通过MmReadFromSwapPage()从页面倒换文件读入相应的页面映像。

MmReadFromSwapPage()

[MmNotPresentFault() > MmNotPresentFaultVirtualMemory()>MmReadFromSwapPage()]

NTSTATUS
NTAPI
MmReadFromSwapPage(SWAPENTRY SwapEntry, PFN_TYPE Page)
{
   ULONG i, offset;
   LARGE_INTEGER file_offset;
   IO_STATUS_BLOCK Iosb;
   NTSTATUS Status;
   KEVENT Event;
   UCHAR MdlBase[sizeof(MDL) + sizeof(ULONG)];
   PMDL Mdl = (PMDL)MdlBase;

   DPRINT("MmReadFromSwapPage\n");

   if (SwapEntry == 0)
   {
      KEBUGCHECK(0);
      return(STATUS_UNSUCCESSFUL);
   }

   i = FILE_FROM_ENTRY(SwapEntry);//高8位是文件号
   offset = OFFSET_FROM_ENTRY(SwapEntry);//低24位是文件内部位移

   if (i >= MAX_PAGING_FILES)//倒换文件的数量(32)
   {
      DPRINT1("Bad swap entry 0x%.8X\n", SwapEntry);
      KEBUGCHECK(0);
   }
   if (PagingFileList[i]->FileObject == NULL ||
         PagingFileList[i]->FileObject->DeviceObject == NULL)
   {
      DPRINT1("Bad paging file 0x%.8X\n", SwapEntry);
      KEBUGCHECK(0);
   }

   MmInitializeMdl(Mdl, NULL, PAGE_SIZE);
   MmBuildMdlFromPages(Mdl, &Page);

   file_offset.QuadPart = offset * PAGE_SIZE;//实际的字节位移要乘上PAGESIZE
   file_offset = MmGetOffsetPageFile(PagingFileList[i]->RetrievalPointers, file_offset);

   KeInitializeEvent(&Event, NotificationEvent, FALSE);
   Status = IoPageRead(PagingFileList[i]->FileObject,
                       Mdl,
                       &file_offset,
                       &Event,
                       &Iosb);
   if (Status == STATUS_PENDING)
   {
      KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
      Status = Iosb.Status;
   }
   MmUnmapLockedPages(Mdl->MappedSystemVa, Mdl);
   return(Status);
}

倒换描述项的高8位(实际上是高7位)是倒换文件号,低24位则是页面在文件内部的位移不过这个位移是以页面为单位的位移,所以实际上是页面号,实际的字节位移则还要乘上页面的大小。下面的MmGetOffsetPageFile()和IoPageRead()就是具体的文件操作了,在这里我们并不关心。
回到 MmNotPresentFaultVirtualMemory()的代码中,现在要在虚存页面与物理页面之间建立映射了,这是由 MmCreateVirtualMappingO完成的。所谓建立映射,就是要将物理页面号和页面保护模式(读/写/执行),以及访问权限DPL(0环或3环)等信息组合起来,形成一个最低位为1(表示页面在位)的页面映射表项 PTE(的值):再把这个值写到页面映射表中与给定虚存地址相对应的表项中(如果所属的目录项为0,则还要分配相应的二级映射表,并修改目录项)。这个操作的原理并不复杂,但是代码却颇为烦琐,限于篇幅这里就不深入下去了,有兴趣或需要的读者可以自行钻研。

标签:Status,STATUS,3.2,Address,PageOp,异常,AddressSpace,页面
From: https://blog.csdn.net/zhyjhacker/article/details/143446171

相关文章

  • 记录一次大炮打蚊子的modbustcp通讯连接异常问题定位事件
    一.问题描述某种场景下,安装有Ubuntu22系统的设备A开机后,1-2min内设备E遥控器不能遥控设备A移动,之后恢复正常。二.设备组网设备A和设备C之间使用modbustcp协议进行通讯。三.首战3.1查看日志放开该端口的modbus查询帧日志打印,发现整体的帧格式,发现返回了modbus数据帧,但是返......
  • 如何在Spring Boot应用中配置全局异常处理器?
    在SpringBoot应用中,可以通过以下步骤配置全局异常处理器:一、创建全局异常处理类创建一个类并添加@ControllerAdvice注解,这个注解表示该类是一个全局的控制器增强类,用于处理控制器中抛出的异常。importorg.springframework.http.HttpStatus;importorg.springframew......
  • 【Java】异常处理见解,了解,进阶到熟练掌握
    各位看官早安午安晚安呀如果您觉得这篇文章对您有帮助的话欢迎您一键三连,小编尽全力做到更好欢迎您分享给更多人哦大家好我们今天来学习Java面向对象的的抽象类和接口,我们大家庭已经来啦~目录1.(throws和throw)我们不管这个异常,把这个异常抛出去1.1:throws的注意事项......
  • webpack5配置传统jQuery多页面应用
    简介大家好,我是chenms,最近我们公司有要求需要开发几个以前传统的前后端不分离的jQuery老项目,现在大部分都是用vue或者react开发习惯了组件化的方式,所以我这边打算用webpack5配置一个可以打包传统jQuery多页面应用想法通过配置postcss给css自动加上前缀通过配置babel把e......
  • 系统网络通信异常Read timed out排查及解决过程
    用户反馈应用连接数据库经常出现网络通信异常相关sql语句在数据库在数据库客户端能正常执行,执行时间不到1s检查数据库参数,运行日志,网络方面,驱动版本都正常查看应用报错日志,里面有readtimdout报错,一般是连接超时导致的 检查durid连接池配置如下 Druid连接池conne......
  • 配置elk插件安全访问elk前台页面
    编辑els配置文件vimelasticsearch.yml,添加以下配置文件用elk用户,启动els服务关闭防火墙,查看els启动是否成功,通过是否启动java进程来判断或者通过查看是否启动9200和9300端口来判断是否启动交互模式启动密码配置文件interactive表示交互模式提示输入用户密码,全部输......
  • Java 自定义异常
    注:建议先阅读Java异常分类Java中使用自定义异常类,一般是继承Exception或者它的某个子类。如果父类是RuntimeException或它的某个子类,则自定义异常也是未受检异常;如果是Exception或Exception的其他子类,则自定义异常是受检异常。Java中的一些异常类(比如NullPointer......
  • Java 异常分类
    总结自:《Java核心技术第10版》下图是Java异常层次结构图:所有的异常都是由Throwable继承而来(注意Throwable是类而不是接口),Error和Exception是Throwable的直接子类。Error类用于描述Java运行时系统的内部错误和资源耗尽错误(比如OOM)。应用程序不应该抛出这种......
  • 关于visual stdio 2022代码在win11上运行异常缓慢的解决方案分享
        此篇博客记录笔者解决visualstdio2022运行c语言异常缓慢的解决方案。    起初我上网查资料得知可能是我的模块加载太多了,但是在禁用了额外的模块依然异常缓慢,我就继续查找资料,于是发现另一篇博客反应了和我一样的问题,他的解决方案是关闭联想自带的安全......
  • 1264. 页面推荐#奇怪,非常奇怪,谁能解决这个问题!!!!!!!!!!!!!!!!!!!!!!!
    目录题目链接题目和要求问题1234当时我人就麻了,因为我非常相信我的思路没有问题56然后我就开始怀疑是力扣解释器的问题7发现都可能没有问题8910题目链接题目链接题目:链接!!!题目和要求朋友关系列表:Friendship+---------------+---------+|ColumnName|......