0 00:00:00,000 --> 00:00:07,120 1 00:00:07,320 --> 00:00:12,160 接下来我们介绍进程加载 2 00:00:12,200 --> 00:00:16,480 进程加载是指我们用户的应用程序 3 00:00:16,520 --> 00:00:19,440 通过系统调用exec()加载 4 00:00:19,480 --> 00:00:23,000 来完成一个新的可执行文件的加载 5 00:00:23,160 --> 00:00:24,200 那在这里头呢 6 00:00:24,240 --> 00:00:26,800 我们前面已经在介绍fork的时候 7 00:00:26,840 --> 00:00:29,480 已经说过它的使用 8 00:00:29,520 --> 00:00:30,960 用exec()这个系统调用 9 00:00:31,000 --> 00:00:34,480 就可以加载一个新的可执行文件 10 00:00:34,520 --> 00:00:36,680 到内存当中覆盖原来的 11 00:00:36,720 --> 00:00:39,280 那个当前进程的地址空间 12 00:00:39,320 --> 00:00:40,760 然后开始执行 13 00:00:40,800 --> 00:00:42,240 那在这儿呢我们会来说 14 00:00:42,280 --> 00:00:45,120 这个加载到底它在怎么做 15 00:00:45,160 --> 00:00:46,240 那我们还知道 16 00:00:46,280 --> 00:00:48,640 大家在系统加载的时候是啥样 17 00:00:48,680 --> 00:00:50,120 我们CPU加电 18 00:00:50,160 --> 00:00:53,920 加电之后去起动从BIOS里的程序 19 00:00:53,960 --> 00:00:55,720 BIOS里的程序运行之后 20 00:00:55,760 --> 00:00:58,600 就从硬盘上去加载引导扇区 21 00:00:58,640 --> 00:01:01,040 引导扇区加载完了之后 22 00:01:01,080 --> 00:01:02,680 是我的bootloader 23 00:01:02,720 --> 00:01:06,160 bootloader然后再到我的内核映像 24 00:01:06,200 --> 00:01:08,240 内核做了一系列工作之后 25 00:01:08,280 --> 00:01:10,640 现在到我们的应用程序这个地方 26 00:01:10,680 --> 00:01:12,920 那用户要想通过exec() 27 00:01:12,960 --> 00:01:16,520 加载一个可执行程序 28 00:01:16,560 --> 00:01:17,760 它类似于我们前面的 29 00:01:17,800 --> 00:01:19,480 这种系统加载一样的 30 00:01:19,520 --> 00:01:22,600 我把一个映像加载到内存当中 31 00:01:22,640 --> 00:01:25,520 然后我得跳过去到指定的位置来执行 32 00:01:25,560 --> 00:01:27,760 实际上这些呢中间就是有 33 00:01:27,800 --> 00:01:30,680 关于可执行文件格式的识别的问题 34 00:01:30,720 --> 00:01:32,680 不同的系统呢它可以加载的 35 00:01:32,720 --> 00:01:35,160 可执行文件的格式是不一样的 36 00:01:35,200 --> 00:01:37,640 这是我们首先要说的 37 00:01:37,680 --> 00:01:40,240 然后再由这里头我们加载的时候 38 00:01:40,280 --> 00:01:43,520 用户需要指定加载时候的参数 39 00:01:43,560 --> 00:01:45,200 你比如说在我们exec()里头 40 00:01:45,240 --> 00:01:48,120 要指定你后面有几个参数 41 00:01:48,160 --> 00:01:50,840 然后每个参数分别是什么 42 00:01:50,880 --> 00:01:52,840 好指定完了之后那这样的话 43 00:01:52,880 --> 00:01:54,280 如果成功 44 00:01:54,320 --> 00:01:55,200 加载成功了 45 00:01:55,240 --> 00:01:57,720 那这时候呢它是相同的 46 00:01:57,760 --> 00:02:00,280 跟加载之前是相同一个进程 47 00:02:00,320 --> 00:02:01,520 ID是一样的 48 00:02:01,560 --> 00:02:04,280 但是它执行了完全不同的程序 49 00:02:04,320 --> 00:02:06,160 50 00:02:06,200 --> 00:02:11,120 那这时候你的代码 堆栈 堆 数据等等 51 00:02:11,160 --> 00:02:12,920 完全都被重写了 52 00:02:12,960 --> 00:02:15,480 这是程序加载的过程 53 00:02:15,520 --> 00:02:19,080 那在这里头实现这个加载的功能呢 54 00:02:19,120 --> 00:02:20,880 相对来说也比较单一 55 00:02:20,920 --> 00:02:24,600 也就是说我从外存上 56 00:02:24,640 --> 00:02:27,960 把可执行文件加载进来 57 00:02:28,000 --> 00:02:30,400 并且跳转到上面去执行就可以了 58 00:02:30,440 --> 00:02:32,400 这和我们引导扇区不一样 59 00:02:32,440 --> 00:02:34,560 引导扇区我读一块内容放那儿去 60 00:02:34,600 --> 00:02:37,040 我对它的格式是没有任何理解的 61 00:02:37,080 --> 00:02:38,360 但是我们现在 62 00:02:38,400 --> 00:02:40,160 到执行一个应用程序的时候 63 00:02:40,200 --> 00:02:42,160 你里头那可执行文件的格式 64 00:02:42,200 --> 00:02:43,720 是非常复杂的 65 00:02:43,760 --> 00:02:45,880 所以在这里呢主要的工作就是 66 00:02:45,920 --> 00:02:48,440 你可执行文件格式的识别 67 00:02:48,480 --> 00:02:50,320 那具体说起来呢在这儿呢 68 00:02:50,360 --> 00:02:53,600 分成三个主要的函数 69 00:02:53,640 --> 00:02:55,840 一个是sys_exec 70 00:02:55,880 --> 00:02:56,920 一个是do_execve 71 00:02:56,960 --> 00:02:59,840 一个是load_icode 72 00:02:59,880 --> 00:03:00,720 那第一个呢 73 00:03:00,760 --> 00:03:02,800 实际上是我在这里获取相应的参数 74 00:03:02,840 --> 00:03:05,240 核心加载功能是在这里头 75 00:03:05,280 --> 00:03:07,400 而在这里头呢最核心的部分呢 76 00:03:07,440 --> 00:03:09,920 是load_icode这实际上是 77 00:03:09,960 --> 00:03:12,280 识别你的可执行文件的格式 78 00:03:12,320 --> 00:03:13,520 并且把它加载到 79 00:03:13,560 --> 00:03:16,000 在内存里加载相应对应的段 80 00:03:16,040 --> 00:03:18,480 然后开始执行 81 00:03:18,520 --> 00:03:19,720 那在ucore里呢 82 00:03:19,760 --> 00:03:25,840 有一个sys_exec()这样一个内核函数 83 00:03:25,880 --> 00:03:26,720 它完成的呢 84 00:03:26,760 --> 00:03:30,720 就是获取我们创建进程的参数 85 00:03:30,760 --> 00:03:33,960 然后到这边呢它获取这些参数之后 86 00:03:34,000 --> 00:03:36,000 最后调到的结果 87 00:03:36,040 --> 00:03:39,560 我们可以从这儿可以看到do exec() 88 00:03:39,600 --> 00:03:41,400 这是内核关于 89 00:03:41,440 --> 00:03:44,640 进程加载执行的最主要的函数 90 00:03:44,680 --> 00:03:46,040 在这个函数里头呢 91 00:03:46,080 --> 00:03:48,600 它主要要做的工作呢 92 00:03:48,640 --> 00:03:53,560 是在这里创建新进程所需要的 93 00:03:53,600 --> 00:03:57,480 内存的相关的这些段结构 94 00:03:57,520 --> 00:03:58,440 比如在这儿 95 00:03:58,480 --> 00:03:59,840 这个地方是打开文件 96 00:03:59,880 --> 00:04:03,120 读你ELF文件的内容 97 00:04:03,160 --> 00:04:05,720 进来进行相应的创建 98 00:04:05,760 --> 00:04:07,600 然后下面这地方呢 99 00:04:07,640 --> 00:04:11,720 是它所需要的内存地址空间的 100 00:04:11,760 --> 00:04:15,480 映射的创建 mm_create() 101 00:04:15,520 --> 00:04:23,000 然后这地方是设置页表的基址 102 00:04:23,040 --> 00:04:26,160 然后这是我们刚才在那里看到的 103 00:04:26,200 --> 00:04:29,960 load_icode()这个函数 104 00:04:30,000 --> 00:04:34,560 这个函数在我们这里呢 105 00:04:34,600 --> 00:04:39,120 主要是解析相应的 106 00:04:39,160 --> 00:04:40,760 可执行文件的格式 107 00:04:40,800 --> 00:04:45,880 我们可以看到它在这里头的控制流图 108 00:04:45,920 --> 00:04:47,480 那在这里头呢 109 00:04:47,520 --> 00:04:52,440 它最主要的一个部分就是去读文件 110 00:04:52,480 --> 00:04:58,720 那这是它最主要的操作 111 00:04:58,760 --> 00:05:02,080 然后更具体的相关的实现呢 112 00:05:02,120 --> 00:05:03,520 希望大家下去之后 113 00:05:03,560 --> 00:05:06,400 利用这些工具可以很方便的看到 114 00:05:06,440 --> 00:05:07,840 115 00:05:07,880 --> 00:05:09,280 116 00:05:09,320 --> 00:05:11,080 好在加载完了之后呢 117 00:05:11,120 --> 00:05:12,840 那我们程序就开始执行了 118 00:05:12,880 --> 00:05:14,920 实际上我们在前面讲创建的时候 119 00:05:14,960 --> 00:05:15,840 也已经说过了 120 00:05:15,880 --> 00:05:17,720 我的系统里的第一个线程 121 00:05:17,760 --> 00:05:20,960 是在那个proc_init()里头来进行的 122 00:05:21,000 --> 00:05:22,640 proc_init()那在这里呢 123 00:05:22,680 --> 00:05:25,920 它手工的构造出一个进程控制块 124 00:05:25,960 --> 00:05:28,120 然后把它放到就绪队列里头 125 00:05:28,160 --> 00:05:29,400 然后开始执行 126 00:05:29,440 --> 00:05:32,320 这时候它起动的第一个用户态的程序 127 00:05:32,360 --> 00:05:34,280 就是这个init_main() 128 00:05:34,320 --> 00:05:35,760 在这里头呢做了 129 00:05:35,800 --> 00:05:40,000 我们下面会去走的shell的程序 130 00:05:40,040 --> 00:05:42,320 那在这里头具体的执行过程呢 131 00:05:42,360 --> 00:05:46,920 我们通过相应的实际代码阅读来完成 132 00:05:46,960 --> 00:05:52,800 接下来我们说ucore创建的第一个进程 133 00:05:52,840 --> 00:05:54,640 创建进程的函数呢 134 00:05:54,680 --> 00:06:04,520 是我们这里的proc_init() 135 00:06:04,560 --> 00:06:06,240 在这个函数里头呢 136 00:06:06,280 --> 00:06:09,720 我们刚才已经从kern_init()里头 137 00:06:09,760 --> 00:06:11,200 看到过这个函数 138 00:06:11,240 --> 00:06:14,000 它完成两个内核线程的创建 139 00:06:14,040 --> 00:06:17,440 idleproc()initproc() 140 00:06:17,480 --> 00:06:19,760 那在这儿呢我们可以通过 141 00:06:19,800 --> 00:06:24,200 它的流程图来给出一个简要的 142 00:06:24,240 --> 00:06:28,480 这是proc_init()的流程图 143 00:06:28,520 --> 00:06:29,720 好那在这里头我们看到 144 00:06:29,760 --> 00:06:32,720 它创建第一个标志性的操作呢 145 00:06:32,760 --> 00:06:34,880 是在这儿idleproc() 146 00:06:34,920 --> 00:06:38,760 在这里给它复制相应的状态信息 147 00:06:38,800 --> 00:06:44,680 这是利用alloc_proc()来创建 148 00:06:44,720 --> 00:06:48,000 idleproc的进程控制块数据结构 149 00:06:48,040 --> 00:06:52,280 然后下边这边呢就是initproc 150 00:06:52,320 --> 00:06:54,200 这是在这里呢 151 00:06:54,240 --> 00:06:57,400 用这里头kernel_thread()这个函数 152 00:06:57,440 --> 00:07:00,240 来创建一个内核线程 153 00:07:00,280 --> 00:07:04,600 执行的函数是init _main 154 00:07:04,640 --> 00:07:08,360 这个内核线程创建完毕之后呢 155 00:07:08,400 --> 00:07:12,040 它被赋值通过ID找 156 00:07:12,080 --> 00:07:15,640 赋值然后确认它就是initproc 157 00:07:15,680 --> 00:07:20,320 这是我们要找到的创建的第二个函数 158 00:07:20,360 --> 00:07:25,760 然后在这个init _main这个函数里 159 00:07:25,800 --> 00:07:27,200 它做了一些什么呢 160 00:07:27,240 --> 00:07:28,160 实际上它这里 161 00:07:28,200 --> 00:07:31,360 又创建了两个内核线程 162 00:07:31,400 --> 00:07:32,680 那这两个内核线程 163 00:07:32,720 --> 00:07:34,800 从这儿呢我们可以很清楚地看到 164 00:07:34,840 --> 00:07:37,080 这是第一个 165 00:07:37,120 --> 00:07:38,480 然后这是第二个 166 00:07:38,520 --> 00:07:38,720 167 00:07:38,760 --> 00:07:38,800 168 00:07:38,840 --> 00:07:38,880 169 00:07:38,920 --> 00:07:38,960 170 00:07:39,000 --> 00:07:39,040