第九章 虚拟内存

虚拟内存

一个系统中的进程是与其它进程共享CPU和主存资源的。
虚拟内存提供了三个重要的能力:

  1. 它将主存看成是一个存贮在硬盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,它高效使用了主存。
  2. 它为每个进程提供了一致的地址空间,从而简化了内存管理。
  3. 它保护了每个进程的地址空间不被其它进程破坏。

    虚拟内存作为缓存的工具

    概念上而言,虚拟内存被组织为一个由存放在磁盘上的N个连续的字节大小的单元组成的数组。每个字节都有唯一的虚拟地址,作为数组的索引。磁盘上数组的内容被缓存在主存中。和存储器层次结构中的其它缓存一样,磁盘(较低层)上的数据被分割成块,这些块作为磁盘和主存(较高层)之间的传送单元。VM系统通过将虚拟内存分割为称为虚拟页(Virtual Page, VP)的大小固定的块来处理这个问题。类似的,物理内存被分割为物理页,也被称为页帧。
    在任意时刻,虚拟页面的集合都分为三个不相交的子集:
  4. 未分配的:VM系统还未分配(或者创建)的页。未分配的块没有任何数据和它们关联,因此也不用占据任何磁盘空间。
  5. 缓存的:当前已缓存在物理内存中的已分配页。
  6. 未缓存的:未缓存在当前物理内存中的已分配页。

    页表

    页表

    虚拟内存作为内存管理的工具

    操作系统为每个进程提供一个独立的页表,VM简化了链接和加载,代码和数据共享,以及应用程序的存储器分配。
  7. 简化链接。独立的地址空间允许每个进程的内存映像使用相同的基本格式,而不管代码和数据实际存放在物理内存的何处。
  8. 简化加载要把目标文件中。text和。data节加载到一个新创建的进程中,Linux加载器为代码和数据段分配虚拟页,把它们标记为无效的(即未被缓存的),将页表条目指向目标文件中适当的位置。加载器从不从磁盘到内存实际复制任何数据。在每个页被初次引用时,要么是CPU取址时引用的,要么是一条正在执行的指令引用一个内存位置时引用的,虚拟内存会按照需要自动的地调入数据页。
  9. 简化共享
  10. 简化内存分配

    Linux虚拟内存系统

    Linux虚拟内存区域

    Linux为每个进程维持一个单独的虚拟地址空间:内核虚拟存储器和进程虚拟存储器。
    下图强调记录一个进程中虚拟内存区域的内核数据结构。内核为系统中的每个进程维护一个单独的任务结构(源代码中的task_struct)。任务结构中的元素包含或者指向内核运行该进程所需要的所有信息(例如,PID、指向用户栈的指针、可执行目标文件的名子,以及程序计数器)。

任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。我们感兴趣的两个字段时pgd和mmap,其中pgd指向第一级页表(页全局目录的基址),而mmap指向一个vm_area_structs(区域结构)的链表,其中每个vm_area_structs都描述了当前虚拟地址空间的一个区域。当内核运行这个进程时,就将pdg存放在CR3控制寄存器中。

  • vm_start:指向这个区域起始处。
  • vm_end:指向这个区域的结束处。
  • vm_prot:描述这个区域内包含的所有页的读写许可权限。
  • vm_flags:描述这个区域内的页面是与其它进程共享的,还是这个进程私有的(还描述了一些其它信息)。
  • vm_next:指向链表中下一个区域结构。

    存储器映射

    定义:
    Linux 通过将一个虚拟存储器区域与一个磁盘上的对象关联,以初始化这个虚拟存储器区域的内容。虚拟存储器区域可以映射到以下两种类型文件:
  1. Unix文件系统中的普通文件:一个区域可以映射到一个普通磁盘文件的连续部分。
    例如,一个可执行文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始化内容。仅仅是初始化,虚拟页面此时还并未进入物理存储器,直到CPU第一次引用这个页面。
  2. 匿名文件
    匿名文件由内核创建,包含的全是二进制零。CPU第一次引用这样区域(匿名文件)的虚拟页面时,将存储器中牺牲页面全部用二进制零覆盖。并将虚拟页面标记为驻留在存储器中。
    注意在磁盘和存储器之间并没有实际的数据传送。又叫请求二进制零的页(demand-zero page)。

    再看共享对象

私有对象的写时复制

再看fork函数

当fork函数被当前进程调用时,内核为新进程创建各种数据结构,并分配给它唯一的PID。为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

当fork在新进程中返回时,新进程现在的虚拟内存刚好和调用fork时存在的虚拟内存相同。当这两个进程中的任一个后来进行写操作时,写时复制就会创建新页面,因此,也就为每个进程保持了私有地址空间的抽象概念。

再看execve函数

假设调用了execve("a.out",NULL,NULL);execve函数在当前进程中加载并运行包含在可执行目标文件a.out中的程序,用a.out代替了当前程序。

  1. 删除已存在的用户区删除当前进程虚拟地址的用户部分中已存在的区域结构。
  2. 映射私有区域为新程序的文本,数据,bss和栈区域创建新的区域结构。所有新的区域结构都是私有的,写时拷贝的。 文本和数据区域被映射到a.out文件中的文件和数据区。bss区域是请求二进制零,映射到匿名文件
  3. 映射共享区域
  4. 设置PC。

    使用mmap函数的用户级内存映射

    Linux进程可以使用mmap函数来创建新的虚拟内存区域,并将对象映射到这些区域中。
    1
    2
    3
    4
    5
    6
    7
    #include <unistd.h>
    #include <sys/mman.h>

    void *mmap(void *start, size_t length, int port, int flags,
    int fd, off_t offset);

    返回:若成功则为指向映射区域的指针,若出错则为-1

munmap函数删除虚拟存储器的区域

1
2
3
4
5
6
#include <unistd.h>
#include <sys/mman.h>

void *munmap(void *start,size_t length);

返回:若成功则为0,若出错则为-1
-------------本文结束感谢您的阅读-------------
+ +