CSAPP学习笔记第9章:

每一个进程需要属于自己的程序代码和变量,但是如果进程太多那么需要的内存也太多,可能导致一些进程没有办法运行。当一个程序没有空间可用的时候,它可能会写到别的进程的存储器里面,这样会带来很难理解的错误!

为了方便管理存储器(内存),操作系统提供了这样一个抽象:虚拟存储器,它将主存作为磁盘的缓存,并且为每一个单独的进程都提供了统一的虚拟地址.保护了存储器不会受到破坏.

[1]虚拟地址和物理地址,每一个程序里面的寻址都是虚拟地址,我们需要将这个虚拟的地址转换为物理存储器的地址,这样才能够找到实际的内容。

[2]从概念上来讲,虚拟存储器(VM)被存放在磁盘上面大小为N的chunk里面,VM将虚拟存储器分割成页,同时也将物理存储器也分割成页,它们之间有个映射关系(所以大小相等)。任何时刻,虚拟页面分为三个不相交的子集:(1)未分配的页,这种页由于没有被虚拟存储器系统所分配,因此不合磁盘上面的数据有任何联系,所以也不会占用任何空间。(2)已分配的也但是没有缓存在贮存里面。(3)已分配的页,而且还缓存在主存里面。

[3]页表:由于我们要进行虚拟地址转换到物理地址,所以必须要有一个表来保存当前进程从虚拟地址到物理地址之间的转换。同时页表还可以告诉cpu当前的虚拟地址所在的页是不是缓存在主存里面了,如果没有没有缓存,那么就会引发缺页异常,从而导致系统先去磁盘里面找到相应的页,然后根据一个替换算法将这某一个页替换出来。最终可以可以将需要的内容加载起来的。注意,从磁盘里面加载时非常慢的,所以我们的程序如果缺少较好的局部性,那么就可能导致程序非常之慢!

[4]虚拟存储器作为存储器管理的功能:

(1)简化链接:独立的地址空间可用让我们用统一的格式处理不同的进程,这样极大的简化了链接器的设计,让链接器可以生成完全独立于实际存储器地址的全链接。

(2)简化加载,linux加载器在一个新的进程开始的时候分配一大段没有缓存chunk(很多页),然后并不去实际的拷贝磁盘上面的数据,而是通过cpu的指令自动的完成对某个虚拟地址存放内容的加载。

(3)简化共享:例如不同的进程都需要去使用标准库的一些变量或者函数,这种情况下虚拟存储器将不同的虚拟地址映射到同一个物理页上,这样就可以实现共享了。

(4)简化存储器分配:当一个用户和系统请求更多的存储器空间时,操作系统将分配k个连续的虚拟存储器页面给用户,并且将它们映射到任意的k个物理页面。由于页表的工作方式,所以操作系统没有必要分配连续的页面。

[5]虚拟存储器作为保护存储器的机制:

任何一个计算机系统都应该对访问存储器提供一定的保护机制,比如用户进程不能修改只读文件,也不能够修改内核里面的任意代码。也不允许它读写其他进程的私有数据!而页表恰好提供了这样的一种机制实现:我们每次读取一个虚拟地址都要访问页表,所以可以在页表上面添加几个有效位来说明权限。

[6]地址翻译:从形式上面来讲,地址翻译是从一个M位的地址空间到N位的地址空间的一次映射。首先页表基址寄存器存指向页表的起始地址,n位的虚拟地址包含两个方面,一个是p位的页面偏移VPO,另一个是(n-p)位的虚拟页号VPN。通过VPN来选择相应的页表然后将页表里面的物理页号号虚拟地址里面的VPO结合起来就得到了真实的地址(想一想这里为什么是虚拟地址里面的VPO?因为物理页和虚拟页的大小是一样的,所以一个地址在页内的偏移也是一样的)。

CSAPP学习笔记第9章:_第1张图片
CSAPP学习笔记第9章:_第2张图片

结合高速缓存和虚拟寄存器:

大多数系统是使用物理地址来访问高速缓存的。

CSAPP学习笔记第9章:_第3张图片

利用TLB进行加速,每次都要去主存查找页表太慢了,所以我们在MMU里面有一个小的关于虚拟地址的缓存。焦作TLB,其中每一行都保存着单个页表的块。用于组选择和行匹配的号是从虚拟地址的页号里面提取出来的:

