# 十、
来源:[JOS学习笔记(十)](http://blog.csdn.net/roger__wong/article/details/9348057)
> 神说、进程要有多个、可以分片、切换、互不影响.
> 并要支持多任务、多处理器并行.事就这样成了。
> 于是 神造了多个内核栈,又开辟多个寄存器。
> 就把这些摆列在内存里、内核空间里、
> 管理多核,分别任务. 神看着是好的.
> 有晚上、有早晨、是第四日。
博主写完论文,又写了个简单的编译器,然后回来再拾起来差点烂尾的JOS发现代码已经完全看不懂了,花了一整天时间复习之前自己写的博客,才勉强做了实验四的一点。
本篇博客内容对应课程地址( http://pdos.csail.mit.edu/6.828/2011/labs/lab4/ )locking标签之前的内容,也就是多核CPU的初始化工作。博客分以下2部分来进行阐述,1、多核启动流程,2、实验部分
## 1、多核CPU启动流程
在内核加载和进行初始化时,即便是有一个多核CPU,也只能使用一个核,为了和JOS实验中的描述一致,我们这里暂且把多个核(Core)称之为多个CPU。
内核启动过程中用于执行代码的CPU叫做bootstrap processor (BSP),其它还未被使用的CPU叫做application processors (APs),所以在内核执行的时候必然会有的一个过程就是使用BSP来激活AP的过程。
对应JOS代码是init.c中的init主函数,其关键片段如下:
![](https://box.kancloud.cn/2015-12-24_567b6e7aeed1f.jpg)
lab2的 mem_init,lab3的env_init和trap_init,lab4的mp_init和lapic_init在这之后需要补充一个Big kernel Lock 暂时不用管,然后boot_ap函数启动所有的CPU,接着有多少个CPU就建立多少个ENV。
在启动过程中,mp_init和lapic_init是和硬件以及体系架构紧密相关的,通过读取某个特殊内存地址(当然前提是能读取的到,所以在mem_init中需要修改进行相应映射),来获取CPU的信息,根据这些信息初始化CPU结构,大致流程就是这样,博主因为能力所限,这部分就不进行详细分析了。
boot_ap是个很有趣的函数。在函数中首先找到一段用于启动的汇编代码,该代码和上一章实验一样是嵌入在内核代码段之上的一部分,其中mpentry_start和mpentry_end是编译器导出符号,代表这段代码在内存(虚拟地址)中的起止位置,接着把代码复制到MPENTRY_PADDR处。随后调用lapic_startap来命令特定的AP去执行这段代码。
这段汇编(mpentry.S)中所做的工作和entry.S所做的工作基本相同。需要注意的一点是每个CPU都有自己的寄存器和栈,需要启动的AP目前还处于实模式,并且内存也没有开启分页,因此所有和符号地址相关的操作都要非常谨慎的转换为物理地址。
之后控制流跳转到mp_main,值得注意的是从mpentry.S开始这些代码都是执行在AP上的,此时BSP正在等待(while循环)AP启动成功。在mp_main里可以看到AP初始化自己的env,trap等,接着改变自己的cpu数据结构中的cpu状态标志为启动成功,然后进入死循环空转。BSP得到AP启动成功的信息后接着尝试启动下一个AP。
整个启动流程大概就是如此,下面进入实验过程。
## 2、实验
### 2.1
第一个任务是因为我们把代码拷贝到了MPENTRY_PADDR处,所以要在初始化Page的时候把这一页从Page_Free_List中取出来,以防止该页被分配出去导致代码拷贝的失败进而不能正确启动AP。
![](https://box.kancloud.cn/2015-12-24_567b6e7b0bd54.jpg)
只需要在pmap.c的page_init函数的最后加上以上几行代码,首先获取该地址对应的Page结构,然后从page_free_list中剥离就行。
如果正确的话,此时应该能通过check_page_free_list函数
### 2.2
第二个实验要求为每一个核映射其内核栈,也就是对于编号为i的cpu,需要映射KSTACKTOP-i\*(KSTKSIZE+KSTKGAP)-KSTKSIZE 到KSTACKTOP-i\*(KSTKSIZE+KSTKGAP) 这块虚拟地址空间到符号percpu_kstacks[i]所对应的物理地址处,改变mp_mem_init函数,代码如下:
![](https://box.kancloud.cn/2015-12-24_567b6e7b1bb4c.jpg)
值得注意的是,虽然课程中说此时应该可以通过check_kern_pgdir,但实际上是通不过的。原因在于BSP的内核栈被映射了两次,第一次是在lab3中将内核栈映射到了bootstack,当时只有一个CPU,自然映射的是BSP的堆栈;第二次是在我们刚才写的代码里,将BSP(也就是cpunum()==0的CPU)的内核栈映射到了percpu_kstack[0]。诡异的是bootstack地址不等于percpu_kstack地址,而check_kern_pgdir里居然要求两种映射都存在的情况下才能通过。这显然是不可能的,感觉是JOS实验设计中的一个疏忽吧。因此我把检查bootstack的那段代码注释掉了,这样才得以通过。
### 2.3
第三个实验是需要完成trap_init_percpu()函数,给相应的CPU加载正确的TSS段选择子,代码如下:
![](https://box.kancloud.cn/2015-12-24_567b6e7b2df25.jpg)
根据函数中的暗示+照葫芦画瓢,不难把代码改成上述的样子。
至此就完成了LAB4的PART A的一小部分,运行目前的OS可以看到(使用make qemu CPUS=4)可以发现创建了9个env,然后出现了未定义的sys_call,系统panic。