操作系统内存管理之虚拟内存

9.1 背景

虚拟地址空间:进程在内存中存放的逻辑视图。如图所示。

虚拟内存:是一种内存管理技术,它会使程序自己认为自己拥有一块很大且连续的内存,然而,这个程序在内存中不是连续的,并且有些还会在磁盘上,在需要时进行数据交换 。

允许随着动态内存分配,堆向上生长;允许随着子程序的不断调用,栈向下生长。只有在堆和栈生长时,才需要实际的物理页。包括空白的虚拟地址空间成为稀地址空间。其优点是:随着程序的执行,栈或堆段的生长或需要载入动态链接库(或共享对象)时,这些空白可以填充。

虚拟内存也允许文件和内存通过共享页而为两个或多个进程所共享。如图所示:

9.2 按需调页              在需要时才调入相应的页(可视为懒惰交换)

区分:交换程序是对整个进程操作(因此这里是懒惰交换),调页程序只是对进程的单个页进行操作,按需调页使用的是调页程序。

有效位和无效位:有效:表示相关的页既合法也在内存中;无效:表示该页不在进程的逻辑地址空间内(有的博客说此时应显示NULL)或有效但是在磁盘中。如图所示:

扫描二维码关注公众号,回复: 2563426 查看本文章

Q:当进程试图访问未调入内存(在磁盘上)的页,将会如何?

A:对这些标记为无效的页,将产生页错误陷阱。由于操作系统未能将所需的页调入内存而产生。处理流程:

(1)检查进程的内部页表(通常与PCB一起保存),以确定该引用是合法的还是非法的地址访问;

(2)若引用非法,则终止进程。若引用有效但尚未调入页面,则现在应调入;

(3)找到一个空闲帧(如从空闲链表中选一个);

(4)调度一个磁盘操作,以便将所需要的页调入刚分配的帧;

(5)当磁盘读操作完成后,修改进程的内部表和页表,以表示该页已在内存中;

(6)重新开始因陷阱而中断的指令,进程现在可以访问所需页。如图所示:

极端情况:所有的页都不在内存中,成为纯粹按需调页:只有在需要时才将页调入内存

原理:局部性:

空间局部性:电脑中某一块内存在使用时,在不久的将来会使用内存地址相近的一块内存区域。

 时间局部性:电脑中某一条指令在使用完之后,在不久的将来有很大可能再次使用它。

因此,有的程序的单个指令可能访问多个页的内存,从而一个指令可能产生多个页错误。不过由于局部性,这种情况很少见。

硬件支持:(1)页表:该表能够通过有效-无效位或保护位的特定值,将条目设为无效;

                  (2)次级存储器:保存不在内存中的页,通常为快速磁盘,通常称为交换设备,用于交换的这部分磁盘通常称为交换空间

关键:能够在页错误后重新执行指令,出现页错误时,保存中断进程的状态(寄存器、条件代码、指令计数器),必须能够按完全相同的位置和地址重新开始执行进程。

性能分析:计算按需调页内存的有效访问时间:

                  设p为页错误的概率(0<=p<=1),则有效访问时间为:

                  有效访问时间 = (1-p)*ma + p*页错误时间            ma:内存访问时间

9.3 写时复制

fork():为子进程创建一个父进程地址空间的副本,复制属于父进程的页。但由于许多子进程在创建之后立即执行exec(),所以父进程地址空间的复制可能没有很必要。

exec():

fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程不共享这些存储空间。linux将复制父进程的地址空间内容给子进程,因此,子进程由了独立的地址空间。),也就是这两个进程做完全相同的事。

在fork后的子进程中使用exec函数族,可以装入和运行其它程序(子进程替换原有进程,和父进程做不同的事)。

exec函数族可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完后,原调用进程的内容除了进程号外,其它全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。

写时复制技术:允许父进程与子进程开始时共享同一页面,这些页面被标记为写时复制页,即如果任何一个进程需要对页进行写操作,就创建一个共享页的副本。如图所示:

  假设子进程试图修改含有部分栈的页,且操作系统可识别出该页为写时复制页(注意:只有可能修改的页才需要被标记为写时复制,不能修改的页(即包含可执行代码的页)可以为父进程与子进程所共享),则  OS创建一个该页的副本,将其映射到紫禁城的地址空间内,这样,子进程会修改自己的页,而非父进程的页。

Q:如何选择一个空闲页被分配用于写时复制?

A:OS提供了空闲缓冲池。这些空闲页在进程栈或堆必须扩展时用于分配,或用于管理写时复制页。OS通常采用按需填零的技术分配页。即这些页在分配之前先填零,清除以前内容。

扩展:vfork()?(fork()的变种,虚拟内存fork)

           vfork()会将父进程挂起,子进程使用父进程的地址空间。vfork不采用写时复制,若子进程修改父进程地址空间的任何页,则这些修改后的页在父进程重启后可见。

fork()与vfork()的区别:https://blog.csdn.net/ValDC_Morning/article/details/77414826

  

猜你喜欢

转载自www.cnblogs.com/dzy521/p/9426146.html