补充一个知识点:MMU (一)
从 lab2 切换到 lab3 的方法

Lab 3 是为了设置用户在运行的时候适应的上下文环境
Allocating the Environments Array

Like a Unix process, a JOS environment couples the concepts of “thread” and “address space”.

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.
这个直接照葫芦画瓢就行



Creating and Running Environments
由于运行环境没有搭建好,因此内核需要设置成能够加载内核中的静态二进制文件。

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 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,

r = -E_NO_MEM;
panic("env_alloc: %e", r);

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

1.env_init()
初始化所有结构体。将 envs_id 字段设置为0,env 结构体的顺序是从头到尾,使用头插法就好了。

// Mark all environments in 'envs' as free, set their env_ids to 0,
// and insert them into the env_free_list.
// Make sure the environments are in the free list in the same order
// they are in the envs array (i.e., so that the first call to
// env_alloc() returns envs[0]).
//
void
env_init(void)
{// Set up envs array// LAB 3: Your code here.env_free_list = envs;envs->env_id = 0;for (int i = 1; i < NENV; i++) {(envs + (i - 1))->env_link = (envs + i);(envs + i)->env_id = 0;}// Per-CPU part of the initializationenv_init_percpu();
}

2.env_setup_vm()
说实话我一直不清楚静态函数是干什么用的。是用来访问静态的成员变量吗?
内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,

话说回来,这个函数的作用是
每个进程都需要有自己的进程地址空间,地址空间分为用户空间和内核空间两部分。用户空间不需要初始化,地址空间需要进程初始化。

//
// Initialize the kernel virtual memory layout for environment e.
// Allocate a page directory, set e->env_pgdir accordingly,
// and initialize the kernel portion of the new environment's address space.
// Do NOT (yet) map anything into the user portion
// of the environment's virtual address space.
//
// Returns 0 on success, < 0 on error.  Errors include:
//  -E_NO_MEM if page directory or table could not be allocated.
//
static int
env_setup_vm(struct Env *e)
{int i;struct PageInfo *p = NULL;// Allocate a page for the page directoryif (!(p = page_alloc(ALLOC_ZERO)))return -E_NO_MEM;// Now, set e->env_pgdir and initialize the page directory.//// Hint://    - The VA space of all envs is identical above UTOP//   (except at UVPT, which we've set below).// See inc/memlayout.h for permissions and layout.//   Can you use kern_pgdir as a template?  Hint: Yes.// (Make sure you got the permissions right in Lab 2.)//    - The initial VA below UTOP is empty.//    - You do not need to make any more calls to page_alloc.//    - Note: In general, pp_ref is not maintained for// physical pages mapped only above UTOP, but env_pgdir//  is an exception -- you need to increment env_pgdir's// pp_ref for env_free to work correctly.//    - The functions in kern/pmap.h are handy.// LAB 3: Your code here.p->pp_ref++;e->env_pgdir = (pde_t *) page2kva(p);memcpy(e->env_pgdir, kern_pgdir, PGSIZE);// UVPT maps the env's own page table read-only.// Permissions: kernel R, user Re->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;return 0;
}

按照上面的理解,觉得下面这个代码更加靠谱。

static int
env_setup_vm(struct Env *e)
{int i;struct PageInfo *p = NULL;// Allocate a page for the page directoryif (!(p = page_alloc(ALLOC_ZERO)))return -E_NO_MEM;// LAB 3: Your code here.e->env_pgdir = (pde_t *)page2kva(p);p->pp_ref++;//Map the directory below UTOP.for(i = 0; i < PDX(UTOP); i++) {e->env_pgdir[i] = 0;}//Map the directory above UTOPfor(i = PDX(UTOP); i < NPDENTRIES; i++) {e->env_pgdir[i] = kern_pgdir[i];}// UVPT maps the env's own page table read-only.// Permissions: kernel R, user Re->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;return 0;
}

3、region_alloc()
为进程建立地址映射

