当前位置: 首页 > news >正文

Linux内核的页面错误:原因与解决方案

当程序访问虚拟内存中的一个页面时,如果该页面当前不在物理内存中,就会触发一个称为"page fault"(页异常)的异常。操作系统需要处理这个异常,并将所需页面从磁盘加载到内存中。实现虚存管理的一个关键是page fault异常处理,其过程中主要涉及到函数 — do_pgfault的具体实现。

比如,在程序的执行过程中由于某种原因(页框不存在/写只读页等)而使 CPU 无法最终访问到相应的物理内存单元,即无法完成从虚拟地址到物理地址映射时,CPU 会产生一次页访问异常,从而需要进行相应的页访问异常的中断服务例程。这个页访问异常处理的时机被操作系统充分利用来完成虚存管理,即实现“按需调页”/“页换入换出”处理的执行时机。当相关处理完成后,页访问异常服务例程会返回到产生异常的指令处重新执行,使得应用软件可以继续正常运行下去。

一、内存管理前奏:虚拟内存与MMU

在深入探讨页异常之前,先来了解一下 Linux 内存管理的基础架构。在 Linux 系统中,进程并不直接访问物理内存,而是通过内存管理单元(Memory Management Unit,MMU)来管理虚拟地址与物理地址的映射关系。

想象一下,你正在玩一款角色扮演游戏,每个角色都有自己独立的背包(虚拟地址空间),背包里的物品位置(虚拟地址)与实际仓库(物理内存)中的存储位置是通过游戏管理员(MMU)来协调的。这样,每个角色都觉得自己的背包很大,有足够的空间放置物品,而实际上仓库的空间是有限的。这就是虚拟内存的作用,它让进程以为自己拥有很大的连续内存空间,而不必关心物理内存的实际布局和大小限制。

什么是虚拟内存?

简单地说是指程序员或CPU“看到”的内存。但有几点需要注意:

  • 虚拟内存单元不一定有实际的物理内存单元对应,即实际的物理内存单元可能不存在;

  • 如果虚拟内存单元对应有实际的物理内存单元,那二者的地址一般是不相等的;

  • 通过操作系统实现的某种内存映射可建立虚拟内存与物理内存的对应关系,使得程序员或CPU访问的虚拟内存地址会自动转换为一个物理内存地址。

那么这个“虚拟”的作用或意义在哪里体现呢?在操作系统中,虚拟内存其实包含多个虚拟层次,在不同的层次体现了不同的作用。首先,在有了分页机制后,程序员或CPU“看到”的地址已经不是实际的物理地址了,这已经有一层虚拟化,我们可简称为内存地址虚拟化。有了内存地址虚拟化,我们就可以通过设置页表项来限定软件运行时的访问空间,确保软件运行不越界,完成内存访问保护的功能。

虚拟内存地址空间的引入,不仅解决了物理内存不足的问题,还提供了内存保护和进程隔离的功能。每个进程都有自己独立的虚拟地址空间,彼此之间互不干扰,就像不同的游戏角色在各自的背包里操作物品,不会影响到其他角色的背包。这样,一个进程的内存访问错误不会导致整个系统崩溃,大大提高了系统的稳定性和安全性。

通过 MMU 的映射,虚拟地址被转换为物理地址,这个过程就像是游戏管理员根据角色背包里的物品位置信息,到实际仓库中找到对应的物品。MMU 通过维护页表(Page Table)来记录虚拟地址与物理地址的映射关系,页表就像是一本详细的地址转换字典,MMU 根据虚拟地址在页表中查找对应的物理地址。

在 32 位的 Linux 系统中,虚拟地址空间通常为 4GB,其中一部分用于用户空间,另一部分用于内核空间。用户空间的进程只能访问自己的虚拟地址空间,无法直接访问内核空间,这种隔离机制有效地保护了内核的安全,防止用户进程的非法操作对内核造成破坏 。例如,普通用户在自己的权限范围内进行文件操作,无法直接访问系统核心文件,保障了系统的稳定性。

虚拟内存与 MMU 的这种映射机制,为 Linux 系统的内存管理奠定了坚实的基础,同时也为页异常的发生埋下了伏笔,当进程访问的虚拟地址在页表中找不到对应的物理地址映射时,页异常就会登场 。

二、数据结构与函数

首先是初始化过程。参考ucore总控函数init的代码,可以看到在调用完成虚拟内存初始化的vmm_init函数之前,需要首先调用pmm_init函数完成物理内存的管理,这也是我们lab2已经完成的内容。接着是执行中断和异常相关的初始化工作,即调用pic_init函数和idt_init函数等,这些工作与lab1的中断异常初始化工作的内容是相同的。

在调用完idt_init函数之后,将进一步调用三个lab3中才有的新函数vmm_init、ide_init和swap_init。这三个函数涉及了本次实验中的两个练习。第一个函数vmm_init是检查我们的练习1是否正确实现了。为了表述不在物理内存中的“合法”虚拟页,需要有数据结构来描述这样的页,为此ucore建立了mm_struct和vma_struct数据结构(接下来的小节中有进一步详细描述),假定我们已经描述好了这样的“合法”虚拟页,当ucore访问这些“合法”虚拟页时,会由于没有虚实地址映射而产生页访问异常。如果我们正确实现了练习1,则do_pgfault函数会申请一个空闲物理页,并建立好虚实映射关系,从而使得这样的“合法”虚拟页有实际的物理页帧对应。这样练习1就算完成了。

ide_init和swap_init是为练习2准备的。由于页面置换算法的实现存在对硬盘数据块的读写,所以ide_init就是完成对用于页换入换出的硬盘(简称swap硬盘)的初始化工作。完成ide_init函数后,ucore就可以对这个swap硬盘进行读写操作了。swap_init函数首先建立swap_manager,swap_manager是完成页面替换过程的主要功能模块,其中包含了页面置换算法的实现(具体内容可参考5小节)。

