0 00:00:00,000 --> 00:00:07,000 1 00:00:07,280 --> 00:00:11,200 接下来我们介绍虚拟页式存储 2 00:00:11,280 --> 00:00:13,680 那么在前面呢我们已经说过了 3 00:00:13,720 --> 00:00:16,720 虚拟存储它的基本概念是什么样的 4 00:00:16,760 --> 00:00:18,600 它的基本思路是啥样的 5 00:00:18,640 --> 00:00:22,200 那我们把它置换的 6 00:00:22,240 --> 00:00:24,920 交换的这个单位设置为页之后 7 00:00:24,960 --> 00:00:27,280 就变成我们这里的虚拟页式存储 8 00:00:27,320 --> 00:00:29,400 那它的基本思路很简单 9 00:00:29,440 --> 00:00:32,040 就是在页式存储管理的基础上 10 00:00:32,080 --> 00:00:35,840 增加一个请求调页和页面置换算法 11 00:00:35,880 --> 00:00:39,560 那这类的做法呢它具体做起来的时候呢 12 00:00:39,600 --> 00:00:42,840 我们在怎么做呢 它思路是这样的 13 00:00:42,880 --> 00:00:48,680 首先在程序加载的时候它只加载一部分页面 14 00:00:48,720 --> 00:00:50,080 那这个和我们前面的 15 00:00:50,120 --> 00:00:51,800 页式存储管理不一样地方 16 00:00:51,840 --> 00:00:53,800 页式存储管理呢它加载的时候 17 00:00:53,840 --> 00:00:56,320 是把所有的都加载到内存当中 18 00:00:56,360 --> 00:00:59,560 只是它可以实现存储的不连续 19 00:00:59,600 --> 00:01:03,360 你可以找到相应的页面 有空闲页面就足够了 20 00:01:03,400 --> 00:01:05,120 那你这样一来的话 21 00:01:05,160 --> 00:01:09,440 在执行的过程当中它就会出现缺页异常 22 00:01:09,480 --> 00:01:11,480 那在原来的页式管理里头呢 23 00:01:11,520 --> 00:01:14,320 它也会出现异常但不会出现 24 00:01:14,360 --> 00:01:16,520 这里所说的这个缺页 25 00:01:16,560 --> 00:01:21,880 因为我这个进程地址空间里头任何一个页面 26 00:01:21,920 --> 00:01:24,360 都有对应的一个物理页面存在 27 00:01:24,400 --> 00:01:25,840 好 这是跟原来不一样的 28 00:01:25,880 --> 00:01:29,120 好 如果出现这种情况那怎么处理呢 29 00:01:29,160 --> 00:01:33,240 操作系统里头要加一个缺页异常的处理 30 00:01:33,280 --> 00:01:37,640 将相对应的在外存当中页面呢调到内存当中来 31 00:01:37,680 --> 00:01:39,760 并且把相应的页表项进行修改 32 00:01:39,800 --> 00:01:43,720 修改完毕之后以便于我可以重新执行这条指令 33 00:01:43,760 --> 00:01:45,600 那这样的话这条指令就能继续下去了 34 00:01:45,640 --> 00:01:48,360 那随着这个执行的过程呢 那我在内存里呢 35 00:01:48,400 --> 00:01:51,680 只有一部分内存进程的地址空间内容 36 00:01:51,720 --> 00:01:54,360 在内存 但是整个进程它是可以一直这样 37 00:01:54,400 --> 00:01:56,800 良好的运行下去 那关键的问题 38 00:01:56,840 --> 00:02:03,080 就在这个地方有个缺异常的处理服务例程 39 00:02:03,120 --> 00:02:07,640 那这是我们在虚拟页式存储当中的地址转换 40 00:02:07,680 --> 00:02:09,000 如果大家看这张表的话 41 00:02:09,040 --> 00:02:12,280 这和我们前面页式存储的地址转换 42 00:02:12,320 --> 00:02:15,800 是完全一样的 说 这是逻辑地址空间 43 00:02:15,840 --> 00:02:18,000 这是物理地址空间 44 00:02:18,040 --> 00:02:20,760 然后我在某一条指令访问的时候 45 00:02:20,800 --> 00:02:24,640 有一个操作数是页号加页内偏移 46 00:02:24,680 --> 00:02:28,240 然后页号加页内偏移呢通过这边转换之后 47 00:02:28,280 --> 00:02:31,560 到页表 页表找到相应的页表项 48 00:02:31,600 --> 00:02:33,960 如果说是页式存储管理 49 00:02:34,000 --> 00:02:35,960 这里对应的页表项 50 00:02:36,000 --> 00:02:40,120 一定会是有一个物理页号 页帧号 51 00:02:40,160 --> 00:02:43,320 然后找了页帧号呢把它的页内偏移加过来 52 00:02:43,360 --> 00:02:47,480 这就能找到相应的物理内存单元的内容了 53 00:02:47,520 --> 00:02:50,080 好 如果说在前面加上虚拟怎么办呢 54 00:02:50,120 --> 00:02:52,440 加上虚拟之后这个地方呢 55 00:02:52,480 --> 00:02:55,240 你在这个地方就会在页表里加一个 56 00:02:55,280 --> 00:02:58,880 多加标志位 这个标志位呢会表示 57 00:02:58,920 --> 00:03:02,320 我对应的这一页是否在物理内存里头 58 00:03:02,360 --> 00:03:04,720 如果不在这条路就走不下去了 59 00:03:04,760 --> 00:03:06,880 好 这条路走不下去之后呢 60 00:03:06,920 --> 00:03:10,200 执行到这儿呢它就会产生缺页异常 61 00:03:10,240 --> 00:03:15,440 然后 这个缺页异常呢 就会由操作系统来接管 62 00:03:15,480 --> 00:03:19,000 操作系统要做的事情呢是找页把它写好 63 00:03:19,040 --> 00:03:21,200 然后把这个位变成有效 64 00:03:21,240 --> 00:03:23,200 OK 这一件事情就算过去了 65 00:03:23,240 --> 00:03:27,000 这是在虚拟页式存储管理当中的地址转换 66 00:03:27,040 --> 00:03:30,520 我们看到变化呢 从整张图上看变化很小 67 00:03:30,560 --> 00:03:35,920 但实际上这些小点的变化呢会导致很多的修改 68 00:03:35,960 --> 00:03:40,480 好 那这时候一种修改呢是在页表项里头 69 00:03:40,520 --> 00:03:46,600 原来的我们页表项是从以逻辑页号为序号 70 00:03:46,640 --> 00:03:49,280 找到的呢就是物理页帧号 71 00:03:49,320 --> 00:03:50,600 有了这个物理页帧号之后 72 00:03:50,640 --> 00:03:53,000 我就能转换出相应的物理地址来了 73 00:03:53,040 --> 00:03:55,200 但是在虚拟页式之后 74 00:03:55,240 --> 00:03:58,520 虚拟呢我们就会加上一些标志位 75 00:03:58,560 --> 00:04:01,440 这是在虚拟存储管理里头 76 00:04:01,480 --> 00:04:03,320 需要用到的几个标志位 77 00:04:03,360 --> 00:04:04,800 分别的含义是什么呢 78 00:04:04,840 --> 00:04:07,520 首先第一个有一个叫驻留位 79 00:04:07,560 --> 00:04:11,400 它是表示该页面是否在内存当中 80 00:04:11,440 --> 00:04:15,200 那如果说是一 表示在内存当中这时候呢 81 00:04:15,240 --> 00:04:17,800 对应过来一定可以找到它的页帧号 82 00:04:17,840 --> 00:04:22,280 那可以转换成实实在在的物理内存单元的地址 83 00:04:22,320 --> 00:04:25,280 好 如果它是零 表示这一页呢在外存里头 84 00:04:25,320 --> 00:04:28,160 那这时候就会导致缺页 85 00:04:28,200 --> 00:04:29,880 那导致缺页之后呢 86 00:04:29,920 --> 00:04:32,280 就会来修改这些标志位 87 00:04:32,320 --> 00:04:33,720 好 这是第一个标志 88 00:04:33,760 --> 00:04:36,000 第二个标志是修改位 89 00:04:36,040 --> 00:04:39,640 表示这一页在物理内存当中有的这一页 90 00:04:39,680 --> 00:04:43,200 这必须是驻留位有效的情况下 91 00:04:43,240 --> 00:04:45,680 好这一页如果被修改过 92 00:04:45,720 --> 00:04:48,000 那这时候呢它会有什么变化呢 93 00:04:48,040 --> 00:04:52,840 如果说我想把这一页淘汰放到外存里头去 94 00:04:52,880 --> 00:04:55,240 那么这时候我必须把内存当中修改的内容 95 00:04:55,280 --> 00:04:57,080 写回到外存当中 96 00:04:57,120 --> 00:04:59,360 如果说这个地方没有修改过 97 00:04:59,400 --> 00:05:02,800 那这时候呢对应在外存单元里头有相应的内容 98 00:05:02,840 --> 00:05:05,480 那这时候我在替换的时候 99 00:05:05,520 --> 00:05:08,960 置换的时候我只需要把这一页作废就行了 100 00:05:09,000 --> 00:05:12,800 这是修改位 然后还有一个呢是访问位 101 00:05:12,840 --> 00:05:15,400 表示这一页在过去一段时间里头 102 00:05:15,440 --> 00:05:17,000 是否被访问过 103 00:05:17,040 --> 00:05:20,040 因为我们在虚拟页式存储管理里头 104 00:05:20,080 --> 00:05:23,880 它需要有一个置换算法把不常用的页面 105 00:05:23,920 --> 00:05:25,560 置换到外存当中去 106 00:05:25,600 --> 00:05:27,840 那么哪些常用哪些不常用呢 107 00:05:27,880 --> 00:05:30,960 就是靠这个访问位来进行统计 108 00:05:31,000 --> 00:05:34,000 访问位访问过它是一 没访问过它是零 109 00:05:34,040 --> 00:05:35,680 那这样的话它就在一定程度上 110 00:05:35,720 --> 00:05:40,160 近似统计出来这一页是否被经常访问 111 00:05:40,200 --> 00:05:45,200 一表示经常访问 零表示不经常访问 112 00:05:45,240 --> 00:05:47,480 还有一个呢保护位 113 00:05:47,520 --> 00:05:50,960 这个保护位是用来表示这一页允许访问的方式 114 00:05:51,000 --> 00:05:55,080 你比如说 只读 可读 可写 可执行等等 115 00:05:55,120 --> 00:06:00,480 这样一些信息 好 那有了前面地址转换 116 00:06:00,520 --> 00:06:03,080 和页表上的修改这件事情从道理上来说 117 00:06:03,120 --> 00:06:05,920 我的虚拟页式存储管理就算说清楚了 118 00:06:05,960 --> 00:06:07,600 那么下面我们先通过一个例子 119 00:06:07,640 --> 00:06:10,040 来说明一下这个过程 120 00:06:10,080 --> 00:06:12,880 假定我有一个16位的逻辑地址 121 00:06:12,920 --> 00:06:18,280 然后这个时候呢它的逻辑地址空间呢是64K 122 00:06:18,320 --> 00:06:21,280 然后物理内存我只有32K 123 00:06:21,320 --> 00:06:25,840 页面大小是4K 好 这时候我划分出来的情况呢 124 00:06:25,880 --> 00:06:29,800 这是逻辑地址空间 这是物理地址空间 125 00:06:29,840 --> 00:06:34,600 好 那么这时候说我在这里做映射怎么做呢 126 00:06:34,640 --> 00:06:37,840 我哪些映射到物理内存里头来的 127 00:06:37,880 --> 00:06:41,400 我在这里有相应的编号 没有映射过来的 128 00:06:41,440 --> 00:06:43,640 我的简化表是这里写的是X 129 00:06:43,680 --> 00:06:45,720 好 然后这样以来的话 130 00:06:45,760 --> 00:06:49,480 它们就建立一张表 对应过来这里写的是几7 131 00:06:49,520 --> 00:06:52,200 对应到物理单元的帧号是7 132 00:06:52,240 --> 00:06:56,080 好 那这样的话我这里的8个物理页面 133 00:06:56,120 --> 00:06:58,680 和16个逻辑页面之间 134 00:06:58,720 --> 00:07:01,880 有一半有对应关系 这是在页表项里有的 135 00:07:01,920 --> 00:07:04,080 好 那么这时候呢这里的X 136 00:07:04,120 --> 00:07:10,320 隐含着你的页表项里头的驻留位是零 137 00:07:10,360 --> 00:07:13,800 然后这个7呢隐含着你的驻留位是1 138 00:07:13,840 --> 00:07:16,800 那这个对应过来是驻留位的表示 139 00:07:16,840 --> 00:07:21,680 好 那么在这种情况下我们看我执行一条指令 140 00:07:21,720 --> 00:07:26,440 MOV把指定存储单元内容移到寄存器里头去 141 00:07:26,480 --> 00:07:29,520 好 那这时候说我在这边看对应的这一页 142 00:07:29,560 --> 00:07:33,800 在这个地方对应过来到这个地方 好 它是有的 143 00:07:33,840 --> 00:07:36,560 那这一条它是正常能访问过来 144 00:07:36,600 --> 00:07:38,680 好 我们再来一条指令 145 00:07:38,720 --> 00:07:42,960 我这个MOV 32到这个地方就是x 146 00:07:43,000 --> 00:07:45,640 不存在 好 这时候会产生什么情况 147 00:07:45,680 --> 00:07:47,200 这地方就是缺页 148 00:07:47,240 --> 00:07:50,880 缺页之后我就需要把现在在内存里某一页去掉 149 00:07:50,920 --> 00:07:53,040 然后把它对应内容写到内存里头 150 00:07:53,080 --> 00:07:54,080 并且改这一项 151 00:07:54,120 --> 00:07:56,880 OK我后面事情就可以做了 152 00:07:56,920 --> 00:08:02,160 以我们现在最常用的X86 32的 153 00:08:02,200 --> 00:08:04,800 CPU的页表结构来作为实例 154 00:08:04,840 --> 00:08:08,360 那么在32位的X86系统当中呢 155 00:08:08,400 --> 00:08:12,600 它有12位的页内偏移 156 00:08:12,640 --> 00:08:16,360 然后有2个10位的二级页表项 157 00:08:16,400 --> 00:08:18,560 这样的话是32位地址 158 00:08:18,600 --> 00:08:21,400 然后物理地址呢也是32位的 159 00:08:21,440 --> 00:08:24,000 那20位呢是物理页帧号 160 00:08:24,040 --> 00:08:27,640 好 然后这时候呢 它使用一个二级页表 161 00:08:27,680 --> 00:08:29,520 那这是它页表结构 162 00:08:29,560 --> 00:08:32,960 然后页表项的起始地址呢是CR3 163 00:08:33,000 --> 00:08:35,600 CPU里一个寄存器指出来 164 00:08:35,640 --> 00:08:36,760 然后从这儿呢 165 00:08:36,800 --> 00:08:42,560 我一个页表项占4字节 那一页由于是32位的 166 00:08:42,600 --> 00:08:46,440 好 那占4个字节呢 那么这4K为一页 167 00:08:46,480 --> 00:08:49,920 那这时候呢这12位是4K 4K为一页 168 00:08:49,960 --> 00:08:54,080 一页里头有1024项 1024个页表项 169 00:08:54,120 --> 00:08:56,320 那么正好对应着我这里的10位 170 00:08:56,360 --> 00:09:00,600 好 那这是第一级 然后第二级 这是它的页表 171 00:09:00,640 --> 00:09:03,560 那我们的地址转换怎么过来呢 172 00:09:03,600 --> 00:09:08,120 先是一级页表项里头的页号到这儿 173 00:09:08,160 --> 00:09:12,200 作为它的偏移找到相应的页表项 174 00:09:12,240 --> 00:09:15,160 一级页表项 好 这个页表项里头它有一个 175 00:09:15,200 --> 00:09:17,400 第二级页表项的物理页号 176 00:09:17,440 --> 00:09:18,800 好 然后这个时候呢 177 00:09:18,840 --> 00:09:23,800 再加上你第二级的页号 好 那从这儿呢 178 00:09:23,840 --> 00:09:25,120 第二级页表项里头呢 179 00:09:25,160 --> 00:09:29,320 以它页号作为偏移找到相应的页表项 180 00:09:29,360 --> 00:09:32,040 那这时候就是你实实在在要访问的 181 00:09:32,080 --> 00:09:34,960 物理页面的物理帧号 182 00:09:35,000 --> 00:09:38,400 好 那这个帧号呢 和你的偏移搁在一起 183 00:09:38,440 --> 00:09:42,440 把页内偏移加在一起这得到你的物理地址 184 00:09:42,480 --> 00:09:46,120 那么这是在这个结构当中它的页表的结构 185 00:09:46,160 --> 00:09:48,880 这和我们前面讲到的页式存储呢 186 00:09:48,920 --> 00:09:51,920 是完全一样的 那它变化地方在哪呢 187 00:09:51,960 --> 00:09:54,800 变化地方是页表项里头的东西 188 00:09:54,840 --> 00:10:01,960 那这地方是X86 32在以4K为页面大小的时候 189 00:10:02,000 --> 00:10:07,280 它的页表项的定义格式 那我们在这里关心的 190 00:10:07,320 --> 00:10:10,200 这是20位的物理页帧号 191 00:10:10,240 --> 00:10:11,200 这个没有什么变化 192 00:10:11,240 --> 00:10:13,480 我们需要关心后面这一段的标志 193 00:10:13,520 --> 00:10:15,920 那我们刚才在逻辑示意当中呢 194 00:10:15,960 --> 00:10:17,120 已经看到有几个标志 195 00:10:17,160 --> 00:10:19,440 在这儿呢实际上看到的标志更多 196 00:10:19,480 --> 00:10:24,360 那需要这几个是能对上的驻留位 可写位 197 00:10:24,400 --> 00:10:26,720 这是相当于权限 好 在这里头 198 00:10:26,760 --> 00:10:30,680 我们可能在前面没注意到就是用户态标志U 199 00:10:30,720 --> 00:10:32,440 实际上这表示我这个页表项 200 00:10:32,480 --> 00:10:34,480 是否可以在用户态访问 201 00:10:34,520 --> 00:10:37,120 那在内核地址空间呢 202 00:10:37,160 --> 00:10:40,280 只能有内核的状态的时候才能访问 203 00:10:40,320 --> 00:10:42,160 那这是一个权限的控制 204 00:10:42,200 --> 00:10:44,240 好 然后访问位 修改位 205 00:10:44,280 --> 00:10:46,840 然后跟我们前面不一样的地方有几个 206 00:10:46,880 --> 00:10:51,200 一个是保留位 那在这里头呢这标志位没用完 207 00:10:51,240 --> 00:10:52,840 它总会留有一些保留 208 00:10:52,880 --> 00:10:58,080 原因在于我们如果在32位的X86系统当中 209 00:10:58,120 --> 00:11:00,080 这个页表项是好用的 210 00:11:00,120 --> 00:11:03,040 但实际上呢我们在实际的系统当中 211 00:11:03,080 --> 00:11:06,160 它的物理地址空间呢 它是在不断变化的 212 00:11:06,200 --> 00:11:08,600 好 你比如说我32位的系统 213 00:11:08,640 --> 00:11:11,600 它最大的物理内存地址空间呢是4个G 214 00:11:11,640 --> 00:11:14,920 实际上我们现在用到的一些32位的系统 215 00:11:14,960 --> 00:11:18,760 它已经不是这个4个G了 216 00:11:18,800 --> 00:11:20,760 大于4个G怎么办 好 那这个时候 217 00:11:20,800 --> 00:11:23,400 相应的页表项就得跟着这个在变 218 00:11:23,440 --> 00:11:26,960 那这些呢都是为了后续的这些改动 219 00:11:27,000 --> 00:11:29,120 留有空间的 如果说你去看 220 00:11:29,160 --> 00:11:30,840 实际的因特尔的手册 221 00:11:30,880 --> 00:11:33,720 那么这里页表项结构呢有很多种 222 00:11:33,760 --> 00:11:35,600 那么它们很多是兼容的 223 00:11:35,640 --> 00:11:37,680 但是这些变化都从这儿出来的 224 00:11:37,720 --> 00:11:40,880 然后还有两个是这个地方 225 00:11:40,920 --> 00:11:43,800 就是缓存是否有效 是否写通 226 00:11:43,840 --> 00:11:48,120 实际上这里说的呢是在内存和CPU之间 227 00:11:48,160 --> 00:11:49,680 有一个高速缓存 228 00:11:49,720 --> 00:11:52,240 这个缓存呢我在读写数据的时候 229 00:11:52,280 --> 00:11:55,440 会把写出的数据先写到缓存里头 230 00:11:55,480 --> 00:11:58,840 然后高速缓存再慢慢地把它写到内存里头 231 00:11:58,880 --> 00:12:02,080 那如果说我要有一些时效性的操作 232 00:12:02,120 --> 00:12:03,720 比如说对I/O端口的操作 233 00:12:03,760 --> 00:12:06,040 那这种缓存是会影响我的语意的 234 00:12:06,080 --> 00:12:08,400 好 那这儿是有这个标志来做控制 235 00:12:08,440 --> 00:12:10,600 然后再有一些呢我在读的时候 236 00:12:10,640 --> 00:12:13,280 是否高速缓存有效 237 00:12:13,320 --> 00:12:15,920 如果说你前面读了一次 那这时候呢 238 00:12:15,960 --> 00:12:17,360 我后面再读的时候我可以直接 239 00:12:17,400 --> 00:12:19,360 从高速缓存里得到这个数据 240 00:12:19,400 --> 00:12:23,080 但实际上呢如果说这个数据它是时时在变的 241 00:12:23,120 --> 00:12:24,800 好 你第一次I/O端口里读到的数据 242 00:12:24,840 --> 00:12:27,560 第二次再读的时候你必须要从I/O端口里 243 00:12:27,600 --> 00:12:29,480 实实在在去读 那么这时候呢 244 00:12:29,520 --> 00:12:31,880 这些标志位都会影响到它的行为 245 00:12:31,920 --> 00:12:33,200 所以从这儿来看呢 246 00:12:33,240 --> 00:12:37,400 我们看到这个基本的原理里的虚拟页式存储 247 00:12:37,440 --> 00:12:41,240 和我们实际系统当中的还是会有一些差别 248 00:12:41,280 --> 00:12:43,680 这些差别转换到我们实际的 249 00:12:43,720 --> 00:12:45,200 实验系统代码里头代码里头 250 00:12:45,240 --> 00:12:47,760 这些差别就会导致我们代码会有很大的变化 251 00:12:47,800 --> 00:12:53,080 252 00:12:53,120 --> 00:12:53,920 253 00:12:53,960 --> 00:12:54,360 254 00:12:54,400 --> 00:12:54,720 255 00:12:54,760 --> 00:12:54,800