源代码参见我的github: https://github.com/YaoZengzeng/jos

Part A: Multiprocessor Support and Cooperative Multitasking

Multiprocessor Support:

1、SMP(symmetric multiprocessing)是这样一种多处理器模型:每个CPU对于系统资源例如内存和IO总线都有平等的访问权限

2、在启动期间,处理器可以被分为两类,一个叫BSP(the bootstrap processor)用于系统的初始化以及启动操作系统;还有一类叫AP(the application processors),它们是在操作系统启动并且运行之后由BSP启动的。至于哪个处理器作为BSP是由硬件和BIOS决定的

3、在SMP系统中,每个CPU都有一个局部的APIC(LAPIC)。LAPIC用于系统间中断的转发,同时每个LAPIC还为与它相连的CPU提供了一个独特的id。在这个lab中,我们主要用到了LAPIC以下的几个功能:

(1)通过LAPIC ID来判断我们的代码运行在哪个CPU上(见cpunum())

(2)BSP发送处理器间中断(IPI)STARTUP至AP来唤醒其他处理器(见lapic_startap())

(3)利用LAPIC内置的时钟中断来支持原生的multitasking(见apic_init())

4、每个处理器都通过MMIO(memory-mapped IO)来访问它的LAPIC。在MMIO中,一部分物理内存和一些IO设备的寄存器进行了绑定,因此我们能像访问内存一样利用load/store指令去访问这些设备寄存器。LAPIC的这片内存区域开始于0xFE000000(4GB以下32MB),这样的地址对于我们显然是太高了。因此,JOS的虚拟内存从MMIOBASE开始留下了4MB的空间,用于设备的映射。

Application Processor Bootstrap:

1、在启动AP之前,BSP首先要收集有关多处理器系统的信息,例如CPU总数,它们的APIC ID,LAPIC的MMIO地址。在jos中,这些都是kern/mpconfig.c中的mp_init()通过读取BIOS内存中的MP配置表得到的。

2、kern/init.c中的boot_aps()驱动着AP的启动。AP首先在实模式下启动,就像boot/boot.S下的bootloader启动一样,将entry code(kern/mpentry.S)拷贝到实模式下可以寻址的内存空间。与bootloader不同的是,我们可以控制AP从何处开始执行代码,通常我们将entry code拷贝到0x7000(MPENTRY_PADDR)。

3、之后,boot_aps()逐个激活AP,通过向每个对应的AP的LAPIC发送STARTUP以及AP开始执行entry code的起始地址的CS:IP(即MPENTRY_PADDR)。kern/mpentry.S中的entry code和boot/boot.S很类似。经过一些简单的设置,AP进入保护模式,之后调用kern/init.c中的mp_main()中的C setup routine。通常boot_aps会一直等待AP将struct CpuInfo中的cpu_status字段置为CPU_STARTED之后才会去唤醒下一个CPU。

Per-CPU State and Initialization

1、kern/cpu.h定义了许多per-CPU state,包括struct CpuInfo,其中包含了每个CPU独有的一些变量,cpunum()总会返回调用它的CPU的ID,它可以作为cpus这样的数组的下标。其中thiscpu是当前CPU的struct CpuInfo

2、Per-CPU kernel stack:因为多个CPU可能同时陷入内核,因此需要为每个处理器分配独立的kernel stack,防止它们执行时互相干扰

3、Per-CPU TSS and TSS descriptor:用于标示每个CPU位于kernel stack的位置

4、Per-CPU current environment pointer:因为每个CPU上可以同时运行用户进程,因此我们用cpus[cpunum()].cpu_env(或者thiscpu->cpu_env)来表示在当前CPU上运行的environment。

5、Per-CPU system registers:所有的寄存器,包括系统寄存器都是每个CPU私有的。因此那些初始化寄存器的指令,例如lcr3(), ltr(), lgdt(), lidt(), 等等都必须在每个CPU上都执行一遍。env_init_percpu和trap_init_percpu()就是用于这个目的。

Locking

1、big kernel lock:是一种全局锁,当environment进入内核的时候就持有该锁,当environment重回用户模式的时候就释放该锁。在这种模型之下,处于用户模式下的environment可以在任意可获得的CPU上运行,但是只有一个environment能在内核模式下运行,其他任何environment想要进入内核都必须等待。

Part B: Copy-on-Write Fork

User-level page fault handling

1、用户级别的copy-on-write fork()需要知道在操作写保护的页面时产生的page faults,因此我们需要先实现它。而Copy-on-Write又是用户级别page fault handling众多用途中的一种。

