【施工中】MIT 6.828 lab 3: User Environments

2019年3月3日 0 作者 CrazyKK

JOS的environments基本可以理解成”process”进程的同义词,但是由于”process”是一个unix术语,因此使用environment这个词.

Part A: User Environments and Exception Handling

查看 kern/env.c文件,看到三个全局变量:

envs会在JOS启动后会指向一个Env structures的数组,表示JOS中的全部environments. 理论上,JOS kernel最多能支持NENV个同时运行的environments. 但是实际上不会远不会达到这个数量.

env_free_list是一个链表结构,用来存放当前没有在运行的Env structure.. 和page_free_list 类似.

curenv表示的是当前正在运行的environment,当JOS刚刚启动,第一个environment运行之前,curenv的值为NULL.

接下来我们来阅读一下inc/env.h文件

 

  • env_tf: 用来在切换环境时保存各种register的值,以便之后恢复现场.
  • env_link: 用于构成一个链表结构,指向喜爱一个空闲的 environment.
  • env_id: 用于唯一标识使用当前这个Env structure(也就是envs数组中的某个位置)的environment的ID.当这个environment终止时,envs数组中的用一个位置可能会被re-allocate一个新的environment,但是env_id是不同的.虽然env_id不同,但是env_id的最后10bit是用来标识在envs的下标的,如果使用的是envs数组中的同一个位置,这部分是相同的.具体可以参考inc/env.h.
  • env_parent_id: 创建这个environment的environment 的env_id. 就是父进程id…
  • env_type:  用于区分不同种类的环境.对于大部分环境,类型都是ENV_TYPE_USER.
  • env_status: 用来标识当前这个environment的状态.
    • ENV_FREE: 标识一个environment是inactive的,因此在env_free_list上.
    • ENV_RUNNABLE: 标识一个environment等待运行在处理器上.
    • ENV_RUNNING:标识正在运行
    • ENV_NOT_RUNNABLE: 标识一个目前active的环境,但是没有准备好运行,原因可能是正在等待一个其他environment的交互.
    • ENV_DYING: 可以类比”僵尸进程”
  • env_pgdir: 当前这个environment的page directory.

 

Allocating the Environments Array

Modify  mem_init() in kern/pmap.c to allocate and map the  envs array. This array consists of exactly  NENV instances of the  Env structure allocated much like how you allocated the  pages array. Also like the  pages array, the memory backing  envsshould also be mapped user read-only at  UENVS (defined in inc/memlayout.h) so user processes can read from this array.

You should run your code and make sure  check_kern_pgdir() succeeds.

和mem_init中申请pages的空间是一样的.

 

后面有空再写

Creating and Running Environments

由于现在JOS还没有一个文件系统,因此将可执行文件以ELF格式嵌入到kernel中.

练习2:

In the file env.c, finish coding the following functions:

env_init()
Initialize all of the  Env structures in the  envs array and add them to the  env_free_list. Also calls  env_init_percpu, which configures the segmentation hardware with separate segments for privilege level 0 (kernel) and privilege level 3 (user).
env_setup_vm()
Allocate a page directory for a new environment and initialize the kernel portion of the new environment’s address space.
region_alloc()
Allocates and maps physical memory for an environment
load_icode()
You will need to parse an ELF binary image, much like the boot loader already does, and load its contents into the user address space of a new environment.
env_create()
Allocate an environment with  env_alloc and call  load_icode to load an ELF binary into it.
env_run()
Start a given environment running in user mode.

As you write these functions, you might find the new cprintf verb  %e useful — it prints a description corresponding to an error code. For example,

will panic with the message “env_alloc: out of memory”.

env_init()是这些函数中最简单的一个,只需要注意为了保证env_free_list的顺序和envs array的相同,需要倒序遍历.

接下来看env_setup_vm函数,作用是为新的environment初始化一个page directory. 每个environment都有一个自己的page directory.

注释里提示用kern_pgdir作为一个template… 说实话完全没get到含义…查阅资料发现其实就是…copy过来的意思…orz  注意头文件string.h中可能有些有用的函数,比如memcpy

接下来是函数region_alloc,作用是为一个函数分配并映射物理内存.使用page_insert即可

接下里要实现的函数是load_icode(),作用是将ELF格式的二进制文件加载到新环境的用户地址空间.

这个函数是相对比较难实现的一个,可以参考boot/main.c中boot loader加载 kernel image的过程.区别在于,boot loader是从disk中加载kernel image,而load_icode()要加载的二进制文件已经在memory中了.

实现参考lab3 抢占式调度

主要的难点在于在load program segment的时候,是load到user的environment,因此需要在load之前使用lcr3指令切换到当前environment的page dir.

以及在指定完program的entry point之后,需要将page dir切换回kern_pgdir

还有就是指定入口点的方法,是将该环境的指令寄存器eip的值设置为该elf格式文件的e_entry的值

 

接下来是函数env_create(),作用是申请一个新的environment,加载二进制文件,没有什么难度

之后是env_run,也没什么难度.

代码实现完了,但是发现直接crash了..显示triple fault

不要慌,问题不大.这是因为JOS目前还没有允许user space转化到kernel的机制,因此抛出了”general protection exception” . 然后JOS也处理不了抛出的”general protection exception”,因此再次抛出异常. 最后以抛出triple fault结束.

但是我们肯定需要一个办法来验证我们的实现是对的.

方式是使用gdb,在kern/env.c的env_pop_tf函数上设置断点.该函数是进入user mode之前运行的最好一个函数.然后单步发现运行的恰好是lib/entry.S中start label后的几个

然后查看obj/user/hello.asm 文件,找到sys_cputs()函数中的 int $0x30指令(在第1934行左右),该指令对应的user-space address是0x00800add

在该地址处设置断点,如果可以顺利执行到 0x800add: int $0x30   ,说明之前的实现没有问题.