0 00:00:00,000 --> 00:00:15,400 1 00:00:15,440 --> 00:00:16,880 各位同学大家好 2 00:00:16,920 --> 00:00:22,760 今天我们来讲非连续存储分配 3 00:00:22,800 --> 00:00:27,160 在前面我们讲到的连续内存分配中 4 00:00:27,200 --> 00:00:30,040 要给一个进程分配内存 5 00:00:30,080 --> 00:00:33,520 那必须分配物理地址 6 00:00:33,560 --> 00:00:37,160 连续的一块内存区域 7 00:00:37,200 --> 00:00:40,320 这给分配带来了很多的麻烦 8 00:00:40,360 --> 00:00:43,680 你比如说用户想要一块区域 9 00:00:43,720 --> 00:00:45,920 而在内存当中呢又没有 10 00:00:45,960 --> 00:00:47,800 满足这个大小的连续区域 11 00:00:47,840 --> 00:00:49,960 那这个分配就会失败 12 00:00:50,000 --> 00:00:53,040 基于这种现状 那提出了一种需求 13 00:00:53,080 --> 00:00:55,080 说我们是否可以不连续 14 00:00:55,120 --> 00:00:56,920 分配的内存空间不连续 15 00:00:56,960 --> 00:00:58,960 好 不连续呢 当然是说 16 00:00:59,000 --> 00:01:01,240 我可以找到它的机率会更高 17 00:01:01,280 --> 00:01:04,280 但这时候也会面临一些新的麻烦 18 00:01:04,320 --> 00:01:07,000 说我不连续之后我把哪些内容 19 00:01:07,040 --> 00:01:09,320 我是任意一个最小的单位 20 00:01:09,360 --> 00:01:11,280 一个字节就可以算成一个区域 21 00:01:11,320 --> 00:01:13,440 还是说我会受到一些限制 22 00:01:13,480 --> 00:01:15,080 那这个时候你这个分配到 23 00:01:15,120 --> 00:01:17,960 一个字节为一个单位太短了 24 00:01:18,000 --> 00:01:19,240 基于这种情况呢 25 00:01:19,280 --> 00:01:22,040 我们会选择不同的尺度来说 26 00:01:22,080 --> 00:01:26,080 我这个非连续内存分配我能每一块 27 00:01:26,120 --> 00:01:27,920 基本的块会有多大 28 00:01:27,960 --> 00:01:30,240 基于这种基本块大小的不同呢 29 00:01:30,280 --> 00:01:31,680 在我们这里有两种 30 00:01:31,720 --> 00:01:34,520 一种是段式 一种是页式 31 00:01:34,560 --> 00:01:36,720 那段式呢分的块比较大 32 00:01:36,760 --> 00:01:38,960 页式呢分的块比较小 33 00:01:39,000 --> 00:01:40,440 分的块小了之后呢 34 00:01:40,480 --> 00:01:42,520 我这两者之间的对应关系 35 00:01:42,560 --> 00:01:46,480 就会从逻辑地址到物理地址的 36 00:01:46,520 --> 00:01:49,440 对应关系就会变得比较复杂 37 00:01:49,480 --> 00:01:50,800 好 因为这个缘故 38 00:01:50,840 --> 00:01:51,960 所以我们这种对应关系呢 39 00:01:52,000 --> 00:01:54,760 就形成我们这里说到的页表 40 00:01:54,800 --> 00:01:56,720 好 还有一种方式是说 41 00:01:56,760 --> 00:01:57,280 我是不是可以 42 00:01:57,320 --> 00:01:58,880 把这两种方式结合起来 43 00:01:58,920 --> 00:02:00,960 那这就是我们这里的段页式 44 00:02:01,000 --> 00:02:08,120 首先呢我们来讲非连续内存分配的需求 45 00:02:08,160 --> 00:02:11,280 那在连续分配的缺点当中 46 00:02:11,320 --> 00:02:14,040 我们说你要求必须连续 47 00:02:14,080 --> 00:02:15,600 这一条很难达到 48 00:02:15,640 --> 00:02:17,000 好即使能达到 49 00:02:17,040 --> 00:02:18,960 在分配回收的过程当中 50 00:02:19,000 --> 00:02:21,800 里头也会有内碎片 外碎片 51 00:02:21,840 --> 00:02:23,360 好 如果说一个程序 52 00:02:23,400 --> 00:02:24,800 在执行的过程当中 53 00:02:24,840 --> 00:02:27,640 它需求的内存空间大小又有变化 54 00:02:27,680 --> 00:02:29,720 好 那这种变化呢就导致 55 00:02:29,760 --> 00:02:35,120 你很难进行动态的增加或者说减少 56 00:02:35,160 --> 00:02:36,600 这样最后的结果呢就是 57 00:02:36,640 --> 00:02:38,960 我们内存的利用效率比较低 58 00:02:39,000 --> 00:02:41,480 原因在于用户应用进程的需求 59 00:02:41,520 --> 00:02:43,800 你没有办法满足 60 00:02:43,840 --> 00:02:45,160 好 针对这种情况 61 00:02:45,200 --> 00:02:47,960 我们非连续内存分配 62 00:02:48,000 --> 00:02:49,480 那这时候要达到的目标呢 63 00:02:49,520 --> 00:02:51,680 就希望能够提高利用效率 64 00:02:51,720 --> 00:02:54,240 提高它的管理的灵活性 65 00:02:54,280 --> 00:02:57,320 具体说起来呢有这样几条 66 00:02:57,360 --> 00:02:59,720 第一个我希望分配的空间 67 00:02:59,760 --> 00:03:02,680 不再是有连续性的要求 68 00:03:02,720 --> 00:03:04,280 好 这样的话我找着你的 69 00:03:04,320 --> 00:03:08,040 要的大小的区域的机率呢就会提高 70 00:03:08,080 --> 00:03:10,800 第二个呢是说我们在用的过程当中 71 00:03:10,840 --> 00:03:13,000 每个进程它都要执行代码 72 00:03:13,040 --> 00:03:16,520 这些进程之间有很多代码是共同的 73 00:03:16,560 --> 00:03:18,760 那各个进程 74 00:03:18,800 --> 00:03:21,680 也会用到一些数据是共用的 75 00:03:21,720 --> 00:03:27,120 我们希望通过共享数据和代码 76 00:03:27,160 --> 00:03:29,160 实现减少内存的使用量 77 00:03:29,200 --> 00:03:30,240 也就是说我们在这里头 78 00:03:30,280 --> 00:03:33,600 两个应用进程都要用到同一个函数库 79 00:03:33,640 --> 00:03:36,200 那我把这个函数库的代码放到内存之后 80 00:03:36,240 --> 00:03:38,280 如果它们两个都能访问的话 81 00:03:38,320 --> 00:03:39,280 能够实现共享 82 00:03:39,320 --> 00:03:40,960 那这样的话我占用的内存区域 83 00:03:41,000 --> 00:03:42,400 不就变少了吗 84 00:03:42,440 --> 00:03:46,000 好 第三个是说我们希望能够变得灵活 85 00:03:46,040 --> 00:03:47,960 那我在这里头是不是分小了之后 86 00:03:48,000 --> 00:03:50,600 我想再要的时候我再给它加一块 87 00:03:50,640 --> 00:03:53,640 或者说其中某一块的大小的动态变化 88 00:03:53,680 --> 00:03:56,520 有了这些之后呢 我们就可以很方便的 89 00:03:56,560 --> 00:04:00,120 能够支持动态加载和动态链接 90 00:04:00,160 --> 00:04:02,760 好 这是这个目标已经说完了 91 00:04:02,800 --> 00:04:06,000 好像说起来这个目标很好 92 00:04:06,040 --> 00:04:07,920 但是这在实现的时候 93 00:04:07,960 --> 00:04:10,000 是会面临很多麻烦的 94 00:04:10,040 --> 00:04:12,720 首先第一个麻烦就是我们这里说到的 95 00:04:12,760 --> 00:04:15,960 虚拟地址到物理地址的转换 96 00:04:16,000 --> 00:04:20,080 那程序里头在实现的时候 97 00:04:20,120 --> 00:04:22,320 那它希望是说我给你一个地址 98 00:04:22,360 --> 00:04:23,360 然后告诉你 99 00:04:23,400 --> 00:04:24,040 你要告诉我 100 00:04:24,080 --> 00:04:26,720 它存在物理内存的什么地方 101 00:04:26,760 --> 00:04:28,640 这种转换呢如果它是连续的 102 00:04:28,680 --> 00:04:30,720 我只需要知道它的起头在哪 103 00:04:30,760 --> 00:04:32,000 剩下的问题就都在 104 00:04:32,040 --> 00:04:34,640 这一个进程的区域里了 105 00:04:34,680 --> 00:04:36,640 好 那如果现在不连续了 106 00:04:36,680 --> 00:04:38,040 那这时候这个转换呢 107 00:04:38,080 --> 00:04:41,760 就有可能是说你在哪一段逻辑地址 108 00:04:41,800 --> 00:04:44,400 你转换的区域在内存里的一个地方 109 00:04:44,440 --> 00:04:46,680 而逻辑地址的另一段 110 00:04:46,720 --> 00:04:47,800 你在这边是连续的 111 00:04:47,840 --> 00:04:50,640 到那边可能要转到另外一块区域里头去 112 00:04:50,680 --> 00:04:52,560 这两种区域的不同就会导致 113 00:04:52,600 --> 00:04:56,520 我在这里这个转换的过程会比较复杂 114 00:04:56,560 --> 00:04:58,320 好这种转换的复杂呢 115 00:04:58,360 --> 00:05:00,120 我们在实际实现的时候呢 116 00:05:00,160 --> 00:05:02,520 怎么来做呢 这里又有两种选择 117 00:05:02,560 --> 00:05:04,200 一种是用软件来实现 118 00:05:04,240 --> 00:05:08,760 比如说我要往内存里存数据 119 00:05:08,800 --> 00:05:10,480 像一个排序程序 120 00:05:10,520 --> 00:05:12,960 那由于我没有办法事先确定 121 00:05:13,000 --> 00:05:14,880 我要排序的数据的总量 122 00:05:14,920 --> 00:05:16,160 这时我给它分配 123 00:05:16,200 --> 00:05:18,840 多大的存储空间都有不合适的情况 124 00:05:18,880 --> 00:05:22,040 那我们会说在数据结构里学会有什么办法 125 00:05:22,080 --> 00:05:25,400 说我们在这里头可以先读一部分进来 126 00:05:25,440 --> 00:05:28,560 排完之后放到硬盘上去 127 00:05:28,600 --> 00:05:29,840 然后再读一部分进来 128 00:05:29,880 --> 00:05:32,040 排完之后放到硬盘上去 129 00:05:32,080 --> 00:05:33,240 那这时候呢 130 00:05:33,280 --> 00:05:35,440 最后我再把排好的再重新捋一遍 131 00:05:35,480 --> 00:05:38,200 那这就是数据结构里说到的外排序 132 00:05:38,240 --> 00:05:39,760 那这种办法呢也可以用到 133 00:05:39,800 --> 00:05:41,880 操作系统里的内存分配上来 134 00:05:41,920 --> 00:05:45,040 如果说你的代码空间存不下 135 00:05:45,080 --> 00:05:47,840 把所有的代码存内存里存不下的话 136 00:05:47,880 --> 00:05:50,200 那这时候我们可以说把其中的 137 00:05:50,240 --> 00:05:52,800 当前要执行的代码放到内存里头 138 00:05:52,840 --> 00:05:55,440 把另外一部分代码呢放到硬盘上去 139 00:05:55,480 --> 00:05:57,760 那这时候说这个倒怎么来做呢 140 00:05:57,800 --> 00:05:58,960 可以由软件来做 141 00:05:59,000 --> 00:06:00,040 也跟我们刚才说的 142 00:06:00,080 --> 00:06:02,880 数据外排序类似的办法 143 00:06:02,920 --> 00:06:03,960 那这种做法是 144 00:06:04,000 --> 00:06:07,920 我们通过你的操作系统软件 145 00:06:07,960 --> 00:06:10,800 或者说甚至于是应用软件来干这事 146 00:06:10,840 --> 00:06:14,160 另一种做法呢是硬件实现 147 00:06:14,200 --> 00:06:16,800 如果你要想用硬件来实现的话 148 00:06:16,840 --> 00:06:18,920 原因在于 我们现在 149 00:06:18,960 --> 00:06:21,520 做的地址转换过于频繁 150 00:06:21,560 --> 00:06:23,440 基本上是说你每执行一条指令 151 00:06:23,480 --> 00:06:25,880 都会去访问内存 那都要做成转换 152 00:06:25,920 --> 00:06:27,280 这时候用硬件来实现呢 153 00:06:27,320 --> 00:06:30,000 它的效果是比较好的 开销比较小 154 00:06:30,040 --> 00:06:32,560 而且这个转换相对来说 155 00:06:32,600 --> 00:06:33,600 它要计算的过程 156 00:06:33,640 --> 00:06:36,560 是比较简单的重复 157 00:06:36,600 --> 00:06:39,960 好 这样也适合于用硬件来实现 158 00:06:40,000 --> 00:06:41,600 好 那有了这个实现之后 159 00:06:41,640 --> 00:06:43,320 说还有一个啥问题呢 160 00:06:43,360 --> 00:06:44,440 还有一个问题是说 161 00:06:44,480 --> 00:06:45,720 我们在这里头 162 00:06:45,760 --> 00:06:53,120 要把这个进程分配的内存 163 00:06:53,160 --> 00:06:56,000 放到不连续的地方 164 00:06:56,040 --> 00:06:58,320 那我每一块大小有多大 165 00:06:58,360 --> 00:07:00,680 那简单来讲你可能会说 166 00:07:00,720 --> 00:07:02,680 要多大我给多大就完了 167 00:07:02,720 --> 00:07:04,960 那这时候是你连续的那样一块 168 00:07:05,000 --> 00:07:06,280 我分成小块之后 169 00:07:06,320 --> 00:07:08,400 我可以分成什么样的小块 170 00:07:08,440 --> 00:07:10,520 这时 我们前面连续分配里说到的 171 00:07:10,560 --> 00:07:13,440 内碎块 外碎块这些问题是否还存在 172 00:07:13,480 --> 00:07:16,000 好 这种分配的不同会导致 173 00:07:16,040 --> 00:07:20,440 这非连续内存分配的办法有很大的区别 174 00:07:20,480 --> 00:07:23,440 这里呢 我们有大致有两类办法 175 00:07:23,480 --> 00:07:28,040 一类叫做段式存储 一类叫做页式存储 176 00:07:28,080 --> 00:07:30,000 简单来说这两者区别就是 177 00:07:30,040 --> 00:07:32,240 段式存储分的块比较大 178 00:07:32,280 --> 00:07:36,400 它以一个段作为一个基本的单位 179 00:07:36,440 --> 00:07:37,360 那你在分配的时候 180 00:07:37,400 --> 00:07:38,640 这一个段的内容 181 00:07:38,680 --> 00:07:40,880 必须在物理内存里是连续的 182 00:07:40,920 --> 00:07:43,880 不同段之间呢是可以放到不同地方的 183 00:07:43,920 --> 00:07:45,520 这是段式 页式呢 184 00:07:45,560 --> 00:07:48,560 我就把它分成更小的块 185 00:07:48,600 --> 00:07:50,480 这个块的名字叫做页 186 00:07:50,520 --> 00:07:52,160 好 那你在分配的时候 187 00:07:52,200 --> 00:07:54,160 就是以页为单位来分配 188 00:07:54,200 --> 00:07:56,280 页与页之间呢是不连续的 189 00:07:56,320 --> 00:07:59,280 好 那由于这两者分配的情况的不同 190 00:07:59,320 --> 00:08:00,520 那实际上它在实现的时候 191 00:08:00,560 --> 00:08:02,040 会有很大的区别 192 00:08:02,080 --> 00:08:04,360 这些区别呢 会落实到我们后面讲到的 193 00:08:04,400 --> 00:08:09,160 段式存储管理和页式存储管理的实现细节 194 00:08:09,200 --> 00:08:10,560 好 有了这些描述之后 195 00:08:10,600 --> 00:08:12,640 那我们就可以来具体讨论说 196 00:08:12,680 --> 00:08:15,360 我的段式和页式到底是怎么实现的 197 00:08:15,400 --> 00:08:15,440