Skip to the content.

Memory 1: Address Translation and Virtual Memory

Virtualizing Resources

多路复用:多个任务共用同一个信道或者同一个资源。

物理现实:不同的进程/线程共享相同的硬件资源

为什么要担心内存共享?

首先,需要先彻底了解一下一段程序是如何被转化为进程的:

Recall: Four Fundamental OS Concepts

线程:执行上下文

地址空间(带或不带转换)

进程:运行程序的一个实例

双模式操作 / 保护:即内核态或用户态

Address Space, Process Virtual Address Space

image-20240401100644965

定义:可访问地址的集合及其关联的状态 处理器读取或写入地址时会发生什么?

Recall: Process Address Space: typical structure

image-20240401100940443

这是处理器上存储的关于当前线程的信息,右侧是一个线程自己的地址空间:

Recall: Single and Multithreaded Processes

image-20240401101929939

线程封装并发性

地址空间封装保护:

为什么一个地址空间需要多个线程?

Important Aspects of Memory Multiplexing

保护:

翻译:

受控的重叠(即不同进程可以在用户希望的情况下共享相同的内存,可能是用于通信):

Alternative View: Interposing on Process Behavior

操作系统对进程的I/O操作进行拦截

操作系统对进程的CPU使用进行拦截

问题:操作系统如何对进程的内存访问进行拦截?

Recall:program code->process

image-20240401104829055

compile、link、load、run

编译(compile)、链接(link)、加载(load)、运行(run):

同时执行程序的两个副本本质上是改变了link和load操作时的地址偏移量,因此程序可以被绑定到另一块物理内存上,如下图所示,可以对比上图。即我们可以改变link和load策略,从而让compile生成的汇编语言被link到物理内存中的一个特定的加载点。

image-20240401105331478

From program code to Process

程序准备执行涉及以下组件:

地址可以下图中路径的任何位置绑定到最终值,这取决于硬件支持,也取决于操作系统。

动态库可以延迟到run阶段的时候再link:

image-20240401110506425

Virtual Address-抛砖引玉(Seg、Multi-Seg(VarLength))

下面说明了运行程序地址转换的发展历程,从没有地址转换到有地址转换,从整个段->多个段,最后再到以page为单位的虚拟地址转换。这是一个translation粒度不断变小的过程。

Base and bound(每个进程占用连续内存区域,单个段)

在Address Translator出现前,是在link-load的时候,采用relocating-loader的方式,将程序加载到一块内存区域中。如果没有额外的保护措施(如base and bound这些),那么程序之间可能会出现越界访问,相互影响从而导致程序崩溃,因此,MMU诞生了。

image-20240403153455438 因此,内存有两种视图:

通过翻译,实现保护变得容易多了!

通过翻译,每个程序都可以链接/加载到用户地址空间的相同区域,即每个程序都认为自己拥有所有的内存地址空间(虚拟的),通过MMU转换,将虚拟地址空间转化为实际的内存地址空间;每当切换进程时,MMU中的翻译机制就会随之改变。

但是仅仅用base and bound和简单地加法地址重定位机制,会导致内存碎片的问题,这种方式也不支持内存共享,也不能良好的支持栈和堆的动态增长。

More Flexible program Segmentation(多段机制)

每个进程可以被拆分为多个段,每个段仍然是一个连续的内存块,从而减缓内存空间碎片化问题。因此我们需要为当前进 程的每个segment都维护一个MMU机制的转换,如下例所示:

image-20240403164435769

Example of segment translation(16 bit address)

image-20240403164507254

根据上例,我们可以得到如下结论:

每次指令提取、加载或存储时进行翻译

在有效范围之外寻址有何影响:这就是栈(和堆?)被允许增长的原因,

分段表中需要保护模式:例如,代码段将是只读的;数据和栈将是读写的(允许存储)

在上下文切换时需要保存/恢复什么?

What if not all segments fit in memory?

image-20240403165522938

极端的上下文切换形式:Swapping整个Segment

有什么可能是一个理想的替代方案?

Problems with Segmentation

必须将可变大小的块放入物理内存中;可能需要多次移动进程以适应所有内容;到磁盘的交换选项有限,只能移动整个Segment大小,磁盘I/O是非常恐怖的。

碎片化:浪费空间

Virtual Address-Fixed Size Chunk(serveral pages)

Paging: Physical Memory in Fixed Size Chunks

image-20240403174430360

分页:将物理内存划分为固定大小的块,这解决了分段带来的碎片化问题吗?

将物理内存划分为固定大小的块(“页面”)

每个物理内存块都是等价的

页面应该和以前的段一样大吗?

进程通信方式: Sharing Memory

下图就是进程间通过共享内存方式进行通信的一个简单示例。

image-20240403174445972

Where is page sharing used ?

内核区域的共享: 每个进程的内核区域具有相同的页表条目,但在用户级别无法访问。但是在用户到内核的切换时,内核代码可以访问它以及属于该用户的区域。这种共享可用于访问其他用户进程所需的内核功能。

运行相同二进制文件的不同进程: 当多个进程运行相同的二进制文件时,它们可以共享code segments/pages,因为这些segments是只执行的,不需要复制。

用户级系统库: 这些库通常是只执行的,因为它们不需要被修改,因此可以被多个进程共享。

不同进程之间的共享内存段: 可以在不同进程之间共享内存段,允许它们直接共享对象。为了实现这种共享,需要将内存页映射到各个进程的相同的实际物理内存地址空间位置。这种共享形式类似于单个进程的内部的多个线程之间的共享。