//
// Allocate len bytes of physical memory for environment env,
// and map it at virtual address va in the environment's address space.
// Does not zero or otherwise initialize the mapped pages in any way.
// Pages should be writable by user and kernel.
// Panic if any allocation attempt fails.
//
static void
region_alloc(struct Env *e, void *va, size_t len)
{// LAB 3: Your code here.// (But only if you need it for load_icode.)void *start = ROUNDDOWN(va, PGSIZE);void *end = ROUNDUP(va + len, PGSIZE);struct PageInfo * pp;while (start < end) {pp = page_alloc(0);if (!pp) {cprintf("region_alloc: out of free memory.\n");return;}page_insert(e->env_pgdir, pp, start, PTE_U | PTE_W);start += PGSIZE;}// Hint: It is easier to use region_alloc if the caller can pass//   'va' and 'len' values that are not page-aligned.//   You should round va down, and round (va + len) up.//   (Watch out for corner-cases!)
}

4、load_icode()功能是为每一个用户进程设置它的初始代码区,堆栈以及处理器标识位。每个用户程序都是ELF文件,所以我们要解析该ELF文件。

//
// Set up the initial program binary, stack, and processor flags
// for a user process.
// This function is ONLY called during kernel initialization,
// before running the first user-mode environment.
//
// This function loads all loadable segments from the ELF binary image
// into the environment's user memory, starting at the appropriate
// virtual addresses indicated in the ELF program header.
// At the same time it clears to zero any portions of these segments
// that are marked in the program header as being mapped
// but not actually present in the ELF file - i.e., the program's bss section.
//
// All this is very similar to what our boot loader does, except the boot
// loader also needs to read the code from disk.  Take a look at
// boot/main.c to get ideas.
//
// Finally, this function maps one page for the program's initial stack.
//
// load_icode panics if it encounters problems.
//  - How might load_icode fail?  What might be wrong with the given input?
//
static void
load_icode(struct Env *e, uint8_t *binary)
{// Hints://  Load each program segment into virtual memory//  at the address specified in the ELF segment header.//  You should only load segments with ph->p_type == ELF_PROG_LOAD.//  Each segment's virtual address can be found in ph->p_va//  and its size in memory can be found in ph->p_memsz.//  The ph->p_filesz bytes from the ELF binary, starting at//  'binary + ph->p_offset', should be copied to virtual address//  ph->p_va.  Any remaining memory bytes should be cleared to zero.//  (The ELF header should have ph->p_filesz <= ph->p_memsz.)//  Use functions from the previous lab to allocate and map pages.////  All page protection bits should be user read/write for now.//  ELF segments are not necessarily page-aligned, but you can//  assume for this function that no two segments will touch//  the same virtual page.////  You may find a function like region_alloc useful.////  Loading the segments is much simpler if you can move data//  directly into the virtual addresses stored in the ELF binary.//  So which page directory should be in force during//  this function?////  You must also do something with the program's entry point,//  to make sure that the environment starts executing there.//  What?  (See env_run() and env_pop_tf() below.)// LAB 3: Your code here.struct Elf *Elf = (struct Elf *) binary;struct Proghdr *ph;             //Program Headerint ph_num;                     //Program entry numberif (Elf->e_magic != ELF_MAGIC) {panic("binary is not ELF format\n");}ph = (struct Proghdr *) (binary+ Elf->e_phoff);//e_phoff, (32位4字节,64位8字节),program header table的offset,如果文件没有PH,这个值是0。ph_num = Elf->e_phnum;
//e_phnum, 2字节。如果文件没有program header table, e_phnum的值为0。e_phentsize乘以e_phnum就得到了整个program header table的大小。lcr3(PADDR(e->env_pgdir)); //转换cr3寄存器里面的内容。cr3寄存器里面放置的是当前任务的页表位置的。这个就是将寄存器的内容更换成为进程自个儿的页表。for (int i = 0; i < ph_num; i++) {if (ph[i].p_type == ELF_PROG_LOAD) { //需要加载每一个被标记为ELF_PROG_LOAD的program header对应的segment//这个有英语提示region_alloc(e, (void *)ph[i].p_va, ph[i].p_memsz);memset((void *)ph[i].p_va, 0, ph[i].p_memsz); // 鍒濆鍖?memcpy((void *)ph[i].p_va, binary + ph[i].p_offset, ph[i].p_filesz); }}lcr3(PADDR(kern_pgdir));//恢复cr3寄存器里面的内容,让页表重新指向内核页目录的页表e->env_tf.tf_eip = Elf->e_entry;
//e_entry,(32位4字节,64位8字节),执行入口点,如果文件没有入口点,这个域保持0。设置environment执行入口是在e->env_tf.tf_eip中// Now map one page for the program's initial stack// at virtual address USTACKTOP - PGSIZE.// LAB 3: Your code here.region_alloc(e, (void *) (USTACKTOP - PGSIZE), PGSIZE);
}