然后会进一步调用执行check_swap函数在内核中分配一些页,模拟对这些页的访问,这会产生页访问异常。如果我们正确实现了练习2,就可通过do_pgfault来调用swap_map_swappable函数来查询这些页的访问情况并间接调用实现页面置换算法的相关函数,把“不常用”的页换出到磁盘上。

ucore在实现上述技术时,需要解决三个关键问题:

  • 当程序运行中访问内存产生page
    fault异常时,如何判定这个引起异常的虚拟地址内存访问是越界、写只读页的“非法地址”访问还是由于数据被临时换出到磁盘上或还没有分配内存的“合法地址”访问?

  • 何时进行请求调页/页换入换出处理?

  • 如何在现有ucore的基础上实现页替换算法?

接下来将进一步分析完成lab3主要注意的关键问题和涉及的关键数据结构。

对于第一个问题的出现,在于实验二中有关内存的数据结构和相关操作都是直接针对实际存在的资源—物理内存空间的管理,没有从一般应用程序对内存的“需求”考虑,即需要有相关的数据结构和操作来体现一般应用程序对虚拟内存的“需求”。一般应用程序的对虚拟内存的“需求”与物理内存空间的“供给”没有直接的对应关系,ucore是通过page fault异常处理来间接完成这二者之间的衔接。

page_fault函数不知道哪些是“合法”的虚拟页,原因是ucore还缺少一定的数据结构来描述这种不在物理内存中的“合法”虚拟页。为此ucore通过建立mm_struct和vma_struct数据结构,描述了ucore模拟应用程序运行所需的合法内存空间。当访问内存产生page fault异常时,可获得访问的内存的方式(读或写)以及具体的虚拟内存地址,这样ucore就可以查询此地址,看是否属于vma_struct数据结构中描述的合法地址范围中,如果在,则可根据具体情况进行请求调页/页换入换出处理(这就是练习2涉及的部分);如果不在,则报错。mm_struct和vma_struct数据结构结合页表表示虚拟地址空间和物理地址空间的示意图如下所示:

图片

在ucore中描述应用程序对虚拟内存“需求”的数据结构是vma_struct(定义在vmm.h中),以及针对vma_struct的函数操作。这里把一个vma_struct结构的变量简称为vma变量。vma_struct的定义如下:

struct vma_struct {// the set of vma using the same PDTstruct mm_struct *vm_mm;uintptr_t vm_start; // start addr of vmauintptr_t vm_end; // end addr of vmauint32_t vm_flags; // flags of vma//linear list link which sorted by start addr of vmalist_entry_t list_link;
};

vm_start和vm_end描述了一个连续地址的虚拟内存空间的起始位置和结束位置,这两个值都应该是PGSIZE 对齐的,而且描述的是一个合理的地址空间范围(即严格确保 vm_start < vm_end的关系);list_link是一个双向链表,按照从小到大的顺序把一系列用vma_struct表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct应该是不相交的,即vma之间的地址空间无交集;vm_flags表示了这个虚拟内存空间的属性,目前的属性包括:

#define VM_READ 0x00000001 //只读
#define VM_WRITE 0x00000002 //可读写
#define VM_EXEC 0x00000004 //可执行

vm_mm是一个指针,指向一个比vma_struct更高的抽象层次的数据结构mm_struct,这里把一个mm_struct结构的变量简称为mm变量。这个数据结构表示了包含所有虚拟内存空间的共同属性,具体定义如下:

struct mm_struct {// linear list link which sorted by start addr of vmalist_entry_t mmap_list;// current accessed vma, used for speed purposestruct vma_struct *mmap_cache;pde_t *pgdir; // the PDT of these vmaint map_count; // the count of these vmavoid *sm_priv; // the private data for swap manager
};

mmap_list是双向链表头,链接了所有属于同一页目录表的虚拟内存空间,mmap_cache是指向当前正在使用的虚拟内存空间,由于操作系统执行的“局部性”原理,当前正在用到的虚拟内存空间在接下来的操作中可能还会用到,这时就不需要查链表,而是直接使用此指针就可找到下一次要用到的虚拟内存空间。由于mmap_cache 的引入,可使得 mm_struct 数据结构的查询加速 30% 以上。pgdir

所指向的就是 mm_struct数据结构所维护的页表。通过访问pgdir可以查找某虚拟地址对应的页表项是否存在以及页表项的属性等。map_count记录mmap_list 里面链接的 vma_struct的个数。sm_priv指向用来链接记录页访问情况的链表头,这建立了mm_struct和后续要讲到的swap_manager之间的联系。

涉及vma_struct的操作函数也比较简单,主要包括三个:

  • vma_create—创建vma

  • insert_vma_struct—插入一个vma

  • find_vma—查询vma。

vma_create函数根据输入参数vm_start、vm_end、vm_flags来创建并初始化描述一个虚拟内存空间的vma_struct结构变量。insert_vma_struct函数完成把一个vma变量按照其空间位置[vma->vm_start,vma->vm_end]从小到大的顺序插入到所属的mm变量中的mmap_list双向链表中。find_vma根据输入参数addr和mm变量,查找在mm变量中的mmap_list双向链表中某个vma包含此addr,即vma->vm_start<=addr end。这三个函数与后续讲到的page fault异常处理有紧密联系。

涉及mm_struct的操作函数比较简单,只有mm_create和mm_destroy两个函数,从字面意思就可以看出是是完成mm_struct结构的变量创建和删除。在mm_create中用kmalloc分配了一块空间,所以在mm_destroy中也要对应进行释放。在ucore运行过程中,会产生描述虚拟内存空间的vma_struct结构,所以在mm_destroy中也要进对这些mmap_list中的vma进行释放。

三、Page Fault异常处理

当进程访问它的虚拟地址空间中的 PAGE 时,如果这个 PAGE 目前还不在物理内存中,此时 CPU 就像一个找不到文件的办事员,无法继续工作。Linux 会立即产生一个 hard page fault 中断,这就像是办事员向上级报告文件缺失的情况 。