2、为了处理page faults,user environment需要向JOS kernel注册一个page fault handler entrypoint。user environment 可以通过系统调用sys_env_set_pgfault_upcall注册page fault entrypoint。并且我们已经在strut Env中加入了一个新的成员env_pgfault_upcall,用于记录该信息

3、static int sys_env_set_pgfault_upcall(envid_t envid, void *func):当envid代表的environment产生了一个page fault时,内核会向exception stack压入一个fault record,之后进入func运行

Normal and Exception Stacks in User Environments

1、当程序正常执行的时候,user environment通常会运行在normal user stack:它的ESP从USTACKTOP开始,栈中的数据会存放在USTAKTOP-PGSIZE到USTACKTOP-1的范围内。当在用户态发生page fault时,kernel会在一个新的叫做user exception stack的栈上运行user environment的page fault handler。事实上,我们会让JOS kernel代表user environment实现自动的“stack switching”。就像x86处理器已经自动为JOS实现在用户态到内核态的"stack switching"。

2、JOS中的user exception stack同样是一个页的大小,它的栈顶的virtual address是UXSTACKTOP,因此它的合法地址是UXSTACKTOP-PGSIZE到UXSTACKTOP-1。当运行在exception stack的时候,用户态的page fault handler可以使用普通的JOS系统调用来映射新的页或者调整页面的映射,从而解决之前造成page fault的问题。之后,page fault handler返回,回到之前在普通堆栈造成page fault的代码。

3、每个user environment如果想要支持user-level的page fault handling需要为它自己的exception stack分配页表,利用之前的sys_page_alloc()系统调用。

Invoking the User Page Fault Handler

1、我们将page fault 发生时,user environment的状态称为trap-time state

2、当一个user environment 已经运行在user exception上时,如果又发生了一个exception,我们需要从当前的tf->tf_esp而不是UXSTACKTOP重新开始一个stack frame。

 Implementing Copy-on-Write Fork

fork()和dumbfork()一样,也会创建一个新的进程,然后扫描parent environment的整个地址空间,然后设置子进程相关的页面映射。它们最大的不同是,dumbfork拷贝页,而fork只拷贝页面映射。fork只会在有一个进程要进行写操作时,才会进行页拷贝。

1、fork()的基本运行流程如下:

(1)、父进程利用之前的set_pgfault_handler设置pgfault()作为page fault handler。

(2)、父进程调用sys_exofork()生成子进程

(3)、对于每个一个地址低于UTOP,并且属性为writable或者为copy-on-write的页面,父进程调用duppage,将子进程中的该页设置为copy-on-write,并且反过来将自己地址空间的该页重映射为copy-on-write。dupage设置了两个进程的PTE,所有两个进程中的该页都是不可写的,并且标志为PTE_COW区分与单纯的只读页。但是exception stack不是用上述方式重映射的,我们需要为子进程的exception stack重新分配一个页面。因为page fault handler会运行在exception stack上。fork()同时还要处理那些既不是writable或者copy-on-write的页面。

(4)、父进程还需要设置子进程的page fault entrypoint

(5)、子进程现在可以准备运行了,父进程需要将它标记为runnable

2、每当一个environment要写一个copy-on-write的页面时,都会产生一个page fault。接下来是page fault handler的处理流程:

(1)、kernel将page fault传递到_pgfault_upcall,它会调用fork()的pgfault()

(2)、pgfault()检查这是一个写错误并且该页的PTE被标志为PTE_COW。否则panic

(3)、pgfault()获得一个新的页面,将它映射到一个临时的位置,并且将相应的页的内容拷贝到里面。之后,将该页重新映射到对应的位置,并且设置好权限位。

Part C: Preemptive Multitasking and Inter-Process communication(IPC)

Clock Interrupts and Preemption

运行user/spin 测试程序。这个测试进程产生fork一个child environment,这个child environment 只是在获取CPU之后陷入不断的循环。而不管是parent environment或是kernel都不能再获取CPU了。这显然不是我们想要的,因此,为了让内核抢占一个正在运行的environment,再次夺回CPU的控制权,我们需要扩展JOS kernel,从而支持来自于clock设备的外部硬件时钟中断。

Interrupt discipline

external interrupts通常被称为IRQ。总共有16个可能的IRQ,标记从0到15,并且从IRQ编号到IDT表的映射不是固定的。在picirq.c的pic_init函数将0到15的IRQ映射到IRQ_OFFSET到IRQ_OFFSET+15的表项中。

