0 00:00:00,000 --> 00:00:06,320 1 00:00:06,520 --> 00:00:08,320 接下来我们看第二部分 2 00:00:08,360 --> 00:00:09,400 第二部分讲的是什么呢 3 00:00:09,440 --> 00:00:13,240 讲的是说当我们的uCore操作系统得到控制权之后 4 00:00:13,280 --> 00:00:14,720 它还要进一步执行 5 00:00:14,760 --> 00:00:16,800 它需要了解函数调用关系 6 00:00:16,840 --> 00:00:20,160 那为什么要了解函数调用关系 这其实很重要 7 00:00:20,200 --> 00:00:21,880 特别是我们想象一下 8 00:00:21,920 --> 00:00:24,480 如果程序在某一点崩了 9 00:00:24,520 --> 00:00:25,960 那我们知道函数调用关系的话 10 00:00:26,000 --> 00:00:29,360 我们就可以知道从哪个地方调用 11 00:00:29,400 --> 00:00:30,720 到最后出现了问题 12 00:00:30,760 --> 00:00:34,480 你可以知道整个一个函数的执行调动的过程 13 00:00:34,520 --> 00:00:37,280 那可以很便于我们去分析理解错误在什么地方 14 00:00:37,320 --> 00:00:41,120 这个是一个很直接的用处 我们后面也会用到 15 00:00:41,160 --> 00:00:44,120 第二个你也知道 我们的编译器 16 00:00:44,160 --> 00:00:47,680 我们的GCC和我们的操作系统在执行程序的时候 17 00:00:47,720 --> 00:00:49,240 大致之间怎么一个协调 18 00:00:49,280 --> 00:00:50,680 GCC怎么产生这个代码 19 00:00:50,720 --> 00:00:56,520 使得它可以把函数的参数给压入到栈里面 20 00:00:56,560 --> 00:01:02,560 具体被调的函数能够取这些参数去执行 21 00:01:02,600 --> 00:01:04,040 最后还能够正确的返回 22 00:01:04,080 --> 00:01:07,280 这个执行过程应该说是很Detail的 23 00:01:07,320 --> 00:01:12,800 应该说在我们的编译 计算机原理和操作系统都相关 24 00:01:12,840 --> 00:01:15,520 这里面希望大家对它有进一步的了解 25 00:01:15,560 --> 00:01:19,200 能够更好的去分析理解这个程序的执行过程 26 00:01:19,240 --> 00:01:26,800 特别是操作系统的执行过程 27 00:01:26,840 --> 00:01:29,960 好 那我们这个分析又不太一样 28 00:01:30,000 --> 00:01:32,120 它不是在C这一级分析的 29 00:01:32,160 --> 00:01:35,920 是在我们机器码这一级来分析的 30 00:01:35,960 --> 00:01:38,160 我们这边可以看出来 这有一个栈 31 00:01:38,200 --> 00:01:42,080 这是一个Stack 这个Stack里面 32 00:01:42,120 --> 00:01:44,880 这是高地址 这是低地址 正好相反 33 00:01:44,920 --> 00:01:46,040 这是一段代码 34 00:01:46,080 --> 00:01:48,760 这段代码虽然是汇编代码 35 00:01:48,800 --> 00:01:51,200 但是我们前面也介绍了 这是基本的寄存器 36 00:01:51,240 --> 00:01:53,080 完成了一个Push操作 37 00:01:53,120 --> 00:01:56,640 把寄存器内容压栈 38 00:01:56,680 --> 00:01:59,320 调用call 调用一个函数 39 00:01:59,360 --> 00:02:01,200 这个foo可以代表一个地址 40 00:02:01,240 --> 00:02:02,840 调用完这个函数之后 41 00:02:02,880 --> 00:02:05,720 这个函数返回之后会执行一些pop操作 42 00:02:05,760 --> 00:02:10,760 来把刚才压栈的信息给弹出来 43 00:02:10,800 --> 00:02:12,800 后面又可以继续执行了 44 00:02:12,840 --> 00:02:14,040 这实际上是一个调用过程 45 00:02:14,080 --> 00:02:17,640 会有压栈 出栈 中间是call的过程 46 00:02:17,680 --> 00:02:19,840 那么这个函数在这 foo 47 00:02:19,880 --> 00:02:22,040 它会完成进一步的处理 48 00:02:22,080 --> 00:02:23,880 那这个处理到底什么意思 我们可以看看 49 00:02:23,920 --> 00:02:26,080 用这个图 可视化来展示一下 50 00:02:26,120 --> 00:02:29,000 当执行到这个地方的时候 51 00:02:29,040 --> 00:02:34,720 基本上是EBP指向caller's caller's EBP 52 00:02:34,760 --> 00:02:36,680 这好像看起来不知道什么意思 53 00:02:36,720 --> 00:02:38,400 但是没关系 我们走一下 54 00:02:38,440 --> 00:02:39,880 看接下来会出现什么情况 55 00:02:39,920 --> 00:02:43,800 特别是执行push1 &ebp的时候 56 00:02:43,840 --> 00:02:45,920 我们看到一些不一样的地方 57 00:02:45,960 --> 00:02:50,000 push ebp还有movl这两条指令 58 00:02:50,040 --> 00:02:54,600 当执行push al的时候 59 00:02:54,640 --> 00:02:57,040 实际上它把caller 60 00:02:57,080 --> 00:03:01,080 就是调用foo这个函数的函数 就是caller 61 00:03:01,120 --> 00:03:04,880 它会把相应的寄存器压栈 62 00:03:04,920 --> 00:03:10,640 再接下来呢 会把相应的参数压栈 63 00:03:10,680 --> 00:03:12,760 相当于是我调这个函数 64 00:03:12,800 --> 00:03:13,840 我需要有一个参数 65 00:03:13,880 --> 00:03:17,480 这些参数也压栈 这是实参 66 00:03:17,520 --> 00:03:23,440 然后再接下来 它调这个函数 67 00:03:23,480 --> 00:03:24,960 在执行这个函数的时候 68 00:03:25,000 --> 00:03:27,640 它会把它的返回地址压栈 69 00:03:27,680 --> 00:03:29,840 返回地址在哪呢 返回地址在这个地方 70 00:03:29,880 --> 00:03:33,000 call下面条语句 是它的返回地址 71 00:03:33,040 --> 00:03:34,440 Return address 72 00:03:34,480 --> 00:03:42,920 压栈之后就跳转到这个地方执行 73 00:03:42,960 --> 00:03:46,080 当执行这两条的时候 需要注意 74 00:03:46,120 --> 00:03:57,560 EBP到这来 同时这是做了一个push之后 75 00:03:57,600 --> 00:03:58,360 这里面的内容是这样的 76 00:03:58,400 --> 00:04:01,720 同时让ESP的内容付给了EBP 77 00:04:01,760 --> 00:04:05,160 所以说EBP从指向这跳到这 78 00:04:05,200 --> 00:04:06,680 指向这个地方 79 00:04:06,720 --> 00:04:08,040 虽然跳到这么一个地方 80 00:04:08,080 --> 00:04:11,480 但其实隐含着建立一个链接关系 81 00:04:11,520 --> 00:04:15,400 这里面的内容其实指向了这个地址 82 00:04:15,440 --> 00:04:21,480 那么这个呢 就形成了所谓的调用栈的链 83 00:04:21,520 --> 00:04:23,040 我们根据这个信息 84 00:04:23,080 --> 00:04:28,080 就可以把更深层次的调用关系给表述出来 85 00:04:28,120 --> 00:04:35,560 这也是我们在Lab 1中需要去理解和实现的一块练习 86 00:04:35,600 --> 00:04:38,360 执行到这的时候其实可以看到 87 00:04:38,400 --> 00:04:40,560 它也做了很多后续的一些压栈工作 88 00:04:40,600 --> 00:04:43,560 把它的局部变量给放到栈里面去 89 00:04:43,600 --> 00:04:44,960 然后做相应的处理 90 00:04:45,000 --> 00:04:47,120 假设执行完毕之后 91 00:04:47,160 --> 00:04:50,040 最后会做一个pop操作 92 00:04:50,080 --> 00:04:54,440 使得EBP又回来了 93 00:04:54,480 --> 00:04:56,920 最后在执行return的时候 94 00:04:56,960 --> 00:04:59,880 因为你这时候ESP已经指向了Return address 95 00:04:59,920 --> 00:05:03,760 所以说做Return 最后一步的时候 96 00:05:03,800 --> 00:05:09,920 它会根据这个Return address跳回来执行 97 00:05:09,960 --> 00:05:11,440 同时把它清掉 98 00:05:11,480 --> 00:05:13,360 所以你看它会执行到这来 99 00:05:13,400 --> 00:05:14,640 在执行这条之后 100 00:05:14,680 --> 00:05:18,600 又把刚才压的实参这个弹出来了 101 00:05:18,640 --> 00:05:21,000 又回复到这么一个状态 102 00:05:21,040 --> 00:05:23,680 接下来继续往下走相应的指令 103 00:05:23,720 --> 00:05:26,480 这是整个C函数的调用过程 104 00:05:26,520 --> 00:05:28,720 你看可以涉及到有caller 105 00:05:28,760 --> 00:05:32,720 有压栈 压栈完成对于实参的一个传递 106 00:05:32,760 --> 00:05:38,000 然后对调用函数的返回地址的压栈 107 00:05:38,040 --> 00:05:39,720 然后再调用函数 108 00:05:39,760 --> 00:05:43,440 调用函数有相应的对EBP和ESP的处理 109 00:05:43,480 --> 00:05:44,600 形成一个调用栈 110 00:05:44,640 --> 00:05:46,600 调用栈的一个链接关系 111 00:05:46,640 --> 00:05:47,720 然后完成相应处理之后 112 00:05:47,760 --> 00:05:50,360 还要把所有的参数和释放掉 113 00:05:50,400 --> 00:05:53,960 使得接下来的形参还有局部变量 114 00:05:54,000 --> 00:05:58,800 都能够回复到一个正常的位置去继续去执行 115 00:05:58,840 --> 00:06:06,360 那么这就是C的函数调用的一个大致的实现 116 00:06:06,400 --> 00:06:07,600 这里面需要注意 117 00:06:07,640 --> 00:06:09,200 我们这里面用了很多push操作 118 00:06:09,240 --> 00:06:13,840 push操作其实是说把这些参数是用内存放在栈里面 119 00:06:13,880 --> 00:06:16,120 栈是内存的一块 所以是放在栈里面运行的 120 00:06:16,160 --> 00:06:19,000 其实为了效率更高 121 00:06:19,040 --> 00:06:21,400 把参数 或者函数的返回值 122 00:06:21,440 --> 00:06:24,160 也可以通过寄存器来传递 123 00:06:24,200 --> 00:06:24,760 同时效率更高一些 124 00:06:24,800 --> 00:06:31,160 取决于我们的编译器生成的调用的一个规则是怎么来做的 125 00:06:31,200 --> 00:06:35,600 另外不是需要去保存恢复所有的寄存器 126 00:06:35,640 --> 00:06:36,840 因为在执行的过程当中 127 00:06:36,880 --> 00:06:38,880 你可能用不到所有的寄存器 128 00:06:38,920 --> 00:06:40,320 你可能只是破坏了一部分寄存器 129 00:06:40,360 --> 00:06:43,480 所以我们前面说 最开始做保存寄存器 130 00:06:43,520 --> 00:06:45,720 后面做恢复寄存器的时候 131 00:06:45,760 --> 00:06:47,080 寄存器并不一定是所有的 132 00:06:47,120 --> 00:06:52,760 这主要是为了简单而做一个表述 133 00:06:52,800 --> 00:06:57,560 这里面如果提高效率的话就不需要这样 134 00:06:57,600 --> 00:07:03,640 好 如果大家对这个栈的执行过程有一个更深入的了解的话 135 00:07:03,680 --> 00:07:07,120 我们可以去返回下面一个网址 136 00:07:07,160 --> 00:07:10,720 更进一步去看看相关的一些信息 137 00:07:10,760 --> 00:07:13,840 这是关于C的函数调用这一块 138 00:07:13,880 --> 00:07:14,440 给大家介绍到这 139 00:07:14,480 --> 00:07:14,520 140 00:07:14,560 --> 00:07:14,600