5.env_create()

//
// Allocates a new env with env_alloc, loads the named elf
// binary into it with load_icode, and sets its env_type.
// This function is ONLY called during kernel initialization,
// before running the first user-mode environment.
// The new env's parent ID is set to 0.
//
void
env_create(uint8_t *binary, enum EnvType type)
{// LAB 3: Your code here.struct Env *env;if (env_alloc(&env, 0) != 0) {cprintf("env_create: error create new env.\n");return;}env->env_type = type;load_icode(env, binary);// If this is the file server (type == ENV_TYPE_FS) give it I/O privileges.// LAB 5: Your code here.if (type == ENV_TYPE_FS) {env->env_tf.tf_eflags |= FL_IOPL_MASK;}
}

6.env_run()

//
// Context switch from curenv to env e.
// Note: if this is the first call to env_run, curenv is NULL.
//
// This function does not return.
//
void
env_run(struct Env *e)
{// Step 1: If this is a context switch (a new environment is running)://      1. Set the current environment (if any) back to//          ENV_RUNNABLE if it is ENV_RUNNING (think about//        what other states it can be in),//       2. Set 'curenv' to the new environment,//     3. Set its status to ENV_RUNNING,//     4. Update its 'env_runs' counter,//       5. Use lcr3() to switch to its address space.// Step 2: Use env_pop_tf() to restore the environment's//    registers and drop into user mode in the//      environment.// Hint: This function loads the new environment's state from// e->env_tf.  Go back through the code you wrote above//   and make sure you have set the relevant parts of//  e->env_tf to sensible values.// LAB 3: Your code here.if (curenv && curenv->env_status == ENV_RUNNING) {curenv->env_status = ENV_RUNNABLE;}curenv = e;e->env_status = ENV_RUNNING;e->env_runs++;lcr3(PADDR(e->env_pgdir));unlock_kernel();env_pop_tf(&e->env_tf);
}

全部完成后发现其不断的重写,刷新,为什么会这样?

We’ll address this problem shortly, but for now we can use the debugger to check that we’re entering user mode. Use make qemu-gdb and set a GDB breakpoint at env_pop_tf, which should be the last function you hit before actually entering user mode. Single step through this function using si; the processor should enter user mode after the iret instruction. You should then see the first instruction in the user environment’s executable, which is the cmpl instruction at the label start in lib/entry.S. Now use b *0x… to set a breakpoint at the int $0x30 in sys_cputs() in hello (see obj/user/hello.asm for the user-space address). This int is the system call to display a character to the console. If you cannot execute as far as the int, then something is wrong with your address space setup or program loading code; go back and fix it before continuing.

break + 函数名称,设置对应函数为断点



查看 lib /entry.S的源码可知,两条蓝色线段之间执行的便是这个文件。
现在对开机启动后的各种函数调用关系十分迷糊。
昨天的函数调用后不断的开机重启的原因也不清楚。

