Part A: User Environments and Exception Handling
As you can see in kern/env.c, the kernel maintains three main global variables pertaining to environments:
struct Env *envs = NULL; // All environments struct Env *curenv = NULL; // The current env static struct Env *env_free_list; // Free environment list
Once JOS gets up and running, the envs
pointer points to an array of Env
structures representing all the environments in the system. In our design, the JOS kernel will support a maximum of NENV
simultaneously active environments, although there will typically be far fewer running environments at any given time. (NENV
is a constant #define
‘d in inc/env.h.) Once it is allocated, the envs
array will contain a single instance of the Env
data structure for each of theNENV
possible environments.
ENV结构体定义:
struct Env { struct Trapframe env_tf; // Saved registers struct Env *env_link; // Next free Env envid_t env_id; // Unique environment identifier envid_t env_parent_id; // env_id of this env‘s parent enum EnvType env_type; // Indicates special system environments unsigned env_status; // Status of the environment uint32_t env_runs; // Number of times environment has run // Address space pde_t *env_pgdir; // Kernel virtual address of page dir };
Here‘s what the Env
fields are for:
- env_tf:
- This structure, defined in inc/trap.h, holds the saved register values for the environment while that environment is not running: i.e., when the kernel or a different environment is running. The kernel saves these when switching from user to kernel mode, so that the environment can later be resumed where it left off.
- env_link:
- This is a link to the next
Env
on theenv_free_list
.env_free_list
points to the first free environment on the list. - env_id:
- The kernel stores here a value that uniquely identifiers the environment currently using this
Env
structure (i.e., using this particular slot in theenvs
array). After a user environment terminates, the kernel may re-allocate the sameEnv
structure to a different environment - but the new environment will have a differentenv_id
from the old one even though the new environment is re-using the same slot in theenvs
array. - env_parent_id:
- The kernel stores here the
env_id
of the environment that created this environment. In this way the environments can form a “family tree,” which will be useful for making security decisions about which environments are allowed to do what to whom. - env_type:
- This is used to distinguish special environments. For most environments, it will be
ENV_TYPE_USER
. We‘ll introduce a few more types for special system service environments in later labs. - env_status:
- This variable holds one of the following values:
ENV_FREE
:- Indicates that the
Env
structure is inactive, and therefore on theenv_free_list
. ENV_RUNNABLE
:- Indicates that the
Env
structure represents an environment that is waiting to run on the processor. ENV_RUNNING
:- Indicates that the
Env
structure represents the currently running environment. ENV_NOT_RUNNABLE
:- Indicates that the
Env
structure represents a currently active environment, but it is not currently ready to run: for example, because it is waiting for an interprocess communication (IPC) from another environment. ENV_DYING
:- Indicates that the
Env
structure represents a zombie environment. A zombie environment will be freed the next time it traps to the kernel. We will not use this flag until Lab 4.
- env_pgdir:
- This variable holds the kernel virtual address of this environment‘s page directory.
1 Exercise 1. 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 envs should 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.
1) 为env申请内存
1 // Make ‘envs‘ point to an array of size ‘NENV‘ of ‘struct Env‘. 2 // LAB 3: Your code here. 3 envs = (struct Env*)boot_alloc(ROUNDUP(NENV * sizeof(struct Env), PGSIZE)); 4 memset(envs, 0, sizeof(struct Env) * NENV); 5 cprintf("alloc envs buffer: start=%08x, len=%08x\n", envs, NENV * sizeof(struct Env));
2) 将逻辑地址UENVS映射到envs的物理地址
1 ////////////////////////////////////////////////////////////////////// 2 // Map the ‘envs‘ array read-only by the user at linear address UENVS 3 // (ie. perm = PTE_U | PTE_P). 4 // Permissions: 5 // - the new image at UENVS -- kernel R, user R 6 // - envs itself -- kernel RW, user NONE 7 // LAB 3: Your code here. 8 boot_map_region(kern_pgdir, 9 UENVS, 10 ROUNDUP((sizeof(struct Env) * NENV) , PGSIZE), 11 PADDR(envs), 12 (PTE_U | PTE_P));
Exercise 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 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, r = -E_NO_MEM; panic("env_alloc: %e", r); will panic with the message "env_alloc: out of memory".
1) env_init
初始化env,最终env_free_list指向env[0], 然后通过env_link链接剩余env,链表。
1 // Mark all environments in ‘envs‘ as free, set their env_ids to 0, 2 // and insert them into the env_free_list. 3 // Make sure the environments are in the free list in the same order 4 // they are in the envs array (i.e., so that the first call to 5 // env_alloc() returns envs[0]). 6 // 7 void 8 env_init(void) 9 { 10 // Set up envs array 11 // LAB 3: Your code here. 12 13 int i = 0; 14 env_free_list = NULL; 15 cprintf("NENV -1 : %u\n", NENV -1); 16 17 for (i = NENV -1; i >= 0; i--) 18 { 19 envs[i].env_id = 0; 20 envs[i].env_parent_id = 0; 21 envs[i].env_type = ENV_TYPE_USER; 22 envs[i].env_status = ENV_FREE; 23 envs[i].env_runs = 0; 24 envs[i].env_pgdir = NULL; 25 envs[i].env_link = env_free_list; 26 env_free_list = &envs[i]; 27 } 28 29 cprintf("env_free_list : 0x%08x, & envs[i]: 0x%08x\n", env_free_list, &envs[i]); 30 31 // Per-CPU part of the initialization 32 env_init_percpu(); 33 }
2) env_setup_vm
env_setup_vm为进程创建进程地址空间,先通过page_alloc分配一页地址作为页目录pagedir,然后pagedir复制内核页目录表,
然后将页表项env_pgdir[PDX(UVPT)]映射到pagedir。
1 static int 2 env_setup_vm(struct Env *e) 3 { 4 int i; 5 struct PageInfo *p = NULL; 6 7 // Allocate a page for the page directory 8 if (!(p = page_alloc(ALLOC_ZERO))) 9 return -E_NO_MEM; 10 11 // Now, set e->env_pgdir and initialize the page directory. 12 // 13 // Hint: 14 // - The VA space of all envs is identical above UTOP 15 // (except at UVPT, which we‘ve set below). 16 // See inc/memlayout.h for permissions and layout. 17 // Can you use kern_pgdir as a template? Hint: Yes. 18 // (Make sure you got the permissions right in Lab 2.) 19 // - The initial VA below UTOP is empty. 20 // - You do not need to make any more calls to page_alloc. 21 // - Note: In general, pp_ref is not maintained for 22 // physical pages mapped only above UTOP, but env_pgdir 23 // is an exception -- you need to increment env_pgdir‘s 24 // pp_ref for env_free to work correctly. 25 // - The functions in kern/pmap.h are handy. 26 27 // LAB 3: Your code here. 28 (p->pp_ref)++; 29 pde_t* page_dir = page2kva(p); 30 memcpy(page_dir, kern_pgdir, PGSIZE); 31 e->env_pgdir = page_dir; 32 33 // UVPT maps the env‘s own page table read-only. 34 // Permissions: kernel R, user R 35 e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U; 36 37 return 0; 38 }
3) region_alloc()
region_alloc 为environment *e 开辟len byte大小的物理空间,并将va虚拟地址开始的len长度大小的空间和物理空间建立映射关系。
1 static void 2 region_alloc(struct Env *e, void *va, size_t len) 3 { 4 // LAB 3: Your code here. 5 // (But only if you need it for load_icode.) 6 // 7 // Hint: It is easier to use region_alloc if the caller can pass 8 // ‘va‘ and ‘len‘ values that are not page-aligned. 9 // You should round va down, and round (va + len) up. 10 // (Watch out for corner-cases!) 11 12 va = ROUNDDOWN(va, PGSIZE); 13 len = ROUNDUP(len, PGSIZE); 14 15 struct PageInfo *pp; 16 int ret = 0; 17 18 for(; len > 0; len -= PGSIZE, va += PGSIZE) 19 { 20 pp = page_alloc(0); 21 22 if(!pp) 23 { 24 panic("region_alloc failed\n"); 25 } 26 27 ret = page_insert(e->env_pgdir, pp, va, PTE_U | PTE_W | PTE_P); 28 29 if(ret) 30 { 31 panic("region_alloc failed\n"); 32 } 33 } 34 }
4) load_icode
load_icode将elf格式的二进制文件load到env中。
1 static void 2 load_icode(struct Env *e, uint8_t * binary) 3 { 4 // Hints: 5 // Load each program segment into virtual memory 6 // at the address specified in the ELF section header. 7 // You should only load segments with ph->p_type == ELF_PROG_LOAD. 8 // Each segment‘s virtual address can be found in ph->p_va 9 // and its size in memory can be found in ph->p_memsz. 10 // The ph->p_filesz bytes from the ELF binary, starting at 11 // ‘binary + ph->p_offset‘, should be copied to virtual address 12 // ph->p_va. Any remaining memory bytes should be cleared to zero. 13 // (The ELF header should have ph->p_filesz <= ph->p_memsz.) 14 // Use functions from the previous lab to allocate and map pages. 15 // 16 // All page protection bits should be user read/write for now. 17 // ELF segments are not necessarily page-aligned, but you can 18 // assume for this function that no two segments will touch 19 // the same virtual page. 20 // 21 // You may find a function like region_alloc useful. 22 // 23 // Loading the segments is much simpler if you can move data 24 // directly into the virtual addresses stored in the ELF binary. 25 // So which page directory should be in force during 26 // this function? 27 // 28 // You must also do something with the program‘s entry point, 29 // to make sure that the environment starts executing there. 30 // What? (See env_run() and env_pop_tf() below.) 31 32 // LAB 3: Your code here. 33 34 struct Elf* elfhdr = (struct Elf *)binary; 35 struct Proghdr *ph, *eph; 36 37 if(elfhdr->e_magic != ELF_MAGIC) 38 { 39 panic("elf header‘s magic is not correct\n"); 40 } 41 42 ph = (struct Proghdr *)((uint8_t *)elfhdr + elfhdr->e_phoff); 43 44 eph = ph + elfhdr->e_phnum; 45 46 lcr3(PADDR(e->env_pgdir)); 47 48 for(;ph < eph; ph++) 49 { 50 if(ph->p_type != ELF_PROG_LOAD) 51 { 52 continue; 53 } 54 55 if(ph->p_filesz > ph->p_memsz) 56 { 57 panic("file size is great than memory size\n"); 58 } 59 60 region_alloc(e, (void *)ph->p_va, ph->p_memsz); 61 memmove((void *)ph->p_va, binary + ph->p_offset, ph->p_filesz); 62 63 memset((void *)ph->p_va + ph->p_filesz, 0, (ph->p_memsz - ph->p_filesz)); 64 } 65 66 // Now map one page for the program‘s initial stack 67 // at virtual address USTACKTOP - PGSIZE. 68 69 // LAB 3: Your code here. 70 71 lcr3(PADDR(kern_pgdir)); 72 73 e->env_tf.tf_eip = elfhdr->e_entry; 74 75 region_alloc(e, (void *)(USTACKTOP - PGSIZE), PGSIZE); 76 }
5) env_create
env_create 申请新的env并将elf文件load到env中.
1 void 2 env_create(uint8_t *binary, enum EnvType type) 3 { 4 // LAB 3: Your code here. 5 int ret = 0; 6 struct Env *e = NULL; 7 ret = env_alloc(&e, 0); 8 9 if(ret < 0) 10 { 11 panic("env_create: %e\n", ret); 12 } 13 14 load_icode(e, binary); 15 e->env_type = type; 16 }
6) env_run
env_run实现进程切换,先将当前进程状态置为RUNNABLE,然后通过lcr3切换进程空间,最后使用env_pop_tf切换到新的进程。
1 void 2 env_run(struct Env *e) 3 { 4 // Step 1: If this is a context switch (a new environment is running): 5 // 1. Set the current environment (if any) back to 6 // ENV_RUNNABLE if it is ENV_RUNNING (think about 7 // what other states it can be in), 8 // 2. Set ‘curenv‘ to the new environment, 9 // 3. Set its status to ENV_RUNNING, 10 // 4. Update its ‘env_runs‘ counter, 11 // 5. Use lcr3() to switch to its address space. 12 // Step 2: Use env_pop_tf() to restore the environment‘s 13 // registers and drop into user mode in the 14 // environment. 15 16 // Hint: This function loads the new environment‘s state from 17 // e->env_tf. Go back through the code you wrote above 18 // and make sure you have set the relevant parts of 19 // e->env_tf to sensible values. 20 21 // LAB 3: Your code here. 22 23 //panic("env_run not yet implemented"); 24 25 if(curenv && curenv->env_status == ENV_RUNNING) 26 { 27 curenv->env_status = ENV_RUNNABLE; 28 } 29 30 curenv = e; 31 e->env_status = ENV_RUNNING; 32 e->env_runs++; 33 34 lcr3(PADDR(e->env_pgdir)); 35 36 env_pop_tf(&(e->env_tf)); 37 }