0 00:00:00,000 --> 00:00:06,320 1 00:00:06,400 --> 00:00:08,640 今天我们通过一个实例 2 00:00:08,680 --> 00:00:15,920 来介绍系统调用的使用和实现 3 00:00:15,960 --> 00:00:21,680 在这里我们通常从应用程序的写法上来说这个事 4 00:00:21,720 --> 00:00:24,080 我们说我想写一个程序 5 00:00:24,120 --> 00:00:27,240 能够把一个文件的内容复制出来 6 00:00:27,280 --> 00:00:31,240 写到另一个文件里头改一个名字 7 00:00:31,280 --> 00:00:34,600 这个地方是我把这件事情展开之后 8 00:00:34,640 --> 00:00:36,960 我写程序会是什么样的 9 00:00:37,000 --> 00:00:40,280 首先我会在屏幕上输出一个提示 10 00:00:40,320 --> 00:00:42,160 要求用户输入 11 00:00:42,200 --> 00:00:46,880 你要读的那个文件的名字 12 00:00:46,920 --> 00:00:49,040 然后等待键盘输入 13 00:00:49,080 --> 00:00:53,120 接下来 我会提示用户 14 00:00:53,160 --> 00:00:57,600 在屏幕上给出提示说让它输入输出文件的名字 15 00:00:57,640 --> 00:01:00,680 等待并接受键盘的输入 16 00:01:00,720 --> 00:01:02,280 等到这两个信息都有了 17 00:01:02,320 --> 00:01:06,720 然后说我们就试图去打开相应的文件 18 00:01:06,760 --> 00:01:09,040 首先去打开输入文件 19 00:01:09,080 --> 00:01:12,400 如果输入文件在你的文件系统当中有 20 00:01:12,440 --> 00:01:15,160 那这是成立的 如果说文件不存在 21 00:01:15,200 --> 00:01:19,160 这个时候肯定出错了因为我要复制的源没有了 22 00:01:19,200 --> 00:01:23,680 接下来说我去看看创建输出文件 23 00:01:23,720 --> 00:01:24,880 如果说你有这个文件 24 00:01:24,920 --> 00:01:26,600 这个时候我会把原来东西覆盖掉 25 00:01:26,640 --> 00:01:29,120 这是不对的 这个时候如果文件存在 26 00:01:29,160 --> 00:01:32,280 这个时候我出错退出 这几步都做完了之后 27 00:01:32,320 --> 00:01:35,720 那我就确认我的输入文件是存在的 28 00:01:35,760 --> 00:01:37,320 输出文件是没有的 29 00:01:37,360 --> 00:01:41,920 然后开始循环从文件当中读数据写到输出文件 30 00:01:41,960 --> 00:01:43,680 从输入文件当中读数据 31 00:01:43,720 --> 00:01:47,720 从输出文件当中把数据写到输出文件中 32 00:01:47,760 --> 00:01:49,360 那为什么会在这做循环 33 00:01:49,400 --> 00:01:51,720 有可能这个文件很小我一次就搞完了 34 00:01:51,760 --> 00:01:54,640 如果这个文很大我可能会循环若干次 35 00:01:54,680 --> 00:01:57,480 等所有这些都做完 36 00:01:57,520 --> 00:02:01,480 那么这个时候我关闭输出文件和输入文件 37 00:02:01,520 --> 00:02:04,280 然后屏幕上提示我整个事情做完 38 00:02:04,320 --> 00:02:07,960 整个程序正常退出 那这是我想写的程序 39 00:02:08,000 --> 00:02:09,400 对于这个程序来说 40 00:02:09,440 --> 00:02:12,680 我会用到哪些系统调用 41 00:02:12,720 --> 00:02:17,520 我们会有键盘的输入 有屏幕的输出 42 00:02:17,560 --> 00:02:20,200 有文件的输入和输出 43 00:02:20,240 --> 00:02:21,800 实际上在操作系统内核里 44 00:02:21,840 --> 00:02:26,280 它这个实现键盘 屏幕和文件 45 00:02:26,320 --> 00:02:28,280 都视为是文件系统里的 46 00:02:28,320 --> 00:02:32,600 只是说键盘和屏幕作为特殊的文件来使用 47 00:02:32,640 --> 00:02:35,880 那么在这里头涉及到的系统调用是 48 00:02:35,920 --> 00:02:40,600 open close还有一个应该是create 49 00:02:40,640 --> 00:02:43,200 然后再有一个write read 50 00:02:43,240 --> 00:02:44,720 有了这几个系统调用之后 51 00:02:44,760 --> 00:02:46,400 那么我这件事情在上边 52 00:02:46,440 --> 00:02:49,640 用我的函数库里内容就可以完成 53 00:02:49,680 --> 00:02:54,440 具体怎么来做 我们看到要想在应用程序写 54 00:02:54,480 --> 00:02:58,680 那应用程序会使用到一个库函数read 55 00:02:58,720 --> 00:03:01,680 这和我们用到其他函数是一样的 56 00:03:01,720 --> 00:03:05,240 在我们ucore有这样一个头文件 57 00:03:05,280 --> 00:03:07,280 告诉你他格式什么样子 58 00:03:07,320 --> 00:03:11,400 然后这里头你需要知道这里的参数是什么意思 59 00:03:11,440 --> 00:03:16,960 你读写的文件 你读出来的数据放在什么地方 60 00:03:17,000 --> 00:03:19,400 缓冲区域头指针 61 00:03:19,440 --> 00:03:21,640 然后这个缓冲区域并不是无限大的 62 00:03:21,680 --> 00:03:24,120 它的最大长度你不能超过这个长度 63 00:03:24,160 --> 00:03:26,640 然后我从里头读数据往里放 64 00:03:26,680 --> 00:03:28,080 那我实际读出来的时候 65 00:03:28,120 --> 00:03:30,000 有可能比这短 66 00:03:30,040 --> 00:03:32,480 因为我实际文件里可能没有这么多数据 67 00:03:32,520 --> 00:03:34,920 如果说我实际数据比这个缓冲区大怎么办 68 00:03:34,960 --> 00:03:37,760 这个时候最多读出来是缓冲区长度 69 00:03:37,800 --> 00:03:40,520 因为在多就缓冲区溢出了 70 00:03:40,560 --> 00:03:45,560 然后读完之后返回值 这个地方有个返回值 71 00:03:45,600 --> 00:03:48,920 返回值是你的长度 你在使用的时候怎么做 72 00:03:48,960 --> 00:03:51,480 你打开文件 把这三个参数填上去 73 00:03:51,520 --> 00:03:52,920 返回来的时候 74 00:03:52,960 --> 00:03:54,880 返回的结果就是你实际的程度 75 00:03:54,920 --> 00:03:56,760 这是你在用的时候 76 00:03:56,800 --> 00:04:00,240 对于我们系统的实现来讲 77 00:04:00,280 --> 00:04:02,280 在编译程序的时候 78 00:04:02,320 --> 00:04:04,680 你的应用程序用到相应的库函数 79 00:04:04,720 --> 00:04:08,600 这个库里头在编译系统调用的内容的时候 80 00:04:08,640 --> 00:04:10,480 它就会前面准参数 81 00:04:10,520 --> 00:04:14,600 实际上这段都会往堆栈压栈 82 00:04:14,640 --> 00:04:18,960 压占完之后最后有函数调用 83 00:04:19,000 --> 00:04:23,400 而这个函数调用最后都转到我有一个 84 00:04:23,440 --> 00:04:26,280 你说这不是系统调用 是的 85 00:04:26,320 --> 00:04:27,320 这是一个函数调用 86 00:04:27,360 --> 00:04:30,360 这个函数调用所有的系统调用 87 00:04:30,400 --> 00:04:34,560 它都是通过一个宏展开形成相应的函数 88 00:04:34,600 --> 00:04:36,520 实际上在这里有一段汇编 89 00:04:36,560 --> 00:04:39,800 大家注意这段汇编有可能你现在还不能完全看懂 90 00:04:39,840 --> 00:04:41,840 那你需要值得这个int 91 00:04:41,880 --> 00:04:46,680 是我们前面说到系统调用的指令 92 00:04:46,720 --> 00:04:53,360 后面i实际上是你系统调用的中段向量编号 93 00:04:53,400 --> 00:04:58,760 后面num实际就是系统调用read系统调用编号 94 00:04:58,800 --> 00:05:01,600 然后后面是相应的参数 95 00:05:01,640 --> 00:05:03,120 这些填完之后 96 00:05:03,160 --> 00:05:05,040 实际上最后你在的执行应用程序 97 00:05:05,080 --> 00:05:07,520 执行到这个地方 到这里来的时候 98 00:05:07,560 --> 00:05:10,480 它就会转成系统调用进到内核里去 99 00:05:10,520 --> 00:05:17,200 接下来我们操作系统里是如何实现系统调用read 100 00:05:17,240 --> 00:05:23,000 首先我们刚才说在用户态一个int进到内核里来之后 101 00:05:23,040 --> 00:05:24,800 这实际上是一个软中段 102 00:05:24,840 --> 00:05:30,480 所有这些都会到你最开始一段汇编程序叫alltraps 103 00:05:30,520 --> 00:05:33,560 在这里会获取到中段所需要的 104 00:05:33,600 --> 00:05:36,040 相关信息组成的数据结构 105 00:05:36,080 --> 00:05:38,720 这个时候实际上TF数据结构 106 00:05:38,760 --> 00:05:41,840 那么在这里头我们注意到其中有一条 107 00:05:41,880 --> 00:05:45,440 是其中的中段向量 108 00:05:45,480 --> 00:05:48,960 那么在这里是系统调用对应中段向量 109 00:05:49,000 --> 00:05:52,480 然后依据这个那么在trap函数里头 110 00:05:52,520 --> 00:05:58,520 它就会转到我们系统调用这个函数里头 111 00:05:58,560 --> 00:06:02,720 在这个函数里头它会读其中的EAX 112 00:06:02,760 --> 00:06:04,800 实际上就是你的系统调用编号 113 00:06:04,840 --> 00:06:10,920 这个系统调用编号会看到它是等于read 114 00:06:10,960 --> 00:06:13,320 等于这个实际上相当于这个时候 115 00:06:13,360 --> 00:06:14,840 我们知道进来是系统调用 116 00:06:14,880 --> 00:06:17,080 并且这个系统调用调用是哪个功能 117 00:06:17,120 --> 00:06:18,440 我们还缺什么 118 00:06:18,480 --> 00:06:20,520 我们还缺它参数 119 00:06:20,560 --> 00:06:26,000 这些参数就会转到相应的系统调用实现里头 120 00:06:26,040 --> 00:06:31,320 这个实现里负责去堆占里头SP 121 00:06:31,360 --> 00:06:36,720 获取相应我们当时填进堆占里头那三个参数 122 00:06:36,760 --> 00:06:40,280 这个文件 缓冲区 头指针 123 00:06:40,320 --> 00:06:43,640 然后缓冲区长度 有了这三个之后 124 00:06:43,680 --> 00:06:45,000 实际上这个时候就相当于 125 00:06:45,040 --> 00:06:47,320 已经从用户态转到内核态 126 00:06:47,360 --> 00:06:48,920 如果说我是一个函数调用的话 127 00:06:48,960 --> 00:06:50,760 这个时候就已经转过来 128 00:06:50,800 --> 00:06:53,200 我在继续做相应函数实现就可以了 129 00:06:53,240 --> 00:06:58,480 这个时候我们就看到最后它到sysfile read 130 00:06:58,520 --> 00:07:02,680 这个函数里头去完成相应的文件读取功能 131 00:07:02,720 --> 00:07:07,080 这个文件读取就是直接操作底下的驱动程序 132 00:07:07,120 --> 00:07:09,840 在往下 等到它最后返回的时候 133 00:07:09,880 --> 00:07:15,040 ok 那这个时候我们到这trapret 134 00:07:15,080 --> 00:07:18,520 在这里头我们去看这类代码最后会有ireturn 135 00:07:18,560 --> 00:07:23,480 这个return会把相应的返回值的长度 136 00:07:23,520 --> 00:07:26,320 读到内容长度返回给用户态 137 00:07:26,360 --> 00:07:29,120 整个实现就全部完成 138 00:07:29,160 --> 00:07:33,200 那接下来我们会去看看实际的系统里的代码是什么样子的 139 00:07:33,240 --> 00:07:33,280 140 00:07:33,320 --> 00:07:33,360