2022-3-17 MIT 6.828 Lab 3: User Environments | Part A: User Environments and Exception Handling相关推荐

  1. 《MIT 6.828 Lab 1 Exercise 10》实验报告

    本实验的网站链接:MIT 6.828 Lab 1 Exercise 10. 题目 Exercise 10. To become familiar with the C calling conventi ...

  2. 2022-3-16 MIT 6.828 Lab 2: Memory Management | Part 3: Kernel Address Space | Exercise 5

    Exercise 5. Fill in the missing code in mem_init() after the call to check_page(). Your code should ...

  3. 2022-2-27 MIT 6.828 Lab 1: Booting a PC | Part 3: The Kernel | The Stack |exercise 9 - 11

    Exercise 9. Determine where the kernel initializes its stack, and exactly where in memory its stack ...

  4. MIT 6.828 lab1 part2

    Part 2: The Boot Loader 加载内核 为了理解boot/main.c,你需要知道ELF二进制文件是什么.当你编译和链接一个C程序(如JOS内核)时,编译器将每个C源文件('. C ...

  5. MIT 6.828 操作系统工程 lab4A:多处理器支持和协作多任务

    MIT 6.828 操作系统工程 lab4A:多处理器支持和协作多任务 这篇是我自己探索实现 MIT 6.828 lab 的笔记记录,会包含一部分代码注释和要求的翻译记录,以及踩过的坑/个人的解决方案 ...

  6. MIT 6.828 JOS学习笔记17. Lab 3.1 Part A User Environments

    Introduction 在这个实验中,我们将实现操作系统的一些基本功能,来实现用户环境下的进程的正常运行.你将会加强JOS内核的功能,为它增添一些重要的数据结构,用来记录用户进程环境的一些信息:创建 ...

  7. MIT 6.828 (三) Lab 3: User Environments

    (最近有点事,做的比较慢.哦,不,抄的比较慢...) Lab 3: User Environments Introduction 在这个实验中,我们将实现操作系统的一些基本功能,来实现用户环境下的进程 ...

  8. MIT 6.828 学习笔记2 阅读main.c

    #include <inc/x86.h> #include <inc/elf.h>/********************************************** ...

  9. mit 6.828 lab1_exercise12_讲解

    好难啊,去年做的时候就很难,难到直接不知道怎么出来的,现在能搞出来了...... Exercise 12. Modify your stack backtrace function to displa ...

最新文章

  1. 《术以载道——软件过程改进实践指南》—第1章1.1节对CMMI的基本认识
  2. Kubernetes客户端client-go简介
  3. C语言基本运算符和表达式
  4. ElasticSearch-安装以及常见错误(自己测试过yum install -y log4j* )
  5. ByteBuffer
  6. url 特殊字符 传递参数解决方法
  7. char double java_java从入门到精髓 - Number char double float
  8. VC通过函数索引调用DLL范例
  9. 使用requests post请求爬取申万一级行业指数行情
  10. automatic preferred max layout width
  11. 怎样从零开始训练一个AI车手?
  12. 读卡器插电脑不显示盘符
  13. 骗赞小程序(仅供恶搞)
  14. 预防WinRAR文件损坏
  15. Android源码编译遇到Java虚拟机内存不够等相关Jack问题,解决方法
  16. CF 71A [字符串统计]
  17. 如何让ecshop做淘宝客
  18. 2022-04-30前端周报 巴厘岛项目交接.md
  19. 德州扑克分池算法 思路及lua实现
  20. 如何应付全英文的技术面试(一)

热门文章

  1. mysqld 命令相关介绍
  2. iphone11信号强度测试软件,至少比上代信号好:iPhone 11 系列天线实验室测试结果出炉...
  3. PC使用网线上网的条件下,通过PC的Wifi共享提供手机上网教程
  4. 勿扰模式代码结构简析
  5. SQLZOO——JOIN Quiz 2
  6. Profile_Day05:企业级360全方位用户画像
  7. JAVA 使用 com.aspose.words将word转换PDF等
  8. android 获取wifi型号,android 获取连接WiFi的名称
  9. Android面试题汇总(中高级)及答案解析,2023年企业面试题精选
  10. transferto方法的应用_SpringMVC的 transferTo使用