首页 > 其他分享 >mit6.828 - lab3练习笔记

mit6.828 - lab3练习笔记

时间:2023-11-05 16:14:01浏览次数:46  
标签:lab3 envs pgdir 笔记 mit6.828 user env kern page

Part A

Exercise 1

练习 1. 修改 `kern/pmap.c` 中的 `mem_init()` ,分配并映射 `envs` 数组。该数组由 `Env` 结构的 `NENV` 实例组成,分配方式与分配页面数组类似。与页面数组一样,支持 `envs` 的内存也应在 `UENVS`(定义于 `inc/mlayout.h` )处映射为用户只读,这样用户进程才能读取该数组。

你应该运行代码并确保 `check_kern_pgdir()` 成功。
 [[lab3 - 翻译#^988084]]

解答

题目要求修改 mem_init

  1. 初始化 envs
  2. 映射 envs

初始化:pmap.c : 163行
image.png

映射 : pmap.c : 196行,
image.png

运行 make qemu 进行检查:
image.png

Excercise 2

env_init

// 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.
	for(int i = NENV-1;i>=0;--i){
		envs[i].env_status = ENV_FREE;
		envs[i].env_id = 0;
		envs[i].env_link = env_free_list;
		env_free_list = &envs[i];
	}
	// Per-CPU part of the initialization
	env_init_percpu();
}

env_setup_vm

// 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 directory
	if (!(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.
	e->env_pgdir = page2kva(p);
	memcpy(e->env_pgdir, kern_pgdir, PGSIZE);
	// UVPT maps the env's own page table read-only.
	p->pp_ref ++;
	// Permissions: kernel R, user R
	e->env_pgdir[PDX(UVPT)] = PADDR(e->env_pgdir) | PTE_P | PTE_U;

	return 0;
}

不是很明白,为什么说,这个时候的pageinfo需要维护pp_ref?

区分好pgdir_walk和page_insert的功能区别:
pgdir_walk:访问页表,更为基础,只是为了修改页表
page_insert:将一个内存页映射到指定va,中间用到前者,因为要修改页表

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 *newenv_store;
	envid_t parent_id = 0;

	int r = env_alloc(&newenv_store, parent_id);

	if (r <= 0)
	{
		panic("env_create error: %e ", r);
	}
	// 加载二进制文件
	load_icode(newenv_store, binary);
	// 设置环境类型
	newenv_store->env_type = type;
}

load_icode

注释翻译:
为用户进程设置初始程序二进制文件、堆栈和处理器标志。
只有在运行第一个用户模式环境之前的内核初始化过程中才会调用该函数。
该函数将 ELF 二进制映像中的所有可加载段加载到环境的用户内存中,从 ELF 程序头中指示的相应虚拟地址开始。
与此同时,它还会将这些段中在程序头中标记为映射但实际上并不存在于 ELF 文件中的部分(即程序的 bss 部分)清零。

除了 Boot Loader 还需要从磁盘读取代码外,所有这些都与我们的 Boot Loader 非常相似。
看看 boot/main.c 就会明白。
最后,该函数为程序的初始堆栈映射了一个页面。
load_icode 在遇到问题时会惊慌失措。

  • load_icode 如何会失败? 给定的输入可能有什么问题?

提示 按照 ELF 程序段头指定的地址将每个程序段加载到虚拟内存中。

  • 只能加载 ph->p_type == ELF_PROG_LOAD 的程序段。
  • 每个程序段的虚拟地址可在 ph->p_va 中找到,其在内存中的大小可在 ph->p_memsz 中找到。
  • 应将 ELF 二进制文件中从 "binary + ph->p_offset "开始的 ph->p_filesz 字节复制到虚拟地址 ph->p_va。
  • 剩余的内存字节应清零。(ELF 头应该是 ph->p_filesz <= ph->p_memsz。)

使用上一个实验室的函数分配和映射页面。//
目前所有页面保护位都应为用户读/写。
ELF 程序段不一定是页面对齐的,但在本函数中可以假定没有两个程序段会接触同一个虚拟页面。//
你可能会发现 region_alloc 这样的函数很有用。//
如果能直接将数据移入 ELF 二进制文件中存储的虚拟地址,加载段就会简单得多。
那么,在执行该函数时,哪个页面目录应该有效呢?//

  • 你还必须对程序的入口点做一些处理,以确保环境在那里开始执行。
    请参阅下面的 env_run() 和 env_pop_tf())。

env_create()
-->env_alloc():分配env结构
-->env_setup_vm():创建映射,将栈映射至USTACKTOP
-->load_icode():加载程序,申请栈的空间
-->region_alloc()

