0 00:00:00,000 --> 00:00:15,480 1 00:00:15,520 --> 00:00:18,000 各位同学大家好 2 00:00:18,040 --> 00:00:20,200 今天给大家介绍一下lab8 3 00:00:20,240 --> 00:00:24,200 也是我们最后一个实验 文件系统 4 00:00:24,240 --> 00:00:25,960 那这主要包含以下几部分 5 00:00:26,000 --> 00:00:27,400 包括总体介绍 6 00:00:27,440 --> 00:00:30,920 以及对ucore文件系统架构的一个描述 7 00:00:30,960 --> 00:00:32,920 还有就是simple file system 8 00:00:32,960 --> 00:00:35,240 一个具体文件系统的一个分析 9 00:00:35,280 --> 00:00:37,160 以及virtual file system 10 00:00:37,200 --> 00:00:38,600 就是虚拟文件系统的一个 11 00:00:38,640 --> 00:00:40,280 展开的一个分析 12 00:00:40,320 --> 00:00:41,720 还有涉及到就是 13 00:00:41,760 --> 00:00:44,720 对I/O设备接口的描述 14 00:00:44,760 --> 00:00:47,920 然后最后对整个执行流程做一个概述 15 00:00:47,960 --> 00:00:49,160 通过这个描述呢 16 00:00:49,200 --> 00:00:50,240 使得大家能够对 17 00:00:50,280 --> 00:00:51,520 我们lab8要完成的实验 18 00:00:51,560 --> 00:00:52,520 有更清楚的了解 19 00:00:52,560 --> 00:00:58,520 这是这次实验的大致内容 20 00:00:58,560 --> 00:01:02,000 首先我们看一下总体介绍这一部分 21 00:01:02,040 --> 00:01:04,320 其实lab8呢 主要是说 22 00:01:04,360 --> 00:01:07,800 怎么去有效把硬盘里面的数据 23 00:01:07,840 --> 00:01:09,160 进行读或者写 24 00:01:09,200 --> 00:01:11,160 那这个读写呢 25 00:01:11,200 --> 00:01:13,000 如果说直接对硬盘数据进行访问 26 00:01:13,040 --> 00:01:14,800 很明显 它是操作繁琐 27 00:01:14,840 --> 00:01:17,720 不具有可移植性 也不简便 28 00:01:17,760 --> 00:01:19,960 为此 我们操作系统提出了 29 00:01:20,000 --> 00:01:21,280 一系列的抽象 30 00:01:21,320 --> 00:01:23,800 来简化对上层应用 31 00:01:23,840 --> 00:01:25,720 它的一个访问 32 00:01:25,760 --> 00:01:26,920 这里面有哪些抽象呢 33 00:01:26,960 --> 00:01:29,120 我们可以看看 有文件 34 00:01:29,160 --> 00:01:30,640 文件就是 大家知道 35 00:01:30,680 --> 00:01:32,840 我们可以把磁盘中的数据呢 36 00:01:32,880 --> 00:01:34,240 以文件的形式来表示出来 37 00:01:34,280 --> 00:01:36,000 从而可以方便地进行读写 38 00:01:36,040 --> 00:01:37,640 那么如果文件比较多的情况下呢 39 00:01:37,680 --> 00:01:38,760 我们可以组织成目录 40 00:01:38,800 --> 00:01:41,720 所以每个目录项呢就是一个文件 41 00:01:41,760 --> 00:01:43,440 还有呢就是什么呢 索引节点 42 00:01:43,480 --> 00:01:44,720 那文件和目录呢 43 00:01:44,760 --> 00:01:48,560 是给我们应用程序看到一个抽象 44 00:01:48,600 --> 00:01:50,280 那对于我们底层的实现呢 45 00:01:50,320 --> 00:01:51,840 我们需要考虑到索引节点 46 00:01:51,880 --> 00:01:52,600 它是一个真实的 47 00:01:52,640 --> 00:01:56,240 对硬盘上文件的一个具体描述 48 00:01:56,280 --> 00:01:58,800 安装点呢 是需要把我们文件系统 49 00:01:58,840 --> 00:02:01,000 放到一个地方 50 00:02:01,040 --> 00:02:03,640 使得我们可以从这个地方作为起始点 51 00:02:03,680 --> 00:02:07,520 来访问这个文件系统中所包含的文件 52 00:02:07,560 --> 00:02:11,160 这是文件系统四个抽象 53 00:02:11,200 --> 00:02:14,000 那有这个抽象之后还不够 54 00:02:14,040 --> 00:02:15,120 我们需要有相应 55 00:02:15,160 --> 00:02:17,120 针对这些抽象的一些操作 56 00:02:17,160 --> 00:02:18,960 比如说针对文件有什么呢 57 00:02:19,000 --> 00:02:21,840 打开 关闭文件 以及读 写文件 58 00:02:21,880 --> 00:02:23,360 对应目录项也是一样的 59 00:02:23,400 --> 00:02:25,560 目录项是目录的组成部分 60 00:02:25,600 --> 00:02:27,680 这里面会涉及到打开目录 61 00:02:27,720 --> 00:02:30,000 以及读取目录中的目录项 62 00:02:30,040 --> 00:02:31,800 等一系列的操作 63 00:02:31,840 --> 00:02:33,080 对索引节点而言 64 00:02:33,120 --> 00:02:35,200 我们其实需要知道的是 65 00:02:35,240 --> 00:02:36,480 一个具体的文件 66 00:02:36,520 --> 00:02:38,720 它对应到磁盘中的哪些数据 67 00:02:38,760 --> 00:02:40,440 这就是通过索引节点 68 00:02:40,480 --> 00:02:42,440 来把磁盘中的数据 69 00:02:42,480 --> 00:02:44,840 和应用程序能看到的那个文件呢 70 00:02:44,880 --> 00:02:46,240 建立起一个对应关系 71 00:02:46,280 --> 00:02:47,840 这是索引节点这一块 72 00:02:47,880 --> 00:02:49,400 安装点呢也是一样的 73 00:02:49,440 --> 00:02:51,480 它会涉及到 你要安装一个文件系统 74 00:02:51,520 --> 00:02:53,320 以及要卸载一个文件系统 75 00:02:53,360 --> 00:02:56,360 那就是有mount unmount这两个操作 76 00:02:56,400 --> 00:02:57,440 那么这些操作呢 77 00:02:57,480 --> 00:02:59,360 它组成了我们文件系统的 78 00:02:59,400 --> 00:03:03,440 主要的实现部分 79 00:03:03,480 --> 00:03:04,920 好 我们可以看看 80 00:03:04,960 --> 00:03:06,200 这个呢可以理解为是 81 00:03:06,240 --> 00:03:08,960 我们ucore文件系统的一个大致组成 82 00:03:09,000 --> 00:03:11,480 可以看到确实它涉及的面比较多 83 00:03:11,520 --> 00:03:13,960 从底层的存储的I/O设备 84 00:03:14,000 --> 00:03:15,880 以及具体文件系统 85 00:03:15,920 --> 00:03:17,120 比如说我们后面会讲到的 86 00:03:17,160 --> 00:03:18,760 simple file system 87 00:03:18,800 --> 00:03:20,120 还有呢虚拟文件系统 88 00:03:20,160 --> 00:03:22,320 它可以抽象出文件系统的 89 00:03:22,360 --> 00:03:23,560 一些共性的东西 90 00:03:23,600 --> 00:03:25,960 形成针对不同类型的 91 00:03:26,000 --> 00:03:28,120 文件系统的一个接口 92 00:03:28,160 --> 00:03:30,240 可以更方便地给我们上层系统调用 93 00:03:30,280 --> 00:03:33,360 提供一个进一步的文件访问接口 94 00:03:33,400 --> 00:03:35,120 使得我们用户态程序 95 00:03:35,160 --> 00:03:36,760 通过一个C库 96 00:03:36,800 --> 00:03:39,520 可以很方便地对硬盘中的数据 97 00:03:39,560 --> 00:03:41,360 进行读写操作 98 00:03:41,400 --> 00:03:42,400 那可以看出来 99 00:03:42,440 --> 00:03:45,920 这是文件系统一个大致一个概貌 100 00:03:45,960 --> 00:03:48,920 有用户态和内核态两大部分 101 00:03:48,960 --> 00:03:50,840 这也是为什么它叫文件系统 102 00:03:50,880 --> 00:03:52,640 而不是叫文件子系统 103 00:03:52,680 --> 00:03:54,320 因为我们知道在操作系统里面 104 00:03:54,360 --> 00:03:55,680 有很多子系统 105 00:03:55,720 --> 00:03:56,960 我们说有进程管理子系统 106 00:03:57,000 --> 00:03:58,120 内存管理子系统 107 00:03:58,160 --> 00:04:01,080 但只有文件这一块呢叫文件系统 108 00:04:01,120 --> 00:04:03,240 这在某种程度上也体现出来 109 00:04:03,280 --> 00:04:05,200 这个文件系统它本身的一个 110 00:04:05,240 --> 00:04:10,160 比较复杂这么一个特征 111 00:04:10,200 --> 00:04:11,520 好 既然我们也知道 112 00:04:11,560 --> 00:04:12,920 一个文件系统的大致一个组成 113 00:04:12,960 --> 00:04:15,960 我们可以看看在我们ucorelab中呢 114 00:04:16,000 --> 00:04:18,280 我们其实在lab8中 115 00:04:18,320 --> 00:04:21,240 已经按照我们这个初始流程 116 00:04:21,280 --> 00:04:22,600 idle init 117 00:04:22,640 --> 00:04:25,120 init之后呢会完成 118 00:04:25,160 --> 00:04:27,160 对文件系统和磁盘外设的初始化 119 00:04:27,200 --> 00:04:29,080 因为我们知道 我们需要把数据 120 00:04:29,120 --> 00:04:30,880 要放在磁盘里面 121 00:04:30,920 --> 00:04:32,480 我们需要对磁盘进行读写 122 00:04:32,520 --> 00:04:34,800 所以说这两块 一个是device 123 00:04:34,840 --> 00:04:36,000 一个是file system 124 00:04:36,040 --> 00:04:37,720 这两块需要初始化 125 00:04:37,760 --> 00:04:40,120 这是在我们之前的lab1到lab7中 126 00:04:40,160 --> 00:04:41,360 没有涉及到的部分 127 00:04:41,400 --> 00:04:42,880 有了这个初始化之后呢 128 00:04:42,920 --> 00:04:46,400 我们就可以把我们在硬盘上建立好的 129 00:04:46,440 --> 00:04:47,480 simple file system 130 00:04:47,520 --> 00:04:49,320 这个文件系统里面一些程序呢 131 00:04:49,360 --> 00:04:51,600 放到内存里面来 132 00:04:51,640 --> 00:04:53,160 可以看到 133 00:04:53,200 --> 00:04:55,160 然后呢它会变成进程 134 00:04:55,200 --> 00:04:58,480 去进一步去在用户空间里面执行 135 00:04:58,520 --> 00:05:00,960 这就是我们在lab8中 136 00:05:01,000 --> 00:05:04,000 需要让大家能够理解和掌握的 137 00:05:04,040 --> 00:05:07,880 关于文件系统的一些知识 138 00:05:07,920 --> 00:05:10,800 好 那我们看看lab8的目标 139 00:05:10,840 --> 00:05:12,120 第一个目标 你要知道 140 00:05:12,160 --> 00:05:14,080 我们站在用户的角度 141 00:05:14,120 --> 00:05:16,080 它怎么能够访问文件系统 142 00:05:16,120 --> 00:05:19,760 及完成open close read write 143 00:05:19,800 --> 00:05:20,920 这个怎么操作的 144 00:05:20,960 --> 00:05:21,800 一个大致了解 145 00:05:21,840 --> 00:05:23,480 第二个呢 我们需要去了解 146 00:05:23,520 --> 00:05:24,560 simple file system 147 00:05:24,600 --> 00:05:26,040 一个基于inode这种结构的 148 00:05:26,080 --> 00:05:27,320 一种具体文件系统 149 00:05:27,360 --> 00:05:28,880 它怎么来设计实现的 150 00:05:28,920 --> 00:05:30,040 在硬盘上怎么组织 151 00:05:30,080 --> 00:05:31,480 怎么把它读到内存中来 152 00:05:31,520 --> 00:05:34,560 来完成相应的这些操作 153 00:05:34,600 --> 00:05:35,680 同时我们还需要理解 154 00:05:35,720 --> 00:05:37,880 一个文件系统的抽象 155 00:05:37,920 --> 00:05:40,000 其实我们操作系统有各个层面的抽象 156 00:05:40,040 --> 00:05:41,920 那么在文件系统里面 157 00:05:41,960 --> 00:05:44,320 也有一层抽象叫Virtual file system 158 00:05:44,360 --> 00:05:45,760 通过Virtual file system呢 159 00:05:45,800 --> 00:05:47,880 它可以给我们应用程序 160 00:05:47,920 --> 00:05:51,200 提供一个很简洁的一致接口 161 00:05:51,240 --> 00:05:53,120 不管底下你是一个硬盘 162 00:05:53,160 --> 00:05:54,920 你还是一个其它的设备 163 00:05:54,960 --> 00:05:56,360 只要你以文件的形式存在 164 00:05:56,400 --> 00:05:58,360 那我们都可以通过VFS 165 00:05:58,400 --> 00:06:00,160 来进行有效的访问 166 00:06:00,200 --> 00:06:02,400 及我们可以用它来干什么呢 167 00:06:02,440 --> 00:06:07,240 open close read write OK很简单 168 00:06:07,280 --> 00:06:09,000 好 我们还需要通过练习 169 00:06:09,040 --> 00:06:12,000 来完成对文件系统更进一步的了解 170 00:06:12,040 --> 00:06:13,000 这里面包含两部分 171 00:06:13,040 --> 00:06:14,760 一部分是说要完成 172 00:06:14,800 --> 00:06:16,640 读文件操作的一个实现 173 00:06:16,680 --> 00:06:19,000 就是我们在从应用程序 174 00:06:19,040 --> 00:06:20,560 发出这个读请求之后 175 00:06:20,600 --> 00:06:21,920 一直到最后 176 00:06:21,960 --> 00:06:24,520 从硬盘中把这个数据读进来 177 00:06:24,560 --> 00:06:26,040 那么整个执行流程中呢 178 00:06:26,080 --> 00:06:27,760 有一些环节我们留出来空 179 00:06:27,800 --> 00:06:29,440 希望大家能够填写出来 180 00:06:29,480 --> 00:06:31,680 你需要去理解整个执行过程 181 00:06:31,720 --> 00:06:34,880 然后再把相应缺的地方给补上 182 00:06:34,920 --> 00:06:37,120 第二个呢 是完成基于文件系统的 183 00:06:37,160 --> 00:06:39,040 执行程序机制的实现 184 00:06:39,080 --> 00:06:41,240 这和我们进程管理相关 185 00:06:41,280 --> 00:06:43,920 我们进程管理在do_execve 186 00:06:43,960 --> 00:06:45,280 这么一个操作里面呢 187 00:06:45,320 --> 00:06:48,840 它会完成对一个elf格式的 188 00:06:48,880 --> 00:06:53,280 文件解析和加载 以及运行 189 00:06:53,320 --> 00:06:54,440 我们可以回忆一下 190 00:06:54,480 --> 00:06:57,120 在lab5里面我们会完成相应的事情 191 00:06:57,160 --> 00:07:00,520 那么当时那个实现 192 00:07:00,560 --> 00:07:02,120 是在内存中完成的 193 00:07:02,160 --> 00:07:03,960 我们需要把这个文件 194 00:07:04,000 --> 00:07:05,480 从硬盘中读出来 195 00:07:05,520 --> 00:07:08,560 那就需要对我们当时文件加载过程 196 00:07:08,600 --> 00:07:09,520 重新进行扩展 197 00:07:09,560 --> 00:07:11,200 使得它能够从硬盘 198 00:07:11,240 --> 00:07:13,200 把这个数据和代码读进来 199 00:07:13,240 --> 00:07:17,600 并产生进程的一个主体内容 200 00:07:17,640 --> 00:07:20,480 好 我们再回顾一下lab5的工作 201 00:07:20,520 --> 00:07:24,280 在lab5中 我们当时是进程创建和执行 202 00:07:24,320 --> 00:07:26,080 当时呢 在一开始 203 00:07:26,120 --> 00:07:29,080 我们bootloader就把整个kernel img 204 00:07:29,120 --> 00:07:31,880 包含了我们的应用程序 205 00:07:31,920 --> 00:07:34,840 一股脑地加载到我们内存中来 206 00:07:34,880 --> 00:07:37,760 然后呢后续的ucore呢 207 00:07:37,800 --> 00:07:40,600 它是在内存中完成了 208 00:07:40,640 --> 00:07:43,680 对elf格式的文件的读取和加载 209 00:07:43,720 --> 00:07:46,000 它跟我们的硬盘没有关系 210 00:07:46,040 --> 00:07:49,600 那这个呢比较简单 211 00:07:49,640 --> 00:07:50,520 但是我们现在 212 00:07:50,560 --> 00:07:52,320 假定我们lab8有了文件系统之后呢 213 00:07:52,360 --> 00:07:54,720 其实这一步就可以从 214 00:07:54,760 --> 00:07:56,560 我们的硬盘直接读到内存中来 215 00:07:56,600 --> 00:07:58,280 而不需要一开始 216 00:07:58,320 --> 00:08:00,120 通过bootloader读取的方式 217 00:08:00,160 --> 00:08:02,280 那么可以更加灵活地来完成 218 00:08:02,320 --> 00:08:06,560 对文件的加载和执行的过程 219 00:08:06,600 --> 00:08:07,640 lab8文件系统呢 220 00:08:07,680 --> 00:08:10,760 它在整个这个实现中呢 221 00:08:10,800 --> 00:08:13,720 包含了很多的数据结构和函数调用 222 00:08:13,760 --> 00:08:16,400 而且它和我们进程 内存 223 00:08:16,440 --> 00:08:17,760 都有很多关系 224 00:08:17,800 --> 00:08:20,480 所以相对来说 它的数据结构 225 00:08:20,520 --> 00:08:23,000 和函数调用关系比较复杂 226 00:08:23,040 --> 00:08:25,720 其实涉及到好像感觉比较简单 227 00:08:25,760 --> 00:08:27,360 比如说 实际上就是完成 228 00:08:27,400 --> 00:08:30,240 硬盘和内存数据一个交换 229 00:08:30,280 --> 00:08:31,640 它需要从硬盘中读数据 230 00:08:31,680 --> 00:08:35,120 或者把内存中的数据写回到硬盘中来 231 00:08:35,160 --> 00:08:37,160 这就是两个它主要干的事情 232 00:08:37,200 --> 00:08:38,480 但是怎么能够 233 00:08:38,520 --> 00:08:41,760 很方便地给我们应用程序提供一个接口 234 00:08:41,800 --> 00:08:43,080 来完成这个工作呢 235 00:08:43,120 --> 00:08:45,600 这就需要我们文件系统 236 00:08:45,640 --> 00:08:50,120 整体地来做一个设计和考虑 237 00:08:50,160 --> 00:08:52,200 为了能够更好理解和分析 238 00:08:52,240 --> 00:08:54,560 uCorelab8这里面的文件系统呢 239 00:08:54,600 --> 00:08:55,920 我们可以采取以下一些方法 240 00:08:55,960 --> 00:08:57,760 比如说 自下而上 241 00:08:57,800 --> 00:09:01,600 从硬盘往内存这么一个角度来分析 242 00:09:01,640 --> 00:09:04,200 也可以自上而下 站在用户的角度 243 00:09:04,240 --> 00:09:06,200 它发出一个请求 这个请求怎么完成的 244 00:09:06,240 --> 00:09:08,880 最终把这个数据写到硬盘上 245 00:09:08,920 --> 00:09:11,680 或者是 把数据从硬盘读到内存中来 246 00:09:11,720 --> 00:09:13,240 这是自上而下 247 00:09:13,280 --> 00:09:14,800 还有呢我们要理一下关系 248 00:09:14,840 --> 00:09:16,680 这里面很多数据结构的关系 249 00:09:16,720 --> 00:09:19,040 实际上体现在数据结构的包含关系 250 00:09:19,080 --> 00:09:20,440 谁包含了谁 251 00:09:20,480 --> 00:09:23,160 以及在你访问这个控制流的时候呢 252 00:09:23,200 --> 00:09:25,680 你要知道谁访问了谁 253 00:09:25,720 --> 00:09:26,560 采取这种方式呢 254 00:09:26,600 --> 00:09:28,960 我们可以比较容易地把握 255 00:09:29,000 --> 00:09:31,360 文件系统整体的一个概貌 256 00:09:31,400 --> 00:09:33,880 其实我们希望大家能够在头脑中 257 00:09:33,920 --> 00:09:35,080 浮现出文件系统 258 00:09:35,120 --> 00:09:37,200 在内核中的一个执行过程 259 00:09:37,240 --> 00:09:39,600 以及它相应数据结构一幅图形 260 00:09:39,640 --> 00:09:40,960 而这个图形之后呢 261 00:09:41,000 --> 00:09:42,800 你去掌握和理解文件系统 262 00:09:42,840 --> 00:09:44,920 就更加容易一些 263 00:09:44,960 --> 00:09:46,640 为此我们也采取一种方法 264 00:09:46,680 --> 00:09:48,680 来进一步地分析这个 265 00:09:48,720 --> 00:09:50,720 ucore里面文件系统的架构 266 00:09:50,760 --> 00:09:52,840 以及用户怎么访问文件的 267 00:09:52,880 --> 00:09:56,200 还有就是内核中跟文件相关的数据结构 268 00:09:56,240 --> 00:10:00,320 它们之间的关系是什么 269 00:10:00,360 --> 00:10:01,920 好 我们可以举个简单例子 270 00:10:01,960 --> 00:10:03,600 我们站在一个比较高的角度 271 00:10:03,640 --> 00:10:06,200 来看一下文件系统到底怎么回事 272 00:10:06,240 --> 00:10:08,840 首先 我们在一个硬盘上 273 00:10:08,880 --> 00:10:10,880 当然这个硬盘是一个虚拟的硬盘 274 00:10:10,920 --> 00:10:12,880 我们用qemu生成了一个文件 275 00:10:12,920 --> 00:10:15,080 这个文件就是模拟硬盘的 276 00:10:15,120 --> 00:10:16,760 整体的一个结构 277 00:10:16,800 --> 00:10:17,640 在这个结构里面呢 278 00:10:17,680 --> 00:10:20,120 会有一个具体文件系统 SFS 279 00:10:20,160 --> 00:10:22,360 那这个文件系统在后面会展开说 280 00:10:22,400 --> 00:10:26,360 然后有了这个文件系统之后呢 281 00:10:26,400 --> 00:10:29,800 我们就要实现一系列的层次 282 00:10:29,840 --> 00:10:32,240 来完成对这个文件系统的一个操作 283 00:10:32,280 --> 00:10:33,600 比如说它I/O层次 284 00:10:33,640 --> 00:10:35,200 它能够去通过I/O接口 285 00:10:35,240 --> 00:10:37,800 来访问这个device 访问这个硬盘 286 00:10:37,840 --> 00:10:40,280 通过Simple FS呢来读取这个硬盘中 287 00:10:40,320 --> 00:10:42,800 保存的这个文件系统的结构 288 00:10:42,840 --> 00:10:45,480 通过VFS呢 给我们上层应用 289 00:10:45,520 --> 00:10:48,240 提供一层一致的接口 290 00:10:48,280 --> 00:10:49,600 那么自然它有一个 291 00:10:49,640 --> 00:10:51,600 file system相关一个system call 292 00:10:51,640 --> 00:10:54,320 这是第二层 293 00:10:54,360 --> 00:10:55,880 然后有了这个接口之后呢 294 00:10:55,920 --> 00:10:58,120 应用程序就可以去 295 00:10:58,160 --> 00:11:01,080 通过这个文件系统相关的接口 296 00:11:01,120 --> 00:11:03,080 来从上到下去地执行 297 00:11:03,120 --> 00:11:07,840 从而完成对这个存储在硬盘中的文件 298 00:11:07,880 --> 00:11:09,960 或者目录的数据一个访问 299 00:11:10,000 --> 00:11:10,040