3434
3535上述几种内存区域中数据段、BSS和堆通常是被连续存储的——内存位置上是连续的,而代码段和栈往往会被独立存放。有趣的是,堆和栈两个区域关系很“暧昧”,他们一个向下“长”(i386体系结构中栈向下、堆向上),一个向上“长”,相对而生。但你不必担心他们会碰头,因为他们之间间隔很大(到底大到多少,你可以从下面的例子程序计算一下),绝少有机会能碰到一起。
3636
37+ ## 地址空间
38+
39+ 如果要使多个应用程序同时运行在内存中,必须要解决两个问题:` 保护 ` 和 ` 重定位 ` 。第一种解决方式是用` 保护密钥标记内存块 ` ,并将执行过程的密钥与提取的每个存储字的密钥进行比较。这种方式只能解决第一种问题(破坏操作系统),但是不能解决多进程在内存中同时运行的问题。
40+
41+ 还有一种更好的方式是创造一个存储器抽象:` 地址空间(the address space) ` 。就像进程的概念创建了一种抽象的 CPU 来运行程序,地址空间也创建了一种抽象内存供程序使用。
42+
43+ #### 基址寄存器和变址寄存器
44+
45+ 最简单的办法是使用` 动态重定位(dynamic relocation) ` 技术,它就是通过一种简单的方式将每个进程的地址空间映射到物理内存的不同区域。还有一种方式是使用基址寄存器和变址寄存器。
46+
47+ - 基址寄存器:存储数据内存的起始位置
48+ - 变址寄存器:存储应用程序的长度。
49+
50+ 每当进程引用内存以获取指令或读取、写入数据时,CPU 都会自动将` 基址值 ` 添加到进程生成的地址中,然后再将其发送到内存总线上。同时,它检查程序提供的地址是否大于或等于` 变址寄存器 ` 中的值。如果程序提供的地址要超过变址寄存器的范围,那么会产生错误并中止访问。
51+
3752
3853
3954## 内存的演变
@@ -90,7 +105,17 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
90105
91106### 伙伴系统
92107
93- 固定分区和动态分区方案都有缺陷。固定分区方案限制了活动进程数量,且如果可用分区的大小与进程大小很不匹配,那么内存空间的利用率会非常低。动态分区的维护特别复杂,并且会引入进行压缩的额外开销。更有吸引力的一种折中方案是伙伴系统。
108+ 固定分区和动态分区方案都有缺陷。固定分区方案限制了活动进程数量,且如果可用分区的大小与进程大小很不匹配,那么内存空间的利用率会非常低。动态分区的维护特别复杂,并且会引入进行压缩的额外开销。更有吸引力的一种折中方案是伙伴系统。 它是一种经典的内存分配方案,是一种特殊的“分离适配”算法。主要思想是将内存按2的幂进行划分,组成若干空闲块链表,查找该链表找到能满足进程需求的最佳匹配块。
109+
110+ 算法:
111+
112+ - 首先将整个可用空间看做一块:2的幂,这里假设一共有1M的内存。
113+ - 假设进程申请的空间大小为s,这里假设为100k,如果满足2的u-1次幂 < s <= 2的u次幂,则分配整个块,否则,将块划分为两个大小相等的伙伴,大小为2的u-1次幂,那这两个大小相等的伙伴就是伙伴关系。等内存回收后,具有伙伴关系的两块还可以合并到一起,组成一个更大的内存块。
114+ - 1M内存分为两块。
115+ - 512k、512k仍然大于100k,将第一块再分为两块。
116+ - 256k、256k、512k,而第一块256k仍然大于100k,第一块256k再分配。
117+ - 128k、128k、256k、512k。这个时候64k < 100k < 128k,所以把第一个128k分配给当前申请100k内存的进程。
118+ - 等该进程的内存回收后,前两个128k的内存具有伙伴关系,还可以合并成256k。合并后和后面的256k又是一个伙伴,又合并成512k,发现和后面的512k又是一个伙伴,就又合并成了1M的内存块。
94119
95120
96121
@@ -101,11 +126,13 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
101126在大小相等的分区中一个进程在其声明周期中可能占据不同的分区。首次创建一个进程映像时,它被装入内存中的某个分区。以后该进程可能被换出,当它再次被换入时,可能被指定到与上一次不同的分区中。动态分区也存在同样的情况,压缩后内存中的进程也可能发生移动。因此,进程访问(指令和数据单元)的位置不是固定的。进程被换入或在内存中移动时,指令和数据单元的位置也发生变化。为了解决这个问题,需要区分几种地址类型:
102127
103128- 逻辑地址(logical address)是指与当前数据在内存中的物理分配地址无关的访问地址,在执行对内存的访问之前必须把它转换为物理地址。
104- - 相对地址(relative address)是逻辑地址的一个特例,他是相对于某些已知点(通常是程序的开始处)的存储单元。
129+ - 相对地址(relative address)是逻辑地址的一个特例,他是相对于某些已知点(通常是程序的开始处)的存储单元。用户程序经过编译、汇编后形成目标代码,目标代码通常采用相对地址的形式,其首地址为0,其余地址相对于首地址而编址。
105130- 物理地址(physical address)或绝对地址是数据在内存中的实际位置。
106131
107132系统采用运行时动态加载的方式把使用相对地址的程序加载到内存。通常情况下,被加载进程中所有内存访问都相对于程序的开始点。因此,在执行包括这类访问的指令时,需要有把相对地址转换为物理内存地址的硬件机制。这类地址转换就需要一个特殊的处理器寄存器(基址寄存器),其内容是程序在内存中的起始地址。还有一个界限寄存器指明程序的终止位置。
108133
134+ 将逻辑地址转换为物理地址的操作就叫做重定位。而把地址进行转换的功能部件就叫做内存管理单元(MMU, Memory Management Unit)。
135+
109136
110137
111138### 分页
@@ -126,7 +153,7 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
126153
127154### 分段
128155
129- 细分用户程序的另一种可选方案是分段。采用分段技术,可以把程序和与其相关的数据划分到几个段 (fragment)中。尽管段有最大长度限制,但并不要求所有程序的所有段的长度都相等。和分页一样,采用分段技术时的逻辑地址也是由两部分组成:段号和偏移量。
156+ 细分用户程序的另一种可选方案是分段。采用分段技术,可以把用户进程地址空间按程序的自身的逻辑关系和与其相关的数据划分到几个段 (fragment)中。尽管段有最大长度限制,但并不要求所有程序的所有段的长度都相等。同样内存空间也会被动态的划分为若干长度不相同的取悦,称为物理段,每个物理段由起始地址和长度确定。 和分页一样,采用分段技术时的逻辑地址也是由两部分组成:段号和偏移量。 以段为单位进行分配,每段在内存中占据连续空间,但各段之间可以不相邻。
130157
131158由于使用大小不等的段,分段类似于动态分区。在未采用覆盖方案或使用虚存的情况下,为执行一个程序,需要把它的所有段都装入内存。与动态分区不同的是,在分段方案中,一个程序可以占据多个分区,并且这些分区不要求是连续的。分段消除了内部碎片,但是和动态分区一样,他会产生外部碎片。不过由于进程被分成多个小块,因此外部碎片也会很小。
132159
@@ -154,12 +181,22 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
154181
155182
156183
184+ ### 段页式存储管理方案
185+
186+ 综合页式、段式方案的优点,客服两者的缺点。用户进程先按段划分,每一段再按页面划分。内存分配还是以页为单位进行分配。
187+
188+
189+
190+
191+
157192
158193
159194## 虚拟内存
160195
161196面对越来越大的程序,常常产生程序>内存的问题,为解决这种问题,虚拟内存的概念得到普及.
162197
198+ 虚拟内存是一种内存分配方案,是一项可以用来辅助内存分配的机制。我们知道,应用程序是按页装载进内存中的。但并不是所有的页都会装载到内存中,计算机中的硬件和软件会将数据从 RAM 临时传输到磁盘中来弥补内存的不足。如果没有虚拟内存的话,一旦你将计算机内存填满后,计算机会对你说呃,不,对不起,您无法再加载任何应用程序,请关闭另一个应用程序以加载新的应用程序。对于虚拟内存,计算机可以执行操作是查看内存中最近未使用过的区域,然后将其复制到硬盘上。虚拟内存通过复制技术实现了 妹子,你快来看哥哥能装这么多程序 的资本。复制是自动进行的,你无法感知到它的存在。
199+
163200要有效的使用处理器和I/O设备,就需要在内存中保留尽可能多的进程。此外,还需要解除程序在开发时对程序使用内存大小的限制。解决这两个问题的途径就是虚拟内存技术。采用虚拟内存技术时,所有的地址访问都是逻辑访问,并在运行时转换为实地址。
164201
165202虚拟内存机制使得期望运行大于物理内存的程序成为可能。其方法是将程序放在磁盘上,而将主存作为一种缓存,用来保存最频繁使用的部分程序。这种机制需要快速的映像内存地址,以便把程序生成的地址转换为有关字节在RAM中的物理地址。这种映像由CPU中的一个称为存储器管理单元(Memory Management Unit,MMU)的部件来完成。
@@ -211,7 +248,17 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
211248
212249以便处理在必须读取一个新页时,应该置换内存中的哪一页。当内存中的所有页框都被占据,且需要读取一个新页以处理一次缺页中断时,置换策略决定置换当前内存中的哪一页。所有策略的目标都是移出最近最不能访问的页。
213250
251+ - ` 最优算法 ` 在当前页面中置换最后要访问的页面。不幸的是,没有办法来判定哪个页面是最后一个要访问的,` 因此实际上该算法不能使用 ` 。然而,它可以作为衡量其他算法的标准。
252+ - ` NRU ` 算法根据 R 位和 M 位的状态将页面氛围四类。从编号最小的类别中随机选择一个页面。NRU 算法易于实现,但是性能不是很好。存在更好的算法。
253+ - ` FIFO ` 会跟踪页面加载进入内存中的顺序,并把页面放入一个链表中。有可能删除存在时间最长但是还在使用的页面,因此这个算法也不是一个很好的选择。
254+ - ` 第二次机会 ` 算法是对 FIFO 的一个修改,它会在删除页面之前检查这个页面是否仍在使用。如果页面正在使用,就会进行保留。这个改进大大提高了性能。
255+ - ` 时钟 ` 算法是第二次机会算法的另外一种实现形式,时钟算法和第二次算法的性能差不多,但是会花费更少的时间来执行算法。
256+ - ` LRU ` 算法是一个非常优秀的算法,但是没有` 特殊的硬件(TLB) ` 很难实现。如果没有硬件,就不能使用 LRU 算法。
257+ - ` NFU ` 算法是一种近似于 LRU 的算法,它的性能不是非常好。
258+ - ` 老化 ` 算法是一种更接近 LRU 算法的实现,并且可以更好的实现,因此是一个很好的选择
259+ - 最后两种算法都使用了工作集算法。工作集算法提供了合理的性能开销,但是它的实现比较复杂。` WSClock ` 是另外一种变体,它不仅能够提供良好的性能,而且可以高效地实现。
214260
261+ 总之,** 「最好的算法是老化算法和WSClock算法」** 。他们分别是基于 LRU 和工作集算法。他们都具有良好的性能并且能够被有效的实现。还存在其他一些好的算法,但实际上这两个可能是最重要的。
215262
216263#### 4. 驻留集管理
217264
@@ -237,6 +284,16 @@ Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自
237284
238285
239286
287+ ## 内存映射
288+
289+ 进程通过一个系统调用(mmap)将一个文件(或部分)映射到其虚拟地址空间的一部分,访问这个文件就像访问内存中的一个大数组,而不是对文件进行读写。
290+
291+ 在多数实现中,在映射共享的页面时不会实际读入页面的内容,而是在访问页面时,页面才会被每次一页的读入(虚拟内存的缺页处理),磁盘文件则被当做后备存储。
292+
293+ 当进程退出或显式地解除文件映射时,所有被修改页面会写回文件。
294+
295+
296+
240297
241298
242299## Android内存管理
0 commit comments