运行:

image.png

从lab/kern/init.c来看,
image.png

在执行i386_init时,最会会通过env_run进入用户态,运行lab/user/hello.c中的umain
image.png
但是确发生了三重错误。

我们用 GDB 在 env_pop_tf() 函数设置断点,然后通过指令 si,单步调试,观察 iret 指令前后寄存器的变化。

image.png

image.png

一个疑问

int $0x30究竟是谁调用的?
hello.c中没有这个代码啊?

Exercise 3

练习 3. 如果还没有,请阅读《80386 程序员手册》第 9 章 "异常和中断"[Chapter 9, Exceptions and Interrupts](https://pdos.csail.mit.edu/6.828/2018/readings/i386/c09.htm)(或《IA-32 开发人员手册》第 5 章 [IA-32 Developer's Manual](https://pdos.csail.mit.edu/6.828/2018/readings/ia32/IA32-3A.pdf))。

观察下TSS

image.png

Exercise 6

image.png


Exercise 7

疑问的答案

来看看hello.c的代码
image.png

hello.c调用了lib/print.c/cprintf,
- lib/print.c/cprintf调用了 lib/syscall.c/sys_cputs()
- lib/syscall.c/sys_cputs()调用了lib/syscall.c/syscall()
- lib/syscall.c/syscall()

(注意这时候我们已经进入了内核模式 CPL=0),在该函数中根据系统调用号调用 kern/print.c 中的 cprintf() 函数,该函数最终调用 kern/console.c 中的 cputchar() 将字符串打印到控制台。当 trap_dispatch() 返回后,trap() 会调用env_run(curenv);

该函数前面讲过,会将 curenv->env_tf 结构中保存的寄存器快照重新恢复到寄存器中,这样又会回到用户程序系统调用之后的那条指令运行,只是这时候已经执行了系统调用并且寄存器 eax 中保存着系统调用的返回值。任务完成重新回到用户模式 CPL=3。


Exercise 8

我们的任务是补充libmain函数,填充this指针。
image.png

by the way:了解下libmain和用户程序的关系
image.png

再次仔细地观察下用户地函数,hello.c中的主函数,是"main"吗?
image.png

是umain,他还引入了头文件inc/lib.h,里面有umain的声明。
然后,操作系统这边的lib/libmain.c中,也引入了这个头文件,这样一来,操作系统的libmain函数就可以调用用户的main函数了。

image.png

Exercise 9 页错误

任务内容:

1. 修改`kern/trap.c`,当页错误发生在内核态时panic。

~~~ad-note
检查`tf_cs`的低位字节可以判断fault发生在**用户态**还是**内核态**
~~~

2. 读`kern/pmap.c` 中的 `user_mem_assert` 并实现 `user_mem_check`

3. 修改 `kern/syscall.c` 对系统调用的参数进行正确性检查

4. 启动你的kernel,运行 `user/buggyhello`。 environment应当被销毁, kernel 不应当 'panic'。你应该会看到:

~~~txt
[00001000] user_mem_check assertion failure for va 00000001
[00001000] free env 00001000
Destroyed the only environment - nothing more to do!
~~~

5. 最终,修改 `kern/kdebug.c` 中的 `debuginfo_eip`,在 `usd, stabs, stabstr` 上调用 'user_mem_check'。如果你现在运行 `user/breakpoint` ,你应能够从 kernel monitor 运行 `backtrace`,并看见backtrace 在kernel panics 之前,随着一个page fault回溯到 `lib/libmain.c` 。
	是什么导致了page fualt?
	你不需要修复他,但是你要明白它为何发生。



页错误处理函数

补充页错误处理函数
image.png


实现user_mem_check

先看看 user_mem_assert 是怎么用 user_mem_check 的:
image.png
然后实现 user_mem_check

  1. 检查 va 开始之后大小为 len 的内存空间范围,确认其权限是否为 perm
  2. 除此之外的限制:
    1. 低于 ULIM
    2. 该页具备权限
  3. 如果发生错误,则将 user_mem_check_addr 设置为第一个有问题的页
  4. 如果没有问题,则返回 0, 否则返回 -E_FAULT

image.png


修改 syscall.c

对 syscall 的输入做检查:
目前 syscall 只有四种调用:
image.png

只有 sys_cputs 中有指针,有访问内存的需求。因此只需要检查 sys_cputs.

最后 make grade
image.png

运行breakpoint

image.png

发生了什么?
首先看看 user/breakpoint.c 的代码:
内容就是直接int3中断
image.png
那么,cpu 会将控制流跳转到中断向量表,经过 kern/trapentry.S 中的 trap_dispatch 到达 kern/monitor.c/monitor,最后调用 print_trapframetrapframe 的内容打印了除了

image.png

标签:lab3,envs,pgdir,笔记,mit6.828,user,env,kern,page
From: https://www.cnblogs.com/toso/p/17810604.html

相关文章

  • 《信息安全系统设计与实现》第九周学习笔记
    硬件定时器定时器是由时钟源和可编程计数器组成的硬件设备。时钟源通常是一个晶体振荡器,会产生周期性电信号,以精确的频率驱动计数器。使用一个倒计时值对计数器进行编程,每个时钟信号减1。当计数减为0时,计数器向CPU生成一个定时器中断,将计数值重新加载到计数器中,并重复倒计时。......
  • 信息安全系统设计与实现学习笔记8
    学习笔记8-重点总结1.定时器及时钟服务1.1硬件定时器由时钟源和可编程计数器组成的硬件设备。时钟源通常是晶体振荡器,驱动计数器以精确的频率。计数器周期称为定时器刻度,是系统的基本计时单元。1.2个人计算机定时器实时时钟(RTC)提供时间和日期信息,即使在关机时也能......
  • yzy第八周学习笔记
    定时器及时钟服务硬件定时器定时器是由时钟源和可编程计数器组成的硬件设备。时钟源通常是一个晶体振荡器,会产生周期性电信号,以精确的频率驱动计数器。使用一个倒计时值对计数器进行编程,每个时钟信号减1.当计数减为0时,计数器向CPU生成一个定时器中断,将计数值重新加载到计数器中,......
  • 大总结:uboot复习--Apple的学习笔记
    一,前言发现现在的uboot做的越来像linux驱动了,包括了设备树及其驱动模型。所以若复习设备树的话,在linux上学习和在uboot上学习是一样的,再加上我学习过了qemu仿真,所以想找到单步仿真调试方法。主要是am335x的调试器当时我焊接失败,所以只考虑仿真,另外发现stm32F407也有uboot支持,所以研......
  • 开发板nfs挂载桥接虚拟机的文件系统环境搭建--Apple的学习笔记
    一,前言我之前虚拟机配置的是NAT方式,不是桥接,然后Kernel及uboot都同nfs挂载。所以先改成了最简单的桥接方式的虚拟机。二,ubuntu虚拟机设置1,vmware先设置为桥接。2,设置ubuntu14.04的静态ip地址gedit/etc/network/interfaces内容autoeth0ifaceeth0inetstaticaddress192.168.7.......
  • 开发板nfs挂载NAT虚拟机的文件系统环境搭建--Apple的学习笔记
    一,前言总体来说我还是想用NAT虚拟机,所以基于开发板nfs挂载桥接虚拟机的文件系统环境搭建--Apple的学习笔记中的配置继续修改。二,ubuntu虚拟机中nfs挂载设置修改ip地址为192.168.112.11添加路由端口sudogedit/etc/services最后添加mountd9999/tcpmountd9999/udpPC以太网2设......
  • 20211314王艺达学习笔记8
    Unix/Linux系统编程第五章定时器及时钟服务5.1硬件定时器定时器由时钟源和可编程计数器组成。时钟源会产生周期性电信号。计数器减为0时,计数器向CPU生成一个定时器中断,计数器周期称为定时器刻度,是系统的基本计时单元。5.2个人计时定时器实时时钟(RTC)即使在个人计算机关机......
  • 第五章学习笔记
    第五章定时器及时钟服务定时器(Timer):1.定时器是计算机系统中的硬件或软件组件,用于测量和管理时间间隔。2.定时器可用于执行定时任务、调度事件和测量程序的性能。3.定时器可以是硬件定时器,如CPU时钟,或是软件定时器,由操作系统或应用程序创建和管理。4.常见的定时器单位包括毫......
  • Git入门笔记--版本控制系统的使用
    首先记录下使用命令行工具git与github交互的“Hello,World!”。"Hello,World!"是任何程序设计语言入门第一课,不管原理,先跑起来再说。git的"Hello,World!"就是如何从github获取仓库到本地,并将修改上传github。1.将远程仓库clone到本地:$gitclone<仓库地址>这条git命令行......
  • 学习笔记8
    20211301学习笔记8教材知识点总结5.1硬件定时器定时器:时钟源和可编程计数器组成的硬件设备时钟源:晶体振荡器,产生周期性电信号定时器中断:计数减到05.2个人计算机定时器实时时钟:RTC,小型备用电池供电,PC关机也能持续运行,提供时间和日期可编程间隔定时器:PIT,与CPU......