0 00:00:00,000 --> 00:00:06,920 1 00:00:06,960 --> 00:00:10,320 下面我们来讨论文件缓存 2 00:00:10,360 --> 00:00:14,000 和打开文件管理 3 00:00:14,040 --> 00:00:16,360 文件缓存呢是指 4 00:00:16,400 --> 00:00:20,320 我们从磁盘上读数据到内存 5 00:00:20,360 --> 00:00:22,200 甚至于到CPU使用 6 00:00:22,240 --> 00:00:24,560 中间有多种缓存 7 00:00:24,600 --> 00:00:26,320 我们先看看 这在哪些地方 8 00:00:26,360 --> 00:00:27,960 都有可能有缓存 9 00:00:28,000 --> 00:00:31,000 首先呢我们是在磁盘上有数据 10 00:00:31,040 --> 00:00:33,760 然后磁盘上通过磁盘控制器 11 00:00:33,800 --> 00:00:37,120 来完成对磁盘上扇区的读写 12 00:00:37,160 --> 00:00:39,440 在这个磁盘控制器上头呢 13 00:00:39,480 --> 00:00:42,520 就有扇区的缓存 14 00:00:42,560 --> 00:00:45,600 基于这个再往上呢 是内存 15 00:00:45,640 --> 00:00:49,160 内存里头呢 我们有数据块的缓存 16 00:00:49,200 --> 00:00:52,120 同时还有一类虚拟磁盘 17 00:00:52,160 --> 00:00:53,960 用内存虚拟盘 18 00:00:54,000 --> 00:00:58,520 它用内存来虚拟一个逻辑的磁盘 19 00:00:58,560 --> 00:01:01,640 然后在这之上呢我们维护了 20 00:01:01,680 --> 00:01:06,120 每一个打开文件的打开文件表 21 00:01:06,160 --> 00:01:07,760 打开文件表里头的每一项呢 22 00:01:07,800 --> 00:01:11,240 对应着我这里的一个文件 23 00:01:11,280 --> 00:01:12,800 最后是到CPU 24 00:01:12,840 --> 00:01:16,680 在这里头我们看到 内存 磁盘控制器 25 00:01:16,720 --> 00:01:18,200 上头都有缓存 26 00:01:18,240 --> 00:01:20,760 在我们操作系统里讨论的缓存呢 27 00:01:20,800 --> 00:01:24,320 是在内存当中的数据块缓存 28 00:01:24,360 --> 00:01:27,560 我们从磁盘上读数据块到内存 29 00:01:27,600 --> 00:01:28,560 这地方的读呢 30 00:01:28,600 --> 00:01:30,160 通常情况下是按需进行的 31 00:01:30,200 --> 00:01:31,480 我有一个read 32 00:01:31,520 --> 00:01:33,000 在执行read操作的时候呢 33 00:01:33,040 --> 00:01:35,440 会把相应的一整块读到内存里来 34 00:01:35,480 --> 00:01:36,840 选择我所需要的 35 00:01:36,880 --> 00:01:39,160 给相应的进程拿去使用 36 00:01:39,200 --> 00:01:40,920 而在读的过程当中 37 00:01:40,960 --> 00:01:43,720 我也可以采取一定的预读机制 38 00:01:43,760 --> 00:01:44,960 我可以多读几块 39 00:01:45,000 --> 00:01:46,880 那么这时候呢我们认为 40 00:01:46,920 --> 00:01:50,360 数据块在使用之后会被缓存 41 00:01:50,400 --> 00:01:53,280 这种缓存的意思呢 在于说 42 00:01:53,320 --> 00:01:55,960 日后我可能这一块还会再用到 43 00:01:56,000 --> 00:01:57,160 如果出现这种情况 44 00:01:57,200 --> 00:01:59,920 那我就不用再去从磁盘上读了 45 00:01:59,960 --> 00:02:01,160 在写的时候呢 46 00:02:01,200 --> 00:02:02,880 也有可能这种写呢 47 00:02:02,920 --> 00:02:05,400 会被延迟到以后 48 00:02:05,440 --> 00:02:06,880 也就相当于 我先把它写到 49 00:02:06,920 --> 00:02:08,760 内存里的缓存里头 50 00:02:08,800 --> 00:02:11,080 然后后续再有修改的时候呢 51 00:02:11,120 --> 00:02:12,440 而我事先没有写 52 00:02:12,480 --> 00:02:13,880 那这样的话 我就可以把两个写 53 00:02:13,920 --> 00:02:15,520 合并到一起 来往下写 54 00:02:15,560 --> 00:02:17,920 当然这种你合并之后写呢 55 00:02:17,960 --> 00:02:19,920 有一种风险 就是可能在你 56 00:02:19,960 --> 00:02:22,080 前一个写没有进行 57 00:02:22,120 --> 00:02:24,600 第二个写也没进行的时候 58 00:02:24,640 --> 00:02:25,960 系统出了故障了 59 00:02:26,000 --> 00:02:26,920 那么你头一个 60 00:02:26,960 --> 00:02:28,680 本来认为它已经正常写进去的 61 00:02:28,720 --> 00:02:30,560 也就可能会丢掉了 62 00:02:30,600 --> 00:02:31,480 有了这个之后 那我们看 63 00:02:31,520 --> 00:02:33,720 这个缓存我们怎么来控制 64 00:02:33,760 --> 00:02:37,080 在这里呢有两种缓存机制 65 00:02:37,120 --> 00:02:39,320 一种呢是数据块缓存 66 00:02:39,360 --> 00:02:40,880 我读磁盘上的东西 67 00:02:40,920 --> 00:02:42,640 我放内存里 我标记这一块 68 00:02:42,680 --> 00:02:44,360 内存的东西是磁盘的缓存 69 00:02:44,400 --> 00:02:47,480 以后你要去读磁盘我先查这个地方 70 00:02:47,520 --> 00:02:50,920 还有一种缓存机制呢是页缓存 71 00:02:50,960 --> 00:02:52,720 从我们刚才这种讨论 72 00:02:52,760 --> 00:02:54,960 大家已经感觉到了 73 00:02:55,000 --> 00:02:56,840 在虚拟存储里头 我们会把 74 00:02:56,880 --> 00:02:58,880 物理内存不够用的地方 75 00:02:58,920 --> 00:03:00,560 放到外存里头 76 00:03:00,600 --> 00:03:02,200 实际上 数据块的缓存呢 77 00:03:02,240 --> 00:03:05,400 你可以理解为是我把磁盘上的东西 78 00:03:05,440 --> 00:03:08,560 在内存里做一个反向的缓存 79 00:03:08,600 --> 00:03:09,480 从这个角度来讲 80 00:03:09,520 --> 00:03:11,560 这两者之间 有很强的 81 00:03:11,600 --> 00:03:13,400 关联性和相似性 82 00:03:13,440 --> 00:03:14,120 所以我们可以把 83 00:03:14,160 --> 00:03:16,400 这两种机制统一到一起 84 00:03:16,440 --> 00:03:18,840 具体我们来看一下这两种机制 85 00:03:18,880 --> 00:03:21,080 首先是磁盘块缓存 86 00:03:21,120 --> 00:03:22,280 磁盘块缓存呢就是 87 00:03:22,320 --> 00:03:23,880 它和虚拟存储之间呢 88 00:03:23,920 --> 00:03:26,400 我们是相互隔离开的 89 00:03:26,440 --> 00:03:29,240 你可能会有虚拟页的对换 90 00:03:29,280 --> 00:03:32,160 也有可能进程有文件的读写 91 00:03:32,200 --> 00:03:35,680 这两个呢在虚拟页这个地方的时候 92 00:03:35,720 --> 00:03:36,760 它可能会是说 93 00:03:36,800 --> 00:03:39,160 我在要去置换的时候 94 00:03:39,200 --> 00:03:40,840 我看是否有相应的缓存 95 00:03:40,880 --> 00:03:42,880 因为在我们前面讲的 96 00:03:42,920 --> 00:03:44,440 页面置换算法里头 97 00:03:44,480 --> 00:03:46,640 有一种情况是 我会往外写 98 00:03:46,680 --> 00:03:48,880 或者说 从磁盘上读的时候 99 00:03:48,920 --> 00:03:50,520 我有一部分内容呢 100 00:03:50,560 --> 00:03:52,400 在内存里头还是有备份的 101 00:03:52,440 --> 00:03:54,680 对于这种情况 我可以直接从页缓存里 102 00:03:54,720 --> 00:03:57,080 拿到相应的结果来使用 103 00:03:57,120 --> 00:03:58,800 而对于读写文件呢 104 00:03:58,840 --> 00:04:01,720 我可能在内存里有磁盘块 105 00:04:01,760 --> 00:04:04,080 已经有缓存 我也可以从这里拿出来 106 00:04:04,120 --> 00:04:06,200 这样一来的话对于这两种情况 107 00:04:06,240 --> 00:04:07,920 它们在这个磁盘块的 108 00:04:07,960 --> 00:04:09,440 数据块的缓存这个地方呢 109 00:04:09,480 --> 00:04:10,880 就可以到合并到一起 110 00:04:10,920 --> 00:04:13,800 但是前面这地方页缓存多了一级 111 00:04:13,840 --> 00:04:15,400 然后这个数据块再往下 112 00:04:15,440 --> 00:04:17,320 到磁盘上文件系统 113 00:04:17,360 --> 00:04:19,640 这是页缓存 实际上相当于 114 00:04:19,680 --> 00:04:24,000 它们俩合并地方是在数据块这个地方 115 00:04:24,040 --> 00:04:26,680 而另一种机制是把它俩统一起来 116 00:04:26,720 --> 00:04:29,000 这时候大家还记得 我们在前面讲的 117 00:04:29,040 --> 00:04:30,960 虚拟页式存储吗 118 00:04:31,000 --> 00:04:33,120 那个地方我可以把虚拟页面 119 00:04:33,160 --> 00:04:36,200 映射到本地的外存文件当中 120 00:04:36,240 --> 00:04:38,000 这就是我们这里说的 121 00:04:38,040 --> 00:04:39,560 逻辑地址空间里的页面 122 00:04:39,600 --> 00:04:44,080 经过内核的虚拟存储管理机构 123 00:04:44,120 --> 00:04:45,960 把它映射到物理内存 124 00:04:46,000 --> 00:04:49,480 或者说把它映射到外存 125 00:04:49,520 --> 00:04:51,440 这两者之间呢 126 00:04:51,480 --> 00:04:53,160 它们都是可以存数据的 127 00:04:53,200 --> 00:04:54,800 所以它可以利用这种方式 128 00:04:54,840 --> 00:04:59,560 来扩展进程可用的逻辑地址空间 129 00:04:59,600 --> 00:05:00,880 这时候说我们除了 130 00:05:00,920 --> 00:05:02,560 把它放在对换区里头 131 00:05:02,600 --> 00:05:03,880 你也可以认为它是一个文件 132 00:05:03,920 --> 00:05:06,240 我们可以还对一些可执行文件 133 00:05:06,280 --> 00:05:07,520 我直接把它映射到 134 00:05:07,560 --> 00:05:09,120 你的可执行文件里头去 135 00:05:09,160 --> 00:05:10,560 这种机制呢 实际上就和我们 136 00:05:10,600 --> 00:05:13,240 这里文件数据块的 137 00:05:13,280 --> 00:05:14,960 页缓存机制是一致的 138 00:05:15,000 --> 00:05:17,120 那么这时候呢我可以把这个页面呢 139 00:05:17,160 --> 00:05:19,280 缓存到别的文件里头 140 00:05:19,320 --> 00:05:20,720 也就是我们这里一种情况 141 00:05:20,760 --> 00:05:22,320 有了这种做法之后 142 00:05:22,360 --> 00:05:24,800 我们就可能反过来提供一种机制 143 00:05:24,840 --> 00:05:28,560 就是把文件缓存到内存当中 144 00:05:28,600 --> 00:05:30,240 把你的文件读写呢 145 00:05:30,280 --> 00:05:32,520 转换成对内存的访问 146 00:05:32,560 --> 00:05:33,520 这样一来 就可以把 147 00:05:33,560 --> 00:05:35,480 这两者之间统一起来 148 00:05:35,520 --> 00:05:37,640 在这种情况下 你在文件访问的时候 149 00:05:37,680 --> 00:05:40,200 就会导致缺页和相应的 150 00:05:40,240 --> 00:05:42,320 页面状态的变化 151 00:05:42,360 --> 00:05:44,400 这是它运用页面缓存的好处 152 00:05:44,440 --> 00:05:46,720 但是它也会有一个问题 153 00:05:46,760 --> 00:05:48,760 就是页面置换算法 154 00:05:48,800 --> 00:05:49,760 我们有一个 说 155 00:05:49,800 --> 00:05:52,320 给每一个进程分配多少物理页面 156 00:05:52,360 --> 00:05:53,920 如果是全局置换算法的话 157 00:05:53,960 --> 00:05:56,200 给整个系统有多少物理页面 158 00:05:56,240 --> 00:05:57,600 如果说你再把这个 159 00:05:57,640 --> 00:06:00,800 和磁盘缓存搁在一起的话 160 00:06:00,840 --> 00:06:04,200 这两者之间的页面数 到底分配多少 161 00:06:04,240 --> 00:06:05,440 实际上你又多了一个 162 00:06:05,480 --> 00:06:07,720 需要动态调节的部分 163 00:06:07,760 --> 00:06:08,800 这时候你需要 164 00:06:08,840 --> 00:06:11,680 在虚拟存储和页缓存之间 165 00:06:11,720 --> 00:06:15,560 去协调各自物理内存分配情况 166 00:06:15,600 --> 00:06:17,200 有了这种机制之后呢 167 00:06:17,240 --> 00:06:20,640 我们会看到 进程的内存访问 168 00:06:20,680 --> 00:06:24,080 和文件读写 都会转换成 169 00:06:24,120 --> 00:06:27,080 我这儿的页缓存 170 00:06:27,120 --> 00:06:29,440 如果说有 直接在内存里头就行了 171 00:06:29,480 --> 00:06:31,520 如果没有 再转换下面 172 00:06:31,560 --> 00:06:34,280 到文件系统里去读写 173 00:06:34,320 --> 00:06:36,880 这是我们说有了文件系统之后 174 00:06:36,920 --> 00:06:39,360 我的缓存的做法 175 00:06:39,400 --> 00:06:41,320 当然你要想维护这一套做法呢 176 00:06:41,360 --> 00:06:44,720 那你所有打开的文件 在操作系统里 177 00:06:44,760 --> 00:06:47,000 都必须维护相关的数据结构 178 00:06:47,040 --> 00:06:49,280 来记录这些缓存的状态 179 00:06:49,320 --> 00:06:52,760 这就是我们这里的打开文件数据结构 180 00:06:52,800 --> 00:06:55,600 其中重要的一个内容呢是文件描述符 181 00:06:55,640 --> 00:06:57,800 打开文件表里的每一项 182 00:06:57,840 --> 00:07:01,200 就是一个文件描述符所对应的信息 183 00:07:01,240 --> 00:07:02,960 每一个打开的文件呢 184 00:07:03,000 --> 00:07:04,480 有一个文件描述符 185 00:07:04,520 --> 00:07:06,800 这里头呢包含的信息是 186 00:07:06,840 --> 00:07:10,640 相关的文件指针 文件操作的设置 187 00:07:10,680 --> 00:07:14,320 以及于对应的目录项的缓存 188 00:07:14,360 --> 00:07:18,000 这些信息呢 对应过来是 189 00:07:18,040 --> 00:07:23,720 每个进程 有一个进程的打开文件表 190 00:07:23,760 --> 00:07:27,640 而整个系统 有一个系统的打开文件表 191 00:07:27,680 --> 00:07:29,520 并且在这种情况下 192 00:07:29,560 --> 00:07:32,880 如果说你某一个文件卷 193 00:07:32,920 --> 00:07:34,840 已经有文件被打开 194 00:07:34,880 --> 00:07:37,360 那么这时候呢 有打开文件的文件卷 195 00:07:37,400 --> 00:07:38,800 你就不能被卸载 196 00:07:38,840 --> 00:07:40,440 这就是为什么在有些情况下 197 00:07:40,480 --> 00:07:42,600 我把系统里的某个文件卷卸载 198 00:07:42,640 --> 00:07:44,480 它会不成功的原因 199 00:07:44,520 --> 00:07:46,800 有了这个之后 我们来看 200 00:07:46,840 --> 00:07:51,400 前面说到的 文件系统的组织视图 201 00:07:51,440 --> 00:07:53,640 跟我们打开文件怎么对应起来呢 202 00:07:53,680 --> 00:07:55,880 在这里头 你打开某一个文件 203 00:07:55,920 --> 00:07:59,400 就对应着相应的目录项 文件控制块 204 00:07:59,440 --> 00:08:03,760 和文件的内容 需要在内存当中有缓存 205 00:08:03,800 --> 00:08:06,200 这一信息呢 在内存当中的记录 206 00:08:06,240 --> 00:08:07,680 就构成了我们这里的 207 00:08:07,720 --> 00:08:10,080 系统打开文件表 208 00:08:10,120 --> 00:08:11,560 这个系统打开文件表里 209 00:08:11,600 --> 00:08:14,920 有一些内容是 各个进程是不一样的 210 00:08:14,960 --> 00:08:16,760 那这些不一样的部分 211 00:08:16,800 --> 00:08:20,120 就构成了我们进程的打开文件表 212 00:08:20,160 --> 00:08:23,240 而进程打开文件表里呢 共同的部分 213 00:08:23,280 --> 00:08:25,760 会映射到系统的打开文件表里头 214 00:08:25,800 --> 00:08:29,000 这样的话两个进程共用的部分呢 215 00:08:29,040 --> 00:08:31,120 它就在打开文件表里头 216 00:08:31,160 --> 00:08:32,640 这就是我们这里说到的 217 00:08:32,680 --> 00:08:36,920 进程打开文件表 和系统打开文件表 218 00:08:36,960 --> 00:08:39,880 有了这个之后 219 00:08:39,920 --> 00:08:42,680 接下来我们看打开文件锁 220 00:08:42,720 --> 00:08:44,880 也就说 有多个进程 221 00:08:44,920 --> 00:08:47,200 共享同一个文件的时候 222 00:08:47,240 --> 00:08:49,280 那么这时候呢 对于它们的访问 223 00:08:49,320 --> 00:08:50,520 就需要协调 224 00:08:50,560 --> 00:08:51,960 操作系统能够提供一种机制 225 00:08:52,000 --> 00:08:53,680 就是文件锁 226 00:08:53,720 --> 00:08:57,920 这种机制呢 分成两种实现策略 227 00:08:57,960 --> 00:09:00,560 一种是强制 你在访问一个文件的时候 228 00:09:00,600 --> 00:09:04,240 它根据锁的保持状态和你的访问请求 229 00:09:04,280 --> 00:09:07,040 来判断是否允许你进行相应的访问 230 00:09:07,080 --> 00:09:08,320 或者是拒绝 231 00:09:08,360 --> 00:09:10,520 还有一种做法呢是劝告 232 00:09:10,560 --> 00:09:11,320 实际上在这儿呢 233 00:09:11,360 --> 00:09:12,280 它就是在操作系统 234 00:09:12,320 --> 00:09:14,320 提供相应的一些机制 235 00:09:14,360 --> 00:09:18,520 使得进程可以查询文件打开 236 00:09:18,560 --> 00:09:20,200 和锁定的状态 237 00:09:20,240 --> 00:09:21,520 由进程来决定 238 00:09:21,560 --> 00:09:23,360 我在这种状态下我怎么做 239 00:09:23,400 --> 00:09:26,480 一种说我不管你怎么样 我直接访问 240 00:09:26,520 --> 00:09:29,680 因为我对它的中心状态不关心 241 00:09:29,720 --> 00:09:32,280 或者说 这个中心状态对我没有影响 242 00:09:32,320 --> 00:09:34,360 如果有影响 那你就可以决定说 243 00:09:34,400 --> 00:09:36,360 我延迟一会儿之后 244 00:09:36,400 --> 00:09:37,680 等相应操作完成了 245 00:09:37,720 --> 00:09:38,920 我再来进行相应操作 246 00:09:38,960 --> 00:09:40,200 这样的话 就把打开文件 247 00:09:40,240 --> 00:09:42,320 访问协调的机制呢 248 00:09:42,360 --> 00:09:45,320 变成是应用进程自己来协调 249 00:09:45,360 --> 00:09:47,600 这是关于数据块缓存 250 00:09:47,640 --> 00:09:50,640 和打开文件维护的讨论 251 00:09:50,680 --> 00:09:50,720