在这个过程中,系统需要从慢速设备(如磁盘)将对应的数据 PAGE 读入物理内存,就好比从仓库(磁盘)中找到文件并取出来。然后,建立物理内存地址与虚拟地址空间 PAGE 的映射关系,这一步就像是给文件贴上标签,标明它在虚拟地址空间中的位置。只有完成这些步骤后,进程才能访问这部分虚拟地址空间的内存,办事员才能继续处理文件。

产生页访问异常的原因主要有:

  • 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);

  • 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上),这在本次实验中会出现,我们将在下面介绍换页机制实现时进一步讲解如何处理;

  • 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)

当出现上面情况之一,那么就会产生页面page fault(#PF)异常。CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。

页访问异常错误码有32位。位0为1表示对应物理页不存在;位1为1表示写异常(比如写了只读页;位2为1表示访问权限异常(比如用户态程序访问内核空间的数据)
CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中对应的中断服务例程可以检查CR2的内容,从而查出线性地址空间中的哪个页引起本次异常。

产生页访问异常后,CPU硬件和软件都会做一些事情来应对此事。首先页访问异常也是一种异常,所以针对一般异常的硬件处理操作是必须要做的,即CPU在当前内核栈保存当前被打断的程序现场,即依次压入当前被打断程序使用的EFLAGS,CS,EIP,errorCode;由于页访问异常的中断号是0xE,CPU把异常中断号0xE对应的中断服务例程的地址(vectors.S中的标号vector14处)加载到CS和EIP寄存器中,开始执行中断服务例程。这时ucore开始处理异常中断,首先需要保存硬件没有保存的寄存器。

在vectors.S中的标号vector14处先把中断号压入内核栈,然后再在trapentry.S中的标号__alltraps处把DS、ES和其他通用寄存器都压栈。自此,被打断的程序执行现场(context)被保存在内核栈中。接下来,在trap.c的trap函数开始了中断服务例程的处理流程,大致调用关系为:

trap—> trap_dispatch—>pgfault_handler—>do_pgfault

在操作系统中,do_pgfault(页错误处理函数)是由内核调用的函数。当程序访问一个尚未映射到物理内存的页面时,会触发页错误异常。此时,操作系统会捕获这个异常,并将控制权转移到do_pgfault函数中进行处理。

具体的调用关系可能因不同的操作系统和架构而有所差异,以下是一般情况下的调用关系:

  • 当发生页错误时,CPU会产生一个异常,并将控制权交给操作系统。

  • 操作系统根据异常类型确定是否为页错误,并检查导致页错误的原因。

  • 如果是页面访问权限问题或者缺页(页面尚未加载到物理内存)等原因引起的页错误,则执行相应的处理逻辑。

  • 在处理逻辑中,操作系统可能需要分配物理内存来满足缺页请求,并将相应的页面加载到物理内存中。

  • 执行完必要的处理后,操作系统将重新设置相关寄存器和标志位,然后恢复被中断的进程继续执行。

总之,do_pgfault函数作为内核提供的一个重要回调函数,在页错误发生时负责处理该异常并采取必要措施

产生页访问异常后,CPU把引起页访问异常的线性地址装到寄存器CR2中,并给出了出错码errorCode,说明了页访问异常的类型。ucore OS会把这个值保存在struct trapframe 中tf_err成员变量中。而中断服务例程会调用页访问异常处理函数do_pgfault进行具体处理。这里的页访问异常处理是实现按需分页、页换入换出机制的关键之处。

ucore中do_pgfault函数是完成页访问异常处理的主要函数,它根据从CPU的控制寄存器CR2中获取的页访问异常的物理地址以及根据errorCode的错误类型来查找此地址是否在某个VMA的地址范围内以及是否满足正确的读写权限,如果在此范围内并且权限也正确,这认为这是一次合法访问,但没有建立虚实对应关系。所以需要分配一个空闲的内存页,并修改页表完成虚地址到物理地址的映射,刷新TLB,然后调用iret中断,返回到产生页访问异常的指令处重新执行此指令。如果该虚地址不在某VMA范围内,则认为是一次非法访问。

3.1缺页错误的分类处理

我们在前作内存寻址中介绍了 CPU 发展过程中内存寻址方式的变化。现代 CPU 都支持分段和分页的内存寻址模式。出于寻址能力的考虑,现代操作系统,也顺应着都支持段页式的内存管理模式。当然,虽然支持段页式,但是 Linux 中只启用了段基址为 0 的段。也就是说,在 Linux 当中,实际起作用的只有分页模式。

具体来说,分页模式在逻辑上将虚拟内存和物理内存同时等分成固定大小的块。这些块在虚拟内存上称之为「页」,而在物理内存上称之为「页帧」,并交由 CPU 中的 MMU 模块来负责页帧和页之间的映射管理。

引入分页模式的好处,可以大致概括为两个方面:

  • 允许虚存空间远大于实际物理内存大小的情况。这是因为,分页之后,操作系统读入磁盘的文件时,无需以文件为单位全部读入,而可以以内存页为单位,分片读入。同时,考虑到 CPU 不可能一次性需要使用整个内存中的数据,因此可以交由特定的算法,进行内存调度:将长时间不用的页帧内的数据暂存到磁盘上。

  • 减少了内存碎片的产生。这是因为,引入分页之后,内存的分配管理都是以页大小(通常是 4KiB,扩展分页模式下是 4MiB)为单位的;虚拟内存中的页总是对应物理内存中实际的页帧。这样一来,在虚拟内存空间中,页内连续的内存在物理内存上也一定是连续的,不会产生碎片。

当进程在进行一些计算时,CPU 会请求内存中存储的数据。在这个请求过程中,CPU 发出的地址是逻辑地址(虚拟地址),然后交由 CPU 当中的 MMU 单元进行内存寻址,找到实际物理内存上的内容。若是目标虚存空间中的内存页(因为某种原因),在物理内存中没有对应的页帧,那么 CPU 就无法获取数据。这种情况下,CPU 是无法进行计算的,于是它就会报告一个缺页错误(Page Fault)。

因为 CPU 无法继续进行进程请求的计算,并报告了缺页错误,用户进程必然就中断了。这样的中断称之为缺页中断。在报告 Page Fault 之后,进程会从用户态切换到系统态,交由操作系统内核的 Page Fault Handler 处理缺页错误。

基本来说,缺页错误可以分为两类:硬缺页错误(Hard Page Fault)和软缺页错误(Soft Page Fault)。这里,前者又称为主要缺页错误(Major Page Fault);后者又称为次要缺页错误(Minor Page Fault)。当缺页中断发生后,Page Fault Handler 会判断缺页的类型,进而处理缺页错误,最终将控制权交给用户态代码。

若是此时物理内存里,已经有一个页帧正是此时 CPU 请求的内存页,那么这是一个软缺页错误;于是,Page Fault Hander 会指示 MMU 建立相应的页帧到页的映射关系。这一操作的实质是进程间共享内存——比如动态库(共享对象),比如 mmap 的文件。

若是此时物理内存中,没有相应的页帧,那么这就是一个硬缺页错误;于是 Page Fault Hander 会指示 CPU,从已经打开的磁盘文件中读取相应的内容到物理内存,而后交由 MMU 建立这份页帧到页的映射关系。

不难发现,软缺页错误只是在内核态里轻轻地走了一遭,而硬缺页错误则涉及到磁盘 I/O。因此,处理起来,硬缺页错误要比软缺页错误耗时长得多。这就是为什么我们要求高性能程序必须在对外提供服务时,尽可能少地发生硬缺页错误。

除了硬缺页错误和软缺页错误之外,还有一类缺页错误是因为访问非法内存引起的。前两类缺页错误中,进程尝试访问的虚存地址尚为合法有效的地址,只是对应的物理内存页帧没有在物理内存当中。后者则不然,进程尝试访问的虚存地址是非法无效的地址。比如尝试对 nullptr 解引用,就会访问地址为 0x0 的虚存地址,这是非法地址。

此时 CPU 报出无效缺页错误(Invalid Page Fault)。操作系统对无效缺页错误的处理各不相同:Windows 会使用异常机制向进程报告;*nix 则会通过向进程发送 SIGSEGV 信号(11),引发内存转储。

缺页中断会交给PageFaultHandler处理,其根据缺页中断的不同类型会进行不同的处理:

  • Hard Page Fault:也被称为Major Page Fault,翻译为硬缺页错误/主要缺页错误,这时物理内存中没有对应的页帧,需要CPU打开磁盘设备读取到物理内存中,再让MMU建立VA和PA的映射。

  • Soft Page Fault:也被称为Minor Page Fault,翻译为软缺页错误/次要缺页错误,这时物理内存中是存在对应页帧的,只不过可能是其他进程调入的,发出缺页异常的进程不知道而已,此时MMU只需要建立映射即可,无需从磁盘读取写入内存,一般出现在多进程共享内存区域。

  • Invalid Page Fault:翻译为无效缺页错误,比如进程访问的内存地址越界访问,又比如对空指针解引用内核就会报segment fault错误中断进程直接挂掉。

图片

3.2缺页错误出现的原因

(1)页表相关问题

当进程访问虚拟地址时,首先会查询页表以获取对应的物理地址。如果页表中找不到对应虚拟地址的页表项(Page Table Entry,PTE),就会触发页异常 。这可能是因为该虚拟地址是无效的,就像你在一个公司的员工名单(页表)中查找一个根本不存在的员工的工号(虚拟地址),自然是找不到的。

另一种情况是,虚拟地址是有效的,但对应的物理页面尚未被载入主存,页表项还没有建立,就好比员工虽然存在,但还没有分配工位(物理内存位置),在名单上也没有记录其工位信息。比如在程序动态分配内存时,malloc 函数只是在虚拟地址空间中预留了一段地址范围,当进程首次访问这段地址时,由于对应的物理内存尚未分配和映射,就会导致页表中没有相应的 PTE,从而触发页异常 。

(2)访问权限冲突

即使页表中存在对应虚拟地址的 PTE,但如果该 PTE 的访问权限设置拒绝当前进程的访问操作,也会引发页异常。例如,一个进程试图对一个只读的页面进行写入操作,而该页面的 PTE 中设置了只读权限,这就好比你拿着一张只能进入图书馆阅读区的通行证,却试图进入书库(禁止区域)取书,自然会被拒绝并触发异常。这种情况通常用于保护系统关键数据和代码,防止进程的非法访问和修改,保障系统的稳定性和安全性。

四、处理流程全解析

4.1捕获与跳转

当页异常发生时,首先由 CPU 捕获这个异常信号。就像在一个公司里,员工(进程)在执行任务(访问内存)时遇到问题(页异常),会立即向上级(CPU)报告 。CPU 捕获到这个异常后,会跳转到专门处理页异常的函数page_fault_handler,这个函数就像是公司里专门处理问题的部门,负责解决内存访问异常的问题 。在page_fault_handler中,会进一步分析异常的原因和类型,为后续的处理做准备。

4.2处理逻辑分支

(1)无效地址处理

如果经检查发现访问的地址是无效地址,属于越界访问,系统会返回segment fault错误 。这就好比员工试图进入一个被禁止进入的办公室(无效地址区域),会被保安(系统)阻止并报告给上级。如果是用户地址发生segment fault,系统会直接杀死该进程,以防止进程对系统造成进一步的破坏,就像公司会开除违反规定的员工 。而如果是内核地址发生segment fault,情况则更为严重,可能会导致内核崩溃,就像公司的核心管理层出现严重问题,可能会导致整个公司运营瘫痪 。

(2)有效地址处理

首次访问:当页是第一次被访问时,会执行demand_page_faults(请求调页)操作 。这时候,系统会检查页表中是否存在该虚拟地址对应的页表项(PTE),即通过pmd_none和pte_none等函数来判断 。如果不存在,就需要分配新的页帧,并对其进行初始化,从磁盘中读取相应的数据到内存中 。这就像是公司要为新入职的员工(新访问的页)分配一个工位(页帧),并从仓库(磁盘)中取出相关的办公用品(数据)放到工位上,以便员工能够正常开展工作(进程能够正常访问内存) 。

页在 swap 分区:如果页被交换到了 swap 分区,系统会检查页表项中的present标志位 。这个标志位就像是一个标签,用来标识页面是否在主存中 。如果present标志位为 0,表示该页不在主存中,此时需要分配新的页帧,并从磁盘的 swap 分区重新读入内存 。这就好比员工的办公用品被暂时存放到了仓库的临时存储区(swap 分区),当员工需要使用时,需要从临时存储区把办公用品取回到工位(内存)上 。

COW 情况:当vm_area_struct允许写操作,但对应的页表项(PTE)禁止写操作时,就会触发写时复制(Copy-On-Write,COW)机制 。这是一种优化策略,在多个进程共享同一个页面时,只有当某个进程试图对页面进行写操作时,才会真正复制出一个新的页面供该进程使用,而不是在一开始就为每个进程都复制一份 。就好比多个员工共同使用一份文件(共享页面),当其中一个员工想要修改文件内容(写操作)时,系统会为他复制一份文件副本(新页面),让他在副本上进行修改,而不影响其他员工使用的原始文件 。这样可以节省内存资源,提高系统的效率 。

五、实例与应用场景

5.1程序运行中的体现

以一个简单的 C 程序为例,当程序执行到malloc函数分配内存时,实际上只是在虚拟地址空间中预留了一段地址范围,并没有立即分配物理内存。只有当程序首次访问这段地址时,才会触发页异常 。假设我们有如下 C 代码:

#include <stdio.h>
#include <stdlib.h>int main() {int *ptr = (int *)malloc(1024 * sizeof(int)); // 分配内存,但未实际占用物理内存if (ptr == NULL) {perror("malloc failed");return 1;}// 首次访问分配的内存,会触发页异常ptr[0] = 100; printf("Value at ptr[0]: %d\n", ptr[0]);free(ptr);return 0;
}

在这段代码中,malloc函数分配了一段虚拟内存地址,当执行ptr[0] = 100;时,由于这是首次访问该地址,对应的物理内存尚未分配和映射,系统会触发页异常。操作系统会捕获这个异常,为该虚拟地址分配物理内存页帧,并建立虚拟地址与物理地址的映射关系,然后程序才能成功地将值 100 写入ptr[0] 。

5.2系统性能影响

页异常对系统性能有着显著的影响,尤其是硬缺页。由于硬缺页需要从磁盘读取数据到内存,而磁盘 I/O 的速度远远慢于内存访问速度,频繁的硬缺页会导致系统性能大幅下降 。当一个进程频繁地访问大量数据,而这些数据又不在物理内存中时,就会不断地触发硬缺页。比如一个数据库管理系统在处理大量数据查询时,如果内存不足,无法缓存所有需要的数据,就会频繁地从磁盘读取数据,导致大量的硬缺页发生。这不仅会增加磁盘 I/O 的负担,还会使进程的执行速度明显变慢,进而影响整个系统的响应速度和吞吐量。

相比之下,软缺页的影响相对较小,因为它不需要从磁盘读取数据,只是建立映射关系,这个过程相对快速。但如果软缺页过于频繁,也会消耗一定的系统资源,影响系统的整体性能 。例如,在一个多进程共享内存的场景中,如果进程之间频繁地访问共享内存,可能会导致大量的软缺页,因为每个进程在首次访问共享内存时都需要建立映射关系。虽然单个软缺页的处理时间较短,但大量的软缺页累积起来,也会对系统性能产生一定的影响。

相关文章:

Linux内核的页面错误:原因与解决方案

当程序访问虚拟内存中的一个页面时&#xff0c;如果该页面当前不在物理内存中&#xff0c;就会触发一个称为"page fault"&#xff08;页异常&#xff09;的异常。操作系统需要处理这个异常&#xff0c;并将所需页面从磁盘加载到内存中。实现虚存管理的一个关键是page…...

LORA 中的 梯度外积是什么意思; 方差和协方差的实际含义:衡量变量的离散程度和变量间的线性相关性

LORA 中的 梯度外积是什么意思 目录 LORA 中的 梯度外积是什么意思**一、梯度外积的定义****二、示例说明****步骤1:计算单样本梯度****步骤2:计算梯度外积****三、梯度外积的作用****四、总结**方差和协方差的实际含义:衡量变量的离散程度和变量间的线性相关性**一、方差(…...

XSS复现漏洞简单前八关靶场

靶场不需要安装任意环境 链接如下&#xff1a;XSS Game - Learning XSS Made Simple! | Created by PwnFunction 目录 XSS Game 第一关&#xff1a;Ma Spaghet! 第二关&#xff1a;Jefff 第三关&#xff1a;Ugandan Knuckles 第四关&#xff1a;Ricardo Milos 第五关&am…...

3.24-3 接口测试断言

一.postman 断言 1.断言再test中 #状态码是否等于200 tests["Status code is 200"] responseCode.code 200; #断言响应时间小于200ms tests["Response time is less than 200ms"] responseTime < 200; #断言响应体包含内容 tests["Body…...

《鸿蒙携手AI:解锁智慧出行底层逻辑》

在科技飞速发展的当下&#xff0c;智慧出行成为人们对未来交通的美好期许&#xff0c;而鸿蒙系统与人工智能的深度融合&#xff0c;正为这一愿景的实现提供强大助力。从技术原理角度深入剖析&#xff0c;鸿蒙系统究竟如何支撑人工智能在智慧出行场景中的应用呢&#xff1f;这背…...

【AVRCP】探寻AVRCP控制互操作性:连接、命令与设备交互

目录 一、AVCTP连接管理 1.1 AVCTP连接建立 1.2 AVCTP连接释放 二、AV/C命令的操作流程 2.1 AV/C命令交换流程 2.2 AV/C命令类型 三、AVRCP特定命令 四、AVRCP浏览命令 五、OBEX连接管理 5.1 OBEX连接建立 5.2 OBEX连接释放 六、总结 七、参考资料 AVRCP对于实现设…...

Mybatis-Plus知识点详解

Mybatis-plus(简称MP),基于Mybatis的增强工具,保留了Mybatis的所有功能,同时增加了通用的CRUD,条件构造器,分页插件等等实用工具 特性 即拿即用:通过通用Mapper和Service,无需编写XML既可以完成单表CURE操作 Lambda支持:使用Lambda表达式构建查询条件,避免硬编码字段名,提升代…...

紧凑交叉引用表

嗯&#xff0c;用户问的是“compact xref table”&#xff0c;也就是紧凑型交叉引用表。我之前在回答中提到过交叉引用流&#xff08;XRef Stream&#xff09;&#xff0c;但可能需要更详细地解释两者的区别和联系。根据搜索结果中的网页1&#xff0c;传统的Xref表以文本形式存…...

CMake 详解:跨平台构建系统的入门与进阶

目录 一、相关知识点 1. 什么是cmake&#xff0c;为什么使用&#xff1f; 2. 构建过程 二、CMake使用流程 1. 创建 CMakeLists.txt 文件 2. 配置构建目录 3. 运行cmake 4. 运行make编译 一、相关知识点 1. 什么是cmake&#xff0c;为什么使用&#xff1f; CMake 是一个开…...

【架构设计】学习路径

掌握前置知识后&#xff0c;学习架构设计需要从理论认知到实践落地逐步推进。以下是系统化的学习路径&#xff0c;结合具体案例与实操建议&#xff0c;帮助你快速进阶&#xff1a; 一、构建架构思维基础 1. 理解架构设计的核心目标 关键问题驱动设计&#xff1a; 每个架构决策…...

14、Python 枚举与类型注解进阶

Python 枚举与类型注解进阶 文章概述 本文深入探讨Python中枚举&#xff08;Enum&#xff09;与类型注解的高级应用场景。通过剖析Enum类的核心特性、dataclass装饰器的工程实践、静态类型检查工具mypy的集成使用&#xff0c;结合状态机等实际案例&#xff0c;系统性地提升代…...

C语言 【实现电脑关机小游戏】非常好玩

引言 在时间限制内做出正确的回答&#xff0c;时间一到&#xff0c;电脑自动关机&#xff0c;听起来是不是很有意思&#xff0c;下面来看看怎么实现吧。 注意&#xff1a;该游戏只在windows系统下可以玩&#xff0c; 一、游戏原理&#xff1a; 在Windows系统下&#xff0c;通…...

【蓝桥杯速成】| 11.回溯 之 子集问题

题目一&#xff1a;子集 问题描述 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例…...

统计矩的高阶推广:经验还是理论推导?

矩的发展既是经验总结的结果&#xff0c;也是数学理论推导的产物。研究者们在分析数据、描述物理现象的过程中&#xff0c;发现了低阶矩与日常物理概念&#xff08;如质心、惯性&#xff09;之间的紧密联系&#xff0c;而高阶矩的应用往往出现在更复杂的数学体系中&#xff0c;…...

SpringBoot2集成Elasticsearch8(使用spring-boot-starter-data-elasticsearch)

写在前面 使用spring-boot-starter-data-elasticsearch集成Elasticsearch8&#xff1f; What? 官方写的不支持啊&#xff1f;让我们来看下官方给出的版本建议。 官方地址&#xff1a; https://docs.spring.io/spring-data/elasticsearch/reference/elasticsearch/versions.…...

Postgresql源码(142)子查询提升pull_up_sublinks

1 案例 drop table t_fun01; create table t_fun01 (image_id numeric primary key, content_id varchar(50), file_code varchar(20)); create index idx3 on t_fun01(content_id); create index idx4 on t_fun01(file_code); insert into t_fun01 select t.i, t.i%10, t.i%1…...

sonar代码检测研究及平台搭建

为了实现提交代码自动检测代码缺陷&#xff0c;本文介绍了一种将jenkins与gitlab集成的自动检测机制&#xff0c;如需应用于生产级开发流程&#xff0c;可在此基础上进行功能丰富和扩展,本文仅进行了原理性搭建。 一、基础环境准备 与sonar配合使用的jenkins和gitlab基础软件…...

清华大学:DeepSeek从入门到精通系列教程1-9讲(持续更新中)|大礼包免费下载

导 读INTRODUCTION 今天分享由清华大学新闻与传播学院、人工智能学院双聘教授沈阳老师团队倾力打造的《DeepSeek从入门到精通系列教程1-9讲&#xff08;持续更新中&#xff09;》&#xff0c;包含&#xff1a;《DeepSeek&#xff1a;从入门到精通》《DeepSeek如何赋能职场应用》…...

使用Python可视化图结构:从GraphML文件生成节点关系图(lightrag 生成)

引言 在数据可视化领域&#xff0c;图结构&#xff08;Graph&#xff09;常用于展示实体间的复杂关系。例如&#xff0c;文学分析中的角色关系、社交网络中的用户互动等。本文将通过一个实际案例&#xff0c;演示如何使用 NetworkX 和 Matplotlib 从 GraphML 文件生成节点关系…...

排序复习_代码纯享

头文件 #pragma once #include<iostream> #include<vector> #include<utility> using std::vector; using std::cout; using std::cin; using std::endl; using std::swap;//插入排序 //1、直接插入排序&#xff08;稳定&#xff09; void InsertSort(vecto…...

Docker Hub Mirror 终极解决方案——0成本,超高速!

CNB Docker Mirror (cdm) CNB Docker Mirror 是一个基于 CNB 的 Docker 镜像加速工具&#xff0c;提供本地镜像加速功能。 功能特性 镜像加速&#xff1a;在本地启动连接到 CNB 环境的 Docker 镜像加速服务&#xff0c;然后通过配置 Docker 客户端实现镜像加速下载自动重连&…...

2000-2019年各省地方财政车船税数据

2000-2019年各省地方财政车船税数据 1、时间&#xff1a;2000-2019年 2、来源&#xff1a;国家统计局、统计年鉴 3、指标&#xff1a;行政区划代码、地区、年份、地方财政车船税 4、范围&#xff1a;31省 5、指标说明&#xff1a;车船税作为地方财政的重要组成部分&#x…...

c#处理算数溢出的情况

在C#中&#xff0c;算术运算的溢出处理可以通过 checked 和 unchecked 关键字控制&#xff0c;默认行为是 静默截断&#xff08;unchecked模式&#xff09;&#xff0c;但可以通过配置或代码块显式调整。以下是详细说明&#xff1a; 1. 默认行为&#xff08;unchecked模式&…...

Java「Deque」 方法详解:从入门到实战

Java Deque 各种方法解析&#xff1a;从入门到实战 在 Java 编程中&#xff0c;Deque&#xff08;双端队列&#xff09;是一个功能强大的数据结构&#xff0c;允许开发者从队列的两端高效地添加、删除和检查元素。作为 java.util 包中的一部分&#xff0c;Deque 接口继承自 Qu…...

简记_FPGA 硬件最小系统设计

一、FPGA板级设计的五要素 1.1、电源电路 核心电压&#xff1a;一般为固定值 IO电压&#xff1a;FPGA的IO分为多个bank&#xff0c;同一个bank的不同IO引脚电压相同&#xff0c;不同bank的电压可以不同 辅助电压&#xff1a;除了核心电压和IO电压&#xff0c;FPGA工作所需的…...

C++题目

1、内存管理 1.内存模型 栈:在执行函数时&#xff0c;函数内局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结束时这些存储单元自动被释放。 堆&#xff1a;就是那些由new分配的内存块&#xff0c;其释放由程序员控制&#xff08;一个new对应一个delete&#xff09…...

Vulhub-jangow-01-1.0.1通关攻略

第0步&#xff1a; 打开靶机&#xff0c;按下shift&#xff0c;出现下图界面 在此页面按下e键&#xff0c;进入如下界面&#xff0c; 将ro 替换为 rw signie init/bin/bash 替换完毕后&#xff0c;按下Ctrl键X键&#xff0c;进入如下页面 ip a查看网卡信息 编辑配置文件网卡信…...

入剖析 Android Compose 框架的关键帧动画(keyframes、Animatable)(二十三)

深入剖析 Android Compose 框架的关键帧动画&#xff08;keyframes、Animatable&#xff09; 引言 在当今的 Android 应用开发领域&#xff0c;用户体验已成为衡量一款应用成功与否的关键因素之一。而动画作为提升用户体验的重要手段&#xff0c;能够为应用增添生动性和交互性…...

java中的枚举类型和c,c++的有区别吗?c,c++的枚举,结构体,联合体,三种数据有什么区别和联系

Java 枚举类型与 C、C 枚举类型的区别 1. 类型安全 Java&#xff1a;Java 的枚举类型是类型安全的。枚举常量是枚举类型的实例&#xff0c;编译器会严格检查传递的参数是否为该枚举类型的有效常量。例如&#xff1a; java Apply enum Color { RED, GREEN, BLUE } // 编译器会检…...

详解Redis的持久化与数据可靠性

Redis持久化与数据可靠性详解&#xff08;结合实例&#xff09; Redis作为内存数据库&#xff0c;持久化是保证数据不丢失的核心机制。它通过将内存数据保存到磁盘&#xff0c;确保服务器重启后能恢复数据。Redis提供RDB、AOF和混合持久化三种方式&#xff0c;下面通过实例和操…...

1、mysql基础篇--概述

关系型数据库&#xff08;RDBMS&#xff09; 概念特点&#xff1a;数据模型&#xff1a; 概念 建立在关系模型基础上&#xff0c;有多张表相互连接的二维表组成的数据库 特点&#xff1a; 1、使用表存储&#xff0c;格式统一&#xff0c;便于维护 2、使用sql语言操作&#…...

【Tiny RDM】Redis客户端工具

Tiny RDM Tiny RDM是一款现代化、轻量级、跨平台的Redis客户端&#xff0c;支持Mac、Windows和Linux&#xff0c;目前在Github上已有10kStar。 Github 项目地址&#xff1a; https://github.com/tiny-craft/tiny-rdm 功能特性 极度轻量&#xff0c;基于Webview2&#xff0c…...

常见框架漏洞攻略-Shiro篇

漏洞名称 Shiro rememberMe反序列化漏洞 漏洞简介 Apache Shiro是⼀个强⼤易⽤的Java安全框架&#xff0c;提供了认证、授权、加密和会话管理等功能。Shiro框架直观、易⽤&#xff0c;同时也能提供健壮的安全性。 漏洞原理 在Shiro框架下&#xff0c;⽤户登陆成功后会⽣成…...

常见框架漏洞之一:Thinkphp5x

ThinkPHP是为了简化企业级应⽤开发和敏捷WEB应⽤开发⽽诞⽣的&#xff0c;是⼀个快速、兼容⽽且简单的轻量级国产PHP开发框架&#xff0c;诞⽣于2006年初&#xff0c;原名FCS&#xff0c;2007年元旦正式更名为 ThinkPHP&#xff0c;遵循Apache2开源协议发布&#xff0c;从Stru…...

MORL4PDEs:基于多目标优化与强化学习的数据驱动偏微分方程发现

摘要&#xff1a;本文提出了一种结合多目标优化与强化学习的数据驱动方法MORL4PDEs&#xff0c;用于从复杂系统观测数据中发现简洁的偏微分方程&#xff08;PDE&#xff09;。该方法无需预定义候选函数库&#xff0c;通过神经网络代理生成符号表达式&#xff0c;结合遗传算法优…...

UniApp和微信小程序中v-switch夜间模式动画开关

UniApp兼容版 <template><view><view class"main-container" :style"{ backgroundColor: value ? #45e3f9 : #20114c,transform:scale(${size})}" tap"onClick"><view class"content" :style"{ left: val…...

六十天Linux从0到项目搭建第四天(通配符命令、其他命令、压缩解压工具、shell的感性理解、linux权限解析)

通配符&#xff08;Wildcard&#xff09; 是 Shell 提供的特殊字符&#xff0c;用于 匹配文件名或路径名&#xff0c;可以代替一个或多个字符&#xff0c;使得命令能批量操作文件&#xff0c;而无需手动输入每个文件名。 典型用法 * 匹配任意字符 *.txt → 匹配所有 .txt 文…...

RAG优化:python从零实现自适应检索增强Adaptive Retrieval

开篇:当RAG遇上“自适应大脑”,检索从此不再“一根筋”!🧠 想象一下,你的AI助手是个超级聪明的“学霸”,但有时候却像个“一根筋”的机器人——无论你问它什么,它都用同一种方式去回答。问它“什么是XAI?”它给你一堆定义;问它“AI发展太快了吗?”它还是给你一堆定…...

C语言实现的冰墩墩

在windows系统下&#xff0c;vs 2022编译。 其中#include <graphics.h>需要自己下载安装。 环境配置没什么难度&#xff0c;直接上demo。 代码如下&#xff1a; #include <graphics.h> #include <conio.h> #include <math.h> #define PI acos(-1.0…...

【构建CV图像识别系统】从传统方法到深度学习

目录 1. 图像的基本概念1.1 像素与色彩1.2 过滤与卷积 2. 图像分类与检测3. 图像特征的提取3.1 全局特征3.2 局部特征3.2.1 边缘&#xff08;Edge&#xff09;3.2.2 角点&#xff08;Corner&#xff09;3.2.3 SIFT 特征 4. 传统方法与深度学习在图像识别中的应用4.1 基于传统方…...

在Centos 7环境下安装MySQL

前言&#xff1a;在安装与卸载MySQL时&#xff0c;用户需切换为root&#xff0c;这样安装之后&#xff0c;普通用户也能够使用。 Tips:我们在刚开始学习时&#xff0c;尽量全部使用root进行&#xff0c;适应mysql语句&#xff0c;后面学了用户管理&#xff0c;就可以考虑新建普…...

【机器学习基础 4】 Pandas库

一、Pandas库简介 Pandas 是一个开源的 Python 数据分析库&#xff0c;主要用于数据清洗、处理、探索与分析。其核心数据结构是 Series&#xff08;一维数据&#xff09;和 DataFrame&#xff08;二维表格数据&#xff09;&#xff0c;可以让我们高效地操作结构化数据。Pandas …...

干部监督预警系统的定义与功能

一、干部监督预警系统是什么&#xff1f; 干部监督预警系统是通过整合多源数据&#xff08;如干部档案、履职表现、廉政记录、舆情反馈等&#xff09;&#xff0c;利用大数据分析、人工智能等技术&#xff0c;对干部行为进行实时监测、风险评估和分级预警的数字化管理工具。 二…...

可视化图解算法:链表的奇偶重排(排序链表)

1. 题目 描述 给定一个单链表&#xff0c;请设定一个函数&#xff0c;将链表的奇数位节点和偶数位节点分别放在一起&#xff0c;重排后输出。 注意是节点的编号而非节点的数值。 数据范围&#xff1a;节点数量满足 0≤n≤105&#xff0c;节点中的值都满足 0≤val≤10000 要…...

获取小红书笔记详情接口的详细指南

一、引言 小红书作为一个集社交、购物、分享于一体的综合性平台&#xff0c;拥有海量的用户和丰富的笔记内容。小红书笔记详情API接口为开发者提供了一种高效获取笔记详细信息的方法&#xff0c;包括笔记的标题、正文、图片、视频、标签、点赞数、评论数等。这些数据可以帮助开…...

麒麟系统运维指令

麒麟系统运维指令 麒麟系统运维指令1、 查看系统版本2、查看系统信息3、用户与权限管理4. 网络相关命令5. 包管理6. 文件操作7. 进程管理 麒麟系统运维指令 1、 查看系统版本 目的指令查看操作系统版本信息cat /etc/os-release查看操作系统版本信息hostnamectl查看内核版本un…...

pyqt SQL Server 数据库查询

一、概述 本项目旨在开发一个基于 Python 和 PyQt6 的数据库查询工具&#xff0c;该工具能够连接到 SQL Server 数据库&#xff0c;显示数据库中的表名&#xff0c;支持用户输入 SQL 查询语句进行数据查询&#xff0c;并将查询结果展示在表格中。同时&#xff0c;为了提升用户…...

抓包软件【Fiddler】

我叫补三补四&#xff0c;很高兴见到大家&#xff0c;欢迎一起学习交流和进步 今天来讲一讲Fiddler 什么是Fidder Fiddler是一款非常流行并且实用的HTTP抓包工具&#xff0c;原理是在电脑上开启一个HTTP代理服务器&#xff0c;然后转发所有的HTTP请求和响应。是用C#开发的工具…...

SpringCould微服务架构之Docker(1)

项目中微服务比较多的时候&#xff0c;一个一个手动的部署太麻烦了&#xff0c;所以就需要用到Docker。 项目部署中的问题&#xff1a; Docker是一种快速交付应用、运行应用的技术。...

计算图(Computation Graph)

在强化学习中&#xff0c;TensorFlow的计算图&#xff08;Computation Graph&#xff09;是用于描述模型结构和训练流程的核心机制。 1. 计算图的基本概念 定义&#xff1a;计算图是TensorFlow中表示数学运算和数据流动的有向图。图中的节点&#xff08;Nodes&#xff09;代表…...