0 00:00:00,000 --> 00:00:15,480 1 00:00:15,520 --> 00:00:17,040 各位同学大家好 2 00:00:17,080 --> 00:00:21,600 今天我们开始介绍存储管理 3 00:00:21,640 --> 00:00:24,800 计算机系统当中除了处理能力之外 4 00:00:24,840 --> 00:00:28,240 还有存储能力 存储能力相当于 5 00:00:28,280 --> 00:00:31,560 我们有一系列基本的存储介质 6 00:00:31,600 --> 00:00:33,080 我们要在这些介质当中 7 00:00:33,120 --> 00:00:36,400 来存我们的代码和数据 8 00:00:36,440 --> 00:00:39,800 为此我们在这里具体的来说 9 00:00:39,840 --> 00:00:41,560 对于计算机系统来讲 10 00:00:41,600 --> 00:00:44,000 它的体系结构当中就约定了 11 00:00:44,040 --> 00:00:46,640 我哪些地方可以来存数据 12 00:00:46,680 --> 00:00:49,560 然后在存数据的这些地方 13 00:00:49,600 --> 00:00:54,640 既包括CPU里的寄存器 也包括内存和外存 14 00:00:54,680 --> 00:00:57,600 这几种不同的存储介质 15 00:00:57,640 --> 00:01:01,960 它的容量 速度 价格都是不一样的 16 00:01:02,000 --> 00:01:03,880 为了组织一个合理的系统 17 00:01:03,920 --> 00:01:06,120 我们把计算机系统当中的存储 18 00:01:06,160 --> 00:01:08,800 组织成了一个层次结构 19 00:01:08,840 --> 00:01:12,600 针对这种层次结构下的存储单元 20 00:01:12,640 --> 00:01:14,680 操作系统需要对它进行管理 21 00:01:14,720 --> 00:01:17,000 操作系统当中的存储管理 22 00:01:17,040 --> 00:01:20,240 实际上就是用来管理这些存储介质的 23 00:01:20,280 --> 00:01:22,640 最基本的管理要求是说 24 00:01:22,680 --> 00:01:26,640 我们一个进程需要使用存储单元的时候 25 00:01:26,680 --> 00:01:29,120 需要从操作系统分一块给它 26 00:01:29,160 --> 00:01:31,560 等它不用的时候还给操作系统 27 00:01:31,600 --> 00:01:39,320 这是它最基本的分配和释放的管理要求 28 00:01:39,360 --> 00:01:42,680 针对这种内存管理的要求 29 00:01:42,720 --> 00:01:45,000 我们来看系统结构当中 30 00:01:45,040 --> 00:01:47,160 有哪些因素对它有影响 31 00:01:47,200 --> 00:01:49,000 这个图实际上是我们在前面 32 00:01:49,040 --> 00:01:52,080 讲系统结构的时候就说过了 33 00:01:52,120 --> 00:01:55,680 计算机系统包括CPU 内存和I/O设备 34 00:01:55,720 --> 00:01:57,080 我们在上一次课里 35 00:01:57,120 --> 00:02:00,000 也说到过CPU在加电的时候 36 00:02:00,040 --> 00:02:03,200 我们关心各个寄存器的初始状态 37 00:02:03,240 --> 00:02:05,480 那么在今天讲存储的时候 38 00:02:05,520 --> 00:02:09,360 我们会更多关注与存储相关的内容 39 00:02:09,400 --> 00:02:10,680 比如说在CPU里头 40 00:02:10,720 --> 00:02:13,040 我们可以往寄存器存内容 41 00:02:13,080 --> 00:02:14,840 寄存器可以存数据 42 00:02:14,880 --> 00:02:18,280 但是寄存器它的容量是非常小的 43 00:02:18,320 --> 00:02:21,360 通常是32位 64位的寄存器 44 00:02:21,400 --> 00:02:24,440 能存的数据也就几十个字节 45 00:02:24,480 --> 00:02:27,120 或者几百个字节这种尺度 46 00:02:27,160 --> 00:02:32,160 然后我们说内存是更多的存数据的地方 47 00:02:32,200 --> 00:02:36,960 我们在前面说到过它只是说能存就行了 48 00:02:37,000 --> 00:02:39,640 在这我们需要更进一步去说 49 00:02:39,680 --> 00:02:41,880 计算机系统当中内存 50 00:02:41,920 --> 00:02:47,040 它的最小访问单位是字节 也就是8bit 51 00:02:47,080 --> 00:02:52,000 而通常我们所说的计算机系统是32位的总线 52 00:02:52,040 --> 00:02:56,160 那所谓32位总线也就相当于我一次读写 53 00:02:56,200 --> 00:02:59,880 可以从内存当中读或者写32位 54 00:02:59,920 --> 00:03:01,160 也就是4字节 55 00:03:01,200 --> 00:03:04,080 这样以来我们读写的速度就会快了 56 00:03:04,120 --> 00:03:05,880 如果针对这种特点 57 00:03:05,920 --> 00:03:10,040 你在由于一次读写32位的有地址对齐的事 58 00:03:10,080 --> 00:03:11,720 你在访问的时候就不能 59 00:03:11,760 --> 00:03:14,200 从任意的地方开始一个四字节 60 00:03:14,240 --> 00:03:16,480 有可能这个读写就会被分成两次 61 00:03:16,520 --> 00:03:18,600 还有一个是说我们在CPU里头 62 00:03:18,640 --> 00:03:20,680 你还会看到高速缓存 63 00:03:20,720 --> 00:03:22,320 高速缓存是什么意思呢 64 00:03:22,360 --> 00:03:25,680 在你进行读写指令 65 00:03:25,720 --> 00:03:27,480 或者指令执行过程中 66 00:03:27,520 --> 00:03:31,000 访问数据都需要从内存里读数据 67 00:03:31,040 --> 00:03:34,520 这个时候如果说我有大量数据要读写 68 00:03:34,560 --> 00:03:36,240 而且我会重复利用的话 69 00:03:36,280 --> 00:03:38,400 我在CPU里加上高速缓存 70 00:03:38,440 --> 00:03:41,160 那这样的话它的读写速度会更快 71 00:03:41,200 --> 00:03:43,520 这个时候整个读写效率会提高 72 00:03:43,560 --> 00:03:46,520 所以在CPU里加了高速缓存 73 00:03:46,560 --> 00:03:50,000 这几个部分都对我们存储管理 74 00:03:50,040 --> 00:03:52,600 有至关重要的影响 75 00:03:52,640 --> 00:03:55,320 所以大家在实际做操作系统的 76 00:03:55,360 --> 00:03:57,120 储存管理实现的时候 77 00:03:57,160 --> 00:04:00,840 你必须很准确的了解对应的CPU的结构 78 00:04:00,880 --> 00:04:02,880 那么对于我们操作系统课来讲 79 00:04:02,920 --> 00:04:05,680 我们讲的是X86的系统 80 00:04:05,720 --> 00:04:10,080 那这底下是英特尔的X86的手册 81 00:04:10,120 --> 00:04:12,200 里头要想查最详细的内容 82 00:04:12,240 --> 00:04:14,000 从这个手册里就可以查到 83 00:04:14,040 --> 00:04:15,480 84 00:04:15,520 --> 00:04:20,000 接下来我们更详细来看存储的层次结构 85 00:04:20,040 --> 00:04:21,120 那么我们刚才已经说了 86 00:04:21,160 --> 00:04:26,000 在CPU里头有两级缓存 这两级缓存 87 00:04:26,040 --> 00:04:30,600 我们如果说你在读写数据或者指令的时候 88 00:04:30,640 --> 00:04:32,880 在缓存里已经有相应的内容 89 00:04:32,920 --> 00:04:34,400 事先已经读过 90 00:04:34,440 --> 00:04:36,840 那这个时候我就直接可以从缓存里拿到 91 00:04:36,880 --> 00:04:38,800 这个时候速度是最快的 92 00:04:38,840 --> 00:04:42,280 然后说如果在这里头缓存不命中 93 00:04:42,320 --> 00:04:44,400 那这个时候你就必须上内存里去读 94 00:04:44,440 --> 00:04:48,080 而在上面这部分 我们在写程序的时候 95 00:04:48,120 --> 00:04:53,040 你是感觉不到L1L2 hash的存在的 实际为啥呢 96 00:04:53,080 --> 00:04:57,080 原因在于这部分完全是由硬件在做控制 97 00:04:57,120 --> 00:05:00,880 你写的程序不能显示的使用到它们 98 00:05:00,920 --> 00:05:02,920 而内存的访问就需要 99 00:05:02,960 --> 00:05:04,640 使用到操作系统的控制 100 00:05:04,680 --> 00:05:08,920 如果说你在内存里访问的时候仍然找不到 101 00:05:08,960 --> 00:05:11,920 这个时候还有可能我是存到外存里头去 102 00:05:11,960 --> 00:05:14,920 那么存到外存里头呢把它读进来 103 00:05:14,960 --> 00:05:15,720 你再进行访问 104 00:05:15,760 --> 00:05:19,200 这个时候就需要用到操作系统的控制 105 00:05:19,240 --> 00:05:21,080 那在这个体系结构当中 106 00:05:21,120 --> 00:05:23,800 我们可以看到从CPU内部 107 00:05:23,840 --> 00:05:25,480 一直到硬盘的外部 108 00:05:25,520 --> 00:05:27,640 这几个速度差的非常大的 109 00:05:27,680 --> 00:05:30,920 具体差到什么程度 我们这里有一张表 110 00:05:30,960 --> 00:05:34,440 最快的跟你CPU的主频是一样的 111 00:05:34,480 --> 00:05:37,640 那就是几个纳秒我就能访问到 112 00:05:37,680 --> 00:05:43,400 最慢是几个毫秒 这两者之间 113 00:05:43,440 --> 00:05:44,960 毫秒 微妙 纳秒 114 00:05:45,000 --> 00:05:47,920 差将近是百万倍的数量级 115 00:05:47,960 --> 00:05:49,520 所以在这套体系当中 116 00:05:49,560 --> 00:05:52,080 要想把它协调成一个有机的整体 117 00:05:52,120 --> 00:05:54,400 实际上对于存储管理来讲 118 00:05:54,440 --> 00:05:56,760 它的挑战性还是很大的 119 00:05:56,800 --> 00:05:57,960 120 00:05:58,000 --> 00:05:59,800 对于操作系统来说 121 00:05:59,840 --> 00:06:02,720 我们存储管理最后想达到什么效果 122 00:06:02,760 --> 00:06:05,080 我们在这给大家一个描述 123 00:06:05,120 --> 00:06:10,160 首先我们看到系统当中的存储 内存 124 00:06:10,200 --> 00:06:13,520 我们刚才说了是以字节为单位进行访问 125 00:06:13,560 --> 00:06:16,320 每一个字节有自己的一个地址 126 00:06:16,360 --> 00:06:18,280 这个地址是物理地址 127 00:06:18,320 --> 00:06:21,080 然后说我如果数据存到外存里了 128 00:06:21,120 --> 00:06:24,040 比如像磁盘 这是外存 129 00:06:24,080 --> 00:06:27,480 磁盘的访问有扇区编号 130 00:06:27,520 --> 00:06:32,360 每一个扇区是512个字节最小单位 131 00:06:32,400 --> 00:06:37,120 那这是你能够读写存储的最基本的内容 132 00:06:37,160 --> 00:06:42,560 而写程序的时候我们希望看到的情况是什么 133 00:06:42,600 --> 00:06:46,480 是我有若干个进程 每一个进程 134 00:06:46,520 --> 00:06:50,120 它们都有共同的一部分的地址空间 135 00:06:50,160 --> 00:06:52,520 是操作系统的内核 136 00:06:52,560 --> 00:06:56,080 然后每一个应用程序自己又是不一样的 137 00:06:56,120 --> 00:06:58,680 它们各自有各自的内容 138 00:06:58,720 --> 00:07:01,160 我希望在各自写这些内容的时候 139 00:07:01,200 --> 00:07:03,920 它们的地址是可以重叠的 140 00:07:03,960 --> 00:07:05,920 相互之间是不干扰的 141 00:07:05,960 --> 00:07:08,320 这是我们希望见到的状态 142 00:07:08,360 --> 00:07:11,800 把下面这种内存的状态 143 00:07:11,840 --> 00:07:15,680 转变成我们上面逻辑的理想状态 144 00:07:15,720 --> 00:07:19,240 我们在中间加了一层存储管理单元 145 00:07:19,280 --> 00:07:22,120 存储管理单元就把逻辑地址空间 146 00:07:22,160 --> 00:07:25,240 转变成物理地址空间 这个时候说 147 00:07:25,280 --> 00:07:28,440 我实际操作系统代码存在哪呢 存在这头上 148 00:07:28,480 --> 00:07:31,320 通常情况下是在内存里头的 149 00:07:31,360 --> 00:07:33,080 而进程的地址空间 150 00:07:33,120 --> 00:07:36,080 随着它们运行的转换 151 00:07:36,120 --> 00:07:37,800 有些是在内存里头 152 00:07:37,840 --> 00:07:40,920 有些是放在外存里头的 153 00:07:40,960 --> 00:07:42,240 这个转换的过程 154 00:07:42,280 --> 00:07:46,560 由中间的存储管理单元来完成 155 00:07:46,600 --> 00:07:49,720 如果说我们能做到这样一步 实际就相当于 156 00:07:49,760 --> 00:07:53,320 存储管理要达到效果是抽象 157 00:07:53,360 --> 00:07:57,160 把线性的物理地址编号 158 00:07:57,200 --> 00:08:01,080 转变成抽象的逻辑地址空间 159 00:08:01,120 --> 00:08:05,880 然后我需要在这里头对地址空间进行保护 160 00:08:05,920 --> 00:08:09,840 每一个进程只能访问自己的空间 161 00:08:09,880 --> 00:08:13,160 尽管说在内存里它们是相邻存放的 162 00:08:13,200 --> 00:08:16,160 与此同时我们还要方便共享 163 00:08:16,200 --> 00:08:18,680 比如说在我们这里头大家可以看到的 164 00:08:18,720 --> 00:08:20,360 操作系统的内核的代码 165 00:08:20,400 --> 00:08:22,280 是各个进程都是一样的 166 00:08:22,320 --> 00:08:25,560 或者说绝大部分是一样的 这种一致 167 00:08:25,600 --> 00:08:28,840 如果说每个进程地址空间是相互保护的 168 00:08:28,880 --> 00:08:31,120 不能访问 这段你就得存多份 169 00:08:31,160 --> 00:08:33,000 这个效率是低的 170 00:08:33,040 --> 00:08:38,080 我们希望能够很好地把保护和共享统一起来 171 00:08:38,120 --> 00:08:41,240 这个目标是有一些矛盾的 172 00:08:41,280 --> 00:08:45,520 与此同时我们还希望它实现更好的虚拟化 173 00:08:45,560 --> 00:08:47,720 这说的是我们每个进程的 174 00:08:47,760 --> 00:08:51,040 地址空间编号都是一样的 175 00:08:51,080 --> 00:08:52,880 但是实际上每个进程 176 00:08:52,920 --> 00:08:55,440 都有自己一段用户地址空间 177 00:08:55,480 --> 00:08:57,640 到这里实际上物理地址空间 178 00:08:57,680 --> 00:08:59,240 存的位置是不一样的 179 00:08:59,280 --> 00:09:02,400 但是给每个进程看到的都是 180 00:09:02,440 --> 00:09:07,280 一个区域一致的一个地址空间 181 00:09:07,320 --> 00:09:10,120 甚至于说我们在逻辑地址空间里 182 00:09:10,160 --> 00:09:12,400 看到的可以存数据的地方 183 00:09:12,440 --> 00:09:17,160 它的大小是大于你的物理内存的总量的 184 00:09:17,200 --> 00:09:20,680 我们看到实际上要想实现存储管理的 185 00:09:20,720 --> 00:09:23,600 抽象 保护 共享和虚拟化 186 00:09:23,640 --> 00:09:27,000 实际上这几个目标还是很有挑战的 187 00:09:27,040 --> 00:09:28,080 具体说起来 188 00:09:28,120 --> 00:09:29,000 我们在操作系统里 189 00:09:29,040 --> 00:09:32,520 可能采用一些什么样办法 190 00:09:32,560 --> 00:09:36,400 我们第一个办法就是重定位 191 00:09:36,440 --> 00:09:38,600 实际上在最早的计算机系统当中 192 00:09:38,640 --> 00:09:41,520 它是直接使用总线上的 193 00:09:41,560 --> 00:09:44,080 物理地址来写你的程序 194 00:09:44,120 --> 00:09:48,440 我要想读写某个内存单元 它在什么位置 195 00:09:48,480 --> 00:09:51,960 我们在程序里见到的就是它的物理地址 196 00:09:52,000 --> 00:09:54,320 但是这种做法实际上有很大局限 197 00:09:54,360 --> 00:09:59,800 你写程序只能在指定类型的机器上运行 198 00:09:59,840 --> 00:10:01,560 我们第一个可以让它做灵活 199 00:10:01,600 --> 00:10:03,880 实际上相当于我可以整块的搬 200 00:10:03,920 --> 00:10:06,280 就是我们这里说的重定位 201 00:10:06,320 --> 00:10:09,800 如果说我们现在看到地址访问的时候 202 00:10:09,840 --> 00:10:12,720 说每一个地址是用一个段地址 203 00:10:12,760 --> 00:10:15,280 加一个偏移来表示 204 00:10:15,320 --> 00:10:18,120 实际上就是从重定位这个地方来的 205 00:10:18,160 --> 00:10:20,160 有了重定位之后我通过移 206 00:10:20,200 --> 00:10:23,560 我只需要改我的段寄存器的地址 207 00:10:23,600 --> 00:10:25,880 这个程序就能运行了 208 00:10:25,920 --> 00:10:28,720 这是第一种做法 为了实现它 209 00:10:28,760 --> 00:10:31,280 我在程序和操作系统里头 210 00:10:31,320 --> 00:10:33,720 都需要有相应的支持 211 00:10:33,760 --> 00:10:36,200 这些支持我后面会来说 212 00:10:36,240 --> 00:10:40,120 接下来一个问题是说我们在这里头 213 00:10:40,160 --> 00:10:41,000 在重定位的时候 214 00:10:41,040 --> 00:10:43,560 我一个进程分的存储空间 215 00:10:43,600 --> 00:10:45,720 是一个连续的空间 216 00:10:45,760 --> 00:10:48,080 我不可以把两个交错起来 217 00:10:48,120 --> 00:10:50,000 实际上这是一个很大的限制 218 00:10:50,040 --> 00:10:53,040 我们希望它能够不连续 219 00:10:53,080 --> 00:10:55,000 实际上我们在写程序的时候 220 00:10:55,040 --> 00:10:56,120 它的逻辑结构 221 00:10:56,160 --> 00:10:59,600 它并不是一个必须连成一片的区域 222 00:10:59,640 --> 00:11:05,280 而是说我们把程序分成数据 代码 堆栈 223 00:11:05,320 --> 00:11:08,320 这三个部分是相对独立的 224 00:11:08,360 --> 00:11:10,240 它不会说我从堆栈里头 225 00:11:10,280 --> 00:11:13,040 直接去访问代码段里的内容 226 00:11:13,080 --> 00:11:14,320 也很少有这种情况 227 00:11:14,360 --> 00:11:17,800 我从代码段里直接去访问数据段的内容 228 00:11:17,840 --> 00:11:18,760 依据这种情况 229 00:11:18,800 --> 00:11:22,960 我至少可以把代码 数据和堆栈分成三块 230 00:11:23,000 --> 00:11:25,120 每一块我要的空间就会变少了 231 00:11:25,160 --> 00:11:28,520 这就是我们这里的分段 232 00:11:28,560 --> 00:11:31,000 分段仍然你是需要一段的内容 233 00:11:31,040 --> 00:11:32,560 还是需要连续的 234 00:11:32,600 --> 00:11:34,960 这个要求仍然足够高 235 00:11:35,000 --> 00:11:37,600 接下来我们会说分页 236 00:11:37,640 --> 00:11:42,720 分页实际上就是把内存分成最基本的单位 237 00:11:42,760 --> 00:11:46,640 就好比说我们要盖一栋楼 238 00:11:46,680 --> 00:11:49,640 盖一栋楼需要各种各样的建筑材料 239 00:11:49,680 --> 00:11:51,040 但是我们用到的最基本的 240 00:11:51,080 --> 00:11:53,400 就是一块一块的方砖 241 00:11:53,440 --> 00:11:55,200 你可以把它加在一起之后 242 00:11:55,240 --> 00:11:57,280 变成你需要的各种各样的形状 243 00:11:57,320 --> 00:12:02,040 我们也希望是从最小的单位 一页 244 00:12:02,080 --> 00:12:04,560 来构建你所需要存储区域 245 00:12:04,600 --> 00:12:06,400 当然你说我们最小的单位 246 00:12:06,440 --> 00:12:08,600 那就用一个字节不就挺好的吗 247 00:12:08,640 --> 00:12:11,600 但是实际上这个时候你一个字节的话 248 00:12:11,640 --> 00:12:16,360 你在访问的时候 它开销就力度太细 249 00:12:16,400 --> 00:12:20,400 以至于管理的时候难度很高 250 00:12:20,440 --> 00:12:22,840 所以我们在这要选一个合适的大小 251 00:12:22,880 --> 00:12:26,600 这一块最基本的单位是一个连续区域 252 00:12:26,640 --> 00:12:29,840 这就是我们这里的页 基于这个来构造 253 00:12:29,880 --> 00:12:32,360 你所需要的存储空间的内容 254 00:12:32,400 --> 00:12:33,520 在这个基础之上 255 00:12:33,560 --> 00:12:36,360 我们希望把数据存到硬盘上 256 00:12:36,400 --> 00:12:39,640 而硬盘外存上的数据和内存上的数据 257 00:12:39,680 --> 00:12:43,880 它们俩之间的倒换是由操作系统内部来实现 258 00:12:43,920 --> 00:12:46,760 这个时候就希望看到的是 259 00:12:46,800 --> 00:12:48,840 程序是一个逻辑的地址空间 260 00:12:48,880 --> 00:12:50,000 甚至这个逻辑地址空间 261 00:12:50,040 --> 00:12:52,280 是大于物理内存的空间 262 00:12:52,320 --> 00:12:54,480 那这就是我们的虚拟存储了 263 00:12:54,520 --> 00:12:55,480 264 00:12:55,520 --> 00:12:58,000 在我们实际上操作系统里 265 00:12:58,040 --> 00:13:02,640 基本上就是这样几种方式来管理内存 266 00:13:02,680 --> 00:13:05,960 当然我们说所有这些管理办法 267 00:13:06,000 --> 00:13:08,800 它对硬件的依赖程度都是非常高的 268 00:13:08,840 --> 00:13:11,200 比如说MMU 269 00:13:11,240 --> 00:13:14,280 存储管理单元里的结构是什么样的 270 00:13:14,320 --> 00:13:18,440 然后我们CPU能够识别的页表是什么样的 271 00:13:18,480 --> 00:13:22,400 这些都直接影响到你存储管理方式的实现 272 00:13:22,440 --> 00:13:23,600 273 00:13:23,640 --> 00:13:28,440 到这我们对内存管理的基本情况有一个介绍 274 00:13:28,480 --> 00:13:29,080 275 00:13:29,120 --> 00:13:29,120