在inc/trap.h中,IRQ_OFFSET被定义为32,因此IDT 32到47被映射为IRQ的0到15。比如,clock interrupt是IRQ 0。因此,IDT[IRQ_OFFSET + 0](或者IDT[32])包含了clock interrupt处理程序在内核中的地址。之所以要设置IRQ_OFFSET是为了避免device interrupt和不和processor exception造成的混淆。事实上,在早期运行MS-DOS的PC机上,IRQ_OFFSET的值其实为0,这样就会在处理processor exception和hardware interrupts的时候造成混淆。

在JOS中,当运行在内核模式时,外部的设备中断总是被禁止的(和xv6类似,在用户空间是可用的)。外部中断通常通过eflags寄存器的FL_IF标志位进行控制。当它被置位时,外部中断是可用的。通常有好几种方式对FL_IF位进行修改,不过我们将它简化了,我们只会在切换用户模式时保存或者恢复eflag寄存器的时候对它进行改变。

我们必须确保在运行user environment的时候,FL_IF标志位是置位的,这样我们才能确保在中断到来的时候,我们的中断处理代码能被执行。除此之外,interrupts始终是被忽略或是屏蔽的。我们在执行bootloader的时候就把中断屏蔽了,直到现在为止都没有再启动过它。

Handling Clock Interrupt

在user/spin这个程序中,当child environment开始运行之后,它就进入无限的循环,kernel就再也夺不回控制权了。我们必须对硬件进行编程,让它能定时产生时钟中断,这样我们才能让kernel重新获得控制权,从而运行其他的用户进程。

其中对于lapic_init和pic_init进程的调用,我们已经能够让时钟和中断控制器定时产生中断了,接下来要做的就是对这些中断进行处理。

Inter-Process communication(IPC)

IPC in JOS

我们需要为JOS kernel添加几个简单的系统调用来提供简单的进程间通信的机制。即需要实现sys_ipc_recv和sys_ipc_try_send两个系统调用,以及ipc_recv和ipc_send两个库函数。在JOS中,两个user environment之间相互传递的“信息”主要由两部分组成:一个32位的变量,以及一个可选的page mapping。允许environment之间能够传递page mapping,可以让传递的数据更多,并且能够让进程之间共享内存变得更容易。

Sending and Receiving Messages

为了能够接收message,当前environment调用sys_ipc_recv。该系统调用重新调度了当前environment,并且在接收到message之前不再重新运行。当一个environment处于接收状态时,任何其他的environment都能向它发送message,而不是某个特定的environment或者具有父子关系的environment。同时,IPC是经过精心设计并且安全可靠的,因此一个environment不会因为另一个environment给它发了一个message就出问题,除非目标进程自己本身就有问题。

当需要传递一个值时,environment需要调用sys_ipc_try_send,参数为接收environment的id和要传输的值。如果目标进程刚好处于接收状态,则将信息发送出去,并返回0。否则返回-E_IPC_NOT_RECV表示目标进程并非处于接收状态。

库函数ipc_recv会接手sys_ipc_recv的任务,并且在当前environment的struct Env中获取信息。同理,库函数ipc_send会接手sys_ipc_try_send直到发送成功。

Transferring Pages

当一个environment调用sys_ipc_recv,获得了一个有效的dstva(小于UTOP),那么,该environment表示愿意接受这个page mapping。当发送者发送了一个page mapping之后,那么这个page就要映射到接受者地址空间的dstva上。如果接受者之前已经在dstva上已经有页面映射了,那么之前的页面将被unmapped。

当一个environment用合法的srcva(即小于UTOP)调用sys_ipc_try_send,这意味着发送者希望将位于srcva的页面发送给接收者,权限为perm。在一个成功的IPC之后,发送者依然在srcva保持原有的页面映射,同时接收者在dstva拥有对同一物理页面的映射。因此,这个物理页就做到了在发送者和接收者之间的共享。

当发送者和接收者都不需要页面传输时,那么页面就不传输了。在任何一次IPC之后,接收者Env的env_ipc_perm域都会被设置为接收到的页的权限,如果没有页面被接收的话,设置为0。