这里写图片描述

多级页表,因为在任意时刻我们都必须把页表存放在内存里面,所以如果虚拟地址的范围比较大,比如有32位的地址空间和4kB的页,四字节的PTA(物理地址),那么我们需要的页表的数量是2^32/(2^12) = 2^20,那么一共有4M大小,这是非常浪费的(如果只需要一小部分的页)。所以一个较好的解决办法就是多级页表:假设现在有两级页表,前一级的也表负责后面的一大块页的范围,如果哪一块范围没有任何一个页是分配过的,那么前一级的页表就不指向任何后一级。否则就指向后面哪一块范围内页表的第一个页表。

CSAPP学习笔记第9章:_第4张图片

[7]存储器映射:将一个虚拟存储器区域(一系列地址)和文件系统山的某一个文件对象关联起来,用来初始化这个虚拟存储器区域,这个过程叫做存储器映射。

这样就会将文件划分成很多的片,每一个片用来初始化虚拟存储器的一个页。因为按需进行调度,所以这些内容实际上并没有进入存储器。直到cpu第一次引用这个虚拟地址。一旦一个页面被初始化了,它就会在一个由内核维护的专门的交换文件之间换来换去。

控制多个进程共享对象:一个对象可以被映射到虚拟地址区域里面,一旦被映射,要么是私有的,要么是共享的。如果是共享的,那么任何对这个区域所做的写操作都回被返反映到文件对象上面。同时在存储器上面的变化对于其他的进程说也是可见的。

另一方面,对一个私有的对象作改变对其他进程来说是不可见的。

CSAPP学习笔记第9章:_第5张图片

nmp:用户级别的映射,这个函数可以创建一个新的虚拟地址区域,同时将这个地址映射到一个文件对象上。

[8]动态存储器分配:

这里实际上就是讲关于堆的分配,由于我们运行的时候可能需要额外的存储器空间。所以就有了动态分配器:它维护着一个进程的虚拟地址区域,叫做堆,堆是向上增长的,同时内核维护一个指针指向队的顶部。

分配器就是将堆看作是不同大小的块来维护,每个块就是一片连续的虚拟存储器片,要么是已经分配的,要么是空闲的。

两种分配风格:

CSAPP学习笔记第9章:_第6张图片
这里写图片描述

malloc,用来显示的分配虚拟存储器地址,可以指定分配的大小

后面讨论了堆的分配算法的一些实现,总结如下:

[1]:用来指示的数据结构,标志边界和大小。

CSAPP学习笔记第9章:_第7张图片

[2]:隐式空闲链表:

这里写图片描述

[3]:放置策略,首次适配,下一次适配,最佳适配。后面分析。

[4]:分割空闲块,决定要分割多少作为分配块。

[5]:合并策略,当释放一个分配块的时候,一共有四种情况需要处理(前后的空闲情况),如何高效的合并?可以通过添加边界标记,推迟合并等技术来处理。

CSAPP学习笔记第9章:_第8张图片

[6]后面还有分离适配等技术,不过就没怎么看了。

[9]:垃圾收集机制:

简单的说,分配器需要将一些不需要的块进行回收,这样就不会浪费系统的资源,为了检测有哪些块需要回收,使用了如下的算法:

CSAPP学习笔记第9章:_第9张图片

对于C和C++来说,这样的机制是保守的,意思是一定不会将不该分配的释放,但是可能会漏掉应该释放的。

CSAPP学习笔记第9章:_第10张图片

实际上理想情况应该就是一次DFS而已。但是C和C++的存储器不维护任何类型的信息,这导致我们不能确定一个字是不是指针,从而不能判断它是不是指向某一个地址还是仅仅是一个巧合。其次,即时它是一个指针,我们也不容易判断这个地址是不是指向有效的分配区域。

针对后者,我们可以采取维护一棵二叉平衡树的方式来解决,每一个分配块的头部都增加一个left和right用来指向下一个分配块的头部。左边的子树地址小而右边的地址大,这样就可以查找一个地址是不是在有效的分配区域了。

CSAPP学习笔记第9章:_第11张图片

你可能感兴趣的:(操作系统)