当前位置: 首页 > 科技观察

为什么进程使用的内存大小(虚拟存储)可以大于物理内存?_0

时间:2023-03-12 16:32:39 科技观察

为什么一个进程需要的存储空间大小可以超过物理内存的大小?操作系统如何管理机器上运行的多个进程的内存?进程间共享存储是如何工作的?通过top命令查看的VIRT和RES指标有什么区别?所有这些问题都与虚拟存储的概念有关。虚拟存储是计算机系统中的一个重要概念。可以说理解虚拟存储是内存管理的关键。进程是一个正在执行的程序。进程是一个正在执行的程序实例。同一个程序可以有多个执行实例,对应多个进程。系统上同时运行的多个进程共享机器的CPU和存储资源。每个进程(线程)都有独立的逻辑执行流程,给人一种独占使用处理器的错觉;同时,每个进程都有一个私有地址空间,这提供了另一种错觉,即它正在独占使用内存。虚拟内存是一个抽象层,为每个进程提供一个一致的、私有的地址空间,正是通过这一层关键的抽象,才能实现计算机科学“进程”中最深刻、最成功的概念。虚拟存储简化了内存管理,链接器独立于内存中的物理地址构建可执行程序。虚拟存储空间分为两部分:内核空间和用户空间。并不是所有的虚拟地址空间都会分配物理内存,只有实际使用的才会分配物理内存。这也体现在通过top命令查看到的进程的VIRT和RES这两个指标在数值上的区别。物理内存可以看作是一个巨大的字节数组。每个元素占用一个字节,有自己独立的编号(即地址)。这个地址称为物理地址(PhysicalAddress,PA)。物理内存资源由系统中运行的所有进程共享,就像CPU资源由系统中运行的所有进程/线程共享一样。应用程序中使用的地址称为虚拟地址(VirtualAddress,VA)。每个进程都有独立的私有虚拟地址空间,进程间的虚拟存储是隔离的。如果没有特殊手段,进程A无法访问进程B。的虚拟地址。当虚拟地址访问真实的物理内存时,需要将其转换为物理地址。从虚拟地址到物理地址的转换过程称为地址转换。CPU硬件和操作系统协同完成地址转换。CPU芯片上的内存管理单元(MMU)查询内存中的页表动态完成地址转换,页表的内容由操作系统管理和维护。这项工作由底层系统默默完成,对应用透明。进程X和进程Y中的同一个虚拟地址会映射到不同的物理地址,多个进程也可以通过共享内存技术(SharedMemory)映射到同一个物理内存。32位系统的虚拟地址范围是[0-2^32],共4GByte,这个地址的集合称为地址空间,64位系统的地址空间更大,但不是2^64。虽然一个进程有这么大的虚拟地址空间,但它实际上通常不会占用那么多的存储空间。系统以页面为单位管理存储。在32位系统上,PageSize通常为4K字节,在64位系统上,PageSize通常为8K字节。物理存储(memory)以PageSize为单位划分物理页(PhysicalPage,PP),[0-4096)的连续空间被划分为第一页,[4096,8192)的连续空间被划分进入第二页,以此类推,PageSize大小的物理页也称为页框。虚拟存储空间也按照相同的PageSize划分为虚拟页(VirtualPage,VP)。虚拟页面分为已分配和未分配状态,已分配页面又细分为未缓存和缓存状态。.进程所有分配的虚拟存储页构成了进程的有效虚拟存储空间。访问未分配的虚拟存储空间是非法的,会触发异常(如常见的段错误)通过调用malloc/new/mmap等编程接口分配虚拟页面存储页面,物理内存页面的分配由系统。分页后,一个虚拟地址分为2部分:页号+页偏移量。操作系统在内存中为每个进程维护一个页表(PageTable,PT),地址转换硬件通过查询存储在物理内存中的页表找到对应的物理地址。每个虚拟页对应一个页表项(PageTableEntry,PTE)。页表被看作是一个页表项的数组,页码是数组的下标(索引)。每个页表项包含虚拟页是否已经分配,??访问未分配页是非法的。如果已分配,则需要区分是否已缓存(对应物理内存页)和未缓存。如果被缓存(pagehit),页表项包含虚拟页对应的物理地址;如果没有被缓存(pagemiss),页表项中包含虚拟页对应的虚拟页在磁盘上的起始位置,系统在物理内存中选择一个victimpage,将虚拟页从磁盘复制到内存中。这个过程称为交换。受害者页面的内容需要从内存复制到磁盘虚拟页面。这个过程称为换出。缓存的新页面需要从磁盘复制到内存中。这个过程称为换入,因为涉及到磁盘操作。在此过程中,成本非常高。这个代价叫做生命损失惩罚,但是根据本地稳定性原则,程序在经过初始启动阶段后,通常只作用于一小部分活动页面,这样可以保证惩罚代价虽然看起来高,事实上,它仍然很好用。通过分页,我们的程序可以工作在超出物理内存容量的虚拟存储空间中,多个进程可以有效共享稀缺的内存资源。了解这个过程对于掌握内存管理和优化技术至关重要。Linux进程虚拟存储空间一个linux进程的虚拟存储空间从下到上分为:代码段、全局变量段、堆、共享存储区、栈、内核段。程序启动时,加载器会将编译后的可执行程序文件中的.text段复制到代码段,将.data复制到全局变量段。每个函数(inline除外)在编译后都会占用存储空间并进入text段,在程序执行过程中,会不断从代码段加载指令。堆向上增长,brk指向堆顶,通过malloc/new等编程接口从堆中分配内存,动态分配的内存块需要手动释放。栈向下增长,局部变量位于栈中,函数调用的参数也是通过栈传递的。函数调用链所需的内存由堆栈提供。堆栈自动展开。每个线程都有一个独立的堆栈。空间有限(4/8M,可调),局部变量不能太大,递归太深有爆栈风险。堆内存和栈内存本质上都是存储区,分别位于进程的不同部分。只有用法上的区别,物理介质上没有区别。它们将通过地址被翻译成物理内存。栈和堆之间是共享存储区,通过共享存储编程接口shmget创建的存储段就位于此,标准库的代码也是进程间共享的,可以节省内存。内核段存储空间是用户态代码无法访问的,但是用户代码调用系统调用或触发异常,进程落入内核,会执行内核段的代码+访问内核态数据。注意:代码段不是从0开始,而是从一个特定的地址开始,0x8048000(32位系统),0x400000(64位系统)段页内存管理早期的计算机系统,采用段页内存管理,都是分页,有也是段,里面包含页,但是linux系统简化了这种管理方式。进程的虚拟地址空间只有1段,相当于变相丢弃段。如果用4K作为一个页,那么32位系统有4G(2^32)的虚拟内存空间,因为页表是进程私有的,所以一个进程的虚拟地址空间包含1M页,而需要1M页表项(64位以上),而且系统中往往有上百个进程,内存占用太大,所以,其实操作系统巧妙地使用了多级页表来解决这个问题.多级页表是一种常用的压缩页表内存占用的方式。引入多级页表后,顶级页表的一个表项不再代表4K/8K的地址范围。它要大得多,只有一级表条目代表它。分配了范围,会存储其对应的二级页表,这样就大大节省了内存空间,而且由于进程实际分配的虚拟页只是整个地址空间的一小部分,我们可以使用一个小的存储成本,支持大地址范围。每转换一个地址,都需要查询内存页表。如果页表项不在缓存中,开销会很大。为了加快内地翻译速度,引入了TLB(translationlook-asidecache)。TLB是MMU中的一个小PTE。MMU进行地址转换时,MMU先从TLB中查找PTE,如果失败,再到PageTable中查找。由于局部性,TLB大大加快了地址转换过程。软硬件协同地址转换需要操作系统和MMU(TLB)的配合来完成。操作系统为每个进程维护页表。MMU首先检查TLB缓存,如果没有找到,再检查页表。进程被调度后,页表基地址寄存器会修改指向新运行进程的页表地址,实际上是pagefaultpagefault:其实不是真正的错误,没有名字看起来那么严重,当指令是执行时,页面错误(pagefault)会触发异常,操作系统的异常处理程序会妥善处理异常,并再次发出指令,此时,由于请求的地址已经加载到内存中,指令正常进行。Majorpagefault:也叫hardpagefault,majorpagefault主要是swap机制引入的,因为涉及到磁盘io,主要是软件完成的,所以速度比较慢,可以通过swapon/swapoff切换分区swappinessminorpagefault:也叫软页错误,指需要访问的代码/数据已经在物理内存页中,但是页表中的映射还没有已经建立,MMU需要建立物理内存和虚拟地址空间的映射关系。进程调用malloc获取虚拟空间地址时,第一次访问该地址时会发生软页错误。多个进程访问同一共享内存中的数据。当某些进程还没有建立映射关系时,访问时也会出现软页错误。可以使用命令:ps-eomin_flt,maj_flt,cmd查看系统中各个进程的pagefault状态。当一个程序通过malloc分配1G字节的内存时,系统不会为进程分配1G的物理内存,而只会分配1G的虚拟内存页。当以后真正访问一个页面时,系统会为其分配物理内存。内存,如果系统的物理内存用完了,它会选择一个内存页换出(将页的内容写入磁盘页)。通过这种策略,我们可以获得比物理内存更大的存储空间,提高内存容量。资源利用还使得在进程之间共享存储成为可能。