MIT jos 6.828 Fall 2014 训练记录(lab 4)的更多相关文章

  1. MIT jos 6.828 Fall 2014 训练记录(lab 6)

    源代码参见我的github: https://github.com/YaoZengzeng/jos 在这个实验中将实现一个基于Intel 82540M(又称E1000)的网卡驱动.不过,一个网卡驱动还 ...

  2. MIT jos 6.828 Fall 2014 训练记录(lab 2)

    注: 源代码参见我的github:https://github.com/YaoZengzeng/jos Part1 : Physical Page Management mem_init函数: /*该 ...

  3. MIT jos 6.828 Fall 2014 训练记录(lab 1)

    注: 源代码参见我的github:https://github.com/YaoZengzeng/jos Part 1: PC Bootstrap +------------------+ <- ...

  4. MIT jos 6.828 Fall 2014 训练记录(lab 5)

    源代码参见我的github: https://github.com/YaoZengzeng/jos File system perliminaries 我们开发的是一个单用户的操作系统,只提供了足够的 ...

  5. MIT jos 6.828 Fall 2014 训练记录(lab 3)

    注:源代码参见我的github: https://github.com/YaoZengzeng/jos Part A : User Environments and Exception Handlin ...

  6. MIT 操作系统实验 MIT JOS lab2

    MIT JOS lab2 首先把内存分布理清楚,由/boot/main.c可知这里把kernel的img的ELF header读入到物理地址0x10000处 这里能够回想JOS lab1的一个小问.当 ...

  7. 台州学院maximum cow训练记录

    前队名太过晦气,故启用最大牛 我们的组队大概就是18年初,组队阵容是17级生詹志龙.陶源和16级的黄睿博. 三人大学前均无接触过此类竞赛,队伍十分年轻.我可能是我们队最菜的,我只是知道的内容最多,靠我 ...

  8. MIT 操作系统实验 MIT JOS lab1

    JOS lab1 首先向MIT还有K&R致敬! 没有非常好的开源环境我不可能拿到这么好的东西. 向每个与我一起交流讨论的programmer致谢!没有道友一起死磕.我也可能会中途放弃. 跟丫死 ...

  9. MIT JOS学习笔记03:kernel 02(2016.11.08)

    未经许可谢绝以任何形式对本文内容进行转载! 本篇接着上一篇对kernel的分析. (5)pte_t * pgdir_walk(pde_t *pgdir, const void *va, int cre ...

随机推荐

  1. 重新想象 Windows 8 Store Apps (40) - 剪切板: 复制/粘贴文本, html, 图片, 文件

    [源码下载] 重新想象 Windows 8 Store Apps (40) - 剪切板: 复制/粘贴文本, html, 图片, 文件 作者:webabcd 介绍重新想象 Windows 8 Store ...

  2. u-boot移植总结(四)u-boot-2010.09框架分析

    (一)本次移植是基于FL2440,板子的基本硬件: CPU 型号为S3C2440,基于ARM920T,指令集ARMV4,时钟主频400MHz SDRAM H57V2562GTR-75C 2片*32MB ...

  3. Access restriction : The constructor BASE64Decoder() is not accessible due to restriction on required library

    1.问题描述 找不到包  sun.misc.BASE64Encoder 2. 解决方案 只需要在project build path中先移除JRE System Library,再添加库JRE Sys ...

  4. tomcat filewatchdog but has failed to stop it原因以及解决方法

    停止tomcat,有些时候会报The web application [/XXX] appears to have started a thread named [FileWatchdog] but ...

  5. 通过原生js添加div和css

    function createStyle(){ return"*{padding:0;margin:0;border:0}.loading{width:640px;height:1024px ...

  6. log4net 日志框架的配置

    log4net 日志框架的配置——静态文件(一) 添加对log4net程序集的引用 选择程序集文件添加引用即可,需要注意的是需要添加相应程序版本的程序集,如果你的应用是基于.netFramework2 ...

  7. 配置windows失败,还原更新,请勿关机

    同事叫我帮忙弄一下电脑,开机,出现"配置Windows Update失败,还原更改,请勿关闭计算机",我从来不更新Windows Update,更新都为成功,第一次遇到失败了,不知 ...

  8. 微信支付redirect_uri参数错误

    在做微信支付的时候,点击提交,出现“redirect_uri参数错误”.经过查找,需要在后台正确设置授权域名.大致步骤如下:1.首先登录微信公众号管理后台2.点击开发者中心3.找到 网页账号—> ...

  9. Android 6.0权限管理

    Android 6.0权限管理 关于权限管理 Android6.0 发布之后,Android 的权限系统被重新设计.在 23 之前 App 的权限只会在用户安装的时候询问一次,App一旦安装后就可以使 ...

  10. JAVA基础学习day14--集合一

    一.集合的出现 1.1.集合简述 面向对象语言对事物的体现都是以对象形式,为了方便对多个对象的操作,就对象对象进行存储,集合就是存仪储对象最常用的一种试 1.2.数组和集合都是容器 数组也存对象,存储 ...