ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

北航操作系统课程lab4实验报告

2022-05-24 17:00:42  阅读:293  来源: 互联网

标签:调用 函数 北航 用户 内核 lab4 进程 实验报告 页表


OS lab4实验报告

实验思考题

Thinking 4.1

思考并回答下面的问题:

  • 内核在保存现场的时候是如何避免破坏通用寄存器的?

  • 系统陷入内核调用后可以直接从当时的$a0-$a3参数寄存器中得到用户调用msyscall留下的信息吗?

  • 我们是怎么做到让sys开头的函数“认为”我们提供了和用户调用msyscall时同样的参数的?

  • 内核处理系统调用的过程对Trapframe做了哪些更改?这种修改对应的用户态的变化是?

 

  • 调用syscall.S中的SAVE_ALL宏来保存通用寄存器和部分CP0寄存器,将其存入以sp为基地址的KERNEL_SP区域,但这个操作并没有将sp也存进去。

  • 能,在陷入内核时,一般不会有操作改变这四个寄存器,但一般情况下,还是从栈中取得这些参数更加保险。

  • 在调用syscall前都将前四个参数按顺序放入$a0-$a3寄存器,后两个参数按顺序存入内核栈中的相同位置(相对sp偏移相同),内核处理这些参数时也按照这个顺序读取。

  • 将栈中存储的EPC寄存器值增加4,这是因为系统调用后,将会返回下一条指令,而用户程序会保证系统调用操作不在延迟槽内,所以直接加4得到下一条指令的地址。

 

Thinking 4.2

思考下面的问题,并对这个问题谈谈你的理解: 请回顾 lib/env.c 文件中 mkenvid() 函数的实现,该函数不会返回 0,请结合系统调用和 IPC 部分的实现 与 envid2env() 函数的行为进行解释。

 

我们可以看到该函数为:

 u_int mkenvid(struct Env *e) {
     u_int idx = e - envs;
     u_int asid = asid_alloc();
     return (asid << (1 + LOG2NENV)) | (1 << LOG2NENV) | idx;
 }

显然,无论如何第11位的值都为1,函数不会返回0,并且由此可得,根进程的进程号为0x400,这也与实验结果相符合。

 

Thinking 4.3

思考下面的问题,并对这两个问题谈谈你的理解:

  • 子进程完全按照 fork() 之后父进程的代码执行,说明了什么?

  • 但是子进程却没有执行 fork() 之前父进程的代码,又说明了什么?

 

  • 子进程与父进程共享代码段空间

  • 子进程创建时,PC值设置到了fork的后一个指令,因此不会执行之前的代码,但由于空间的共享,之前的变量都可以使用

     

Thinking 4.4

关于 fork 函数的两个返回值,下面说法正确的是:

A、fork 在父进程中被调用两次,产生两个返回值

B、fork 在两个进程中分别被调用一次,产生两个不同的返回值

C、fork 只在父进程中被调用了一次,在两个进程中各产生一个返回值

D、fork 只在子进程中被调用了一次,在两个进程中各产生一个返回值

 

说法正确的是C

 

Thinking 4.5

我们并不应该对所有的用户空间页都使用duppage进行映射。那么究竟哪些用户空间页应该映射,哪些不应该呢? 请结合本章的后续描述、mm/pmap.c 中 mips_vm_init 函数进行的页面映射以及 include/mmu.h 里的内存布局图进行思考。

 

在0 ~ USTACKTOP范围的内存需要进行映射,其上范围的内存要么属于内核,要么是所有用户进程共享的空间,用户模式下只可以读取。可写但不共享的页面都需要设置PTE_COW进行保护。

 

Thinking 4.6

在遍历地址空间存取页表项时你需要使用到vpd和vpt这两个“指针的指针”,请参考 user/entry.S 和 include/mmu.h 中的相关实现,思考并回答这几个问题:

  • vpt和vpd的作用是什么?怎样使用它们?

  • 从实现的角度谈一下为什么进程能够通过这种方式来存取自身的页表?

  • 它们是如何体现自映射设计的?

  • 进程能够通过这种方式来修改自己的页表项吗?

 

  • vpd是页目录首地址,以vpd为基地址,加上页目录项偏移数即可指向va对应页目录项,即((Pde*)(*vpd)) + (va >> 22);vpt是页表首地址,以vpt为基地址,加上页表项偏移数即可指向va对应的页表项,即((Pte*)(*vpt)) + (va >> 12)。*vpd的值是可以来判断当前va是否有对应页表项的,若存在这个页表项,才可以访问对应的*vpt的值。

  • 在user/entry.S中定义了vpt和vpd

  •      .globl vpt
     vpt:
         .word UVPT
     ​
         .globl vpd
     vpd:
         .word (UVPT+(UVPT>>12)*4)

    定义了用户页表用户页目录的虚拟地址,而且还是自映射的页表。这使得每个进程的页表都能在UVPT中保存,切换进程时,页表也会切换。

  • vpd的地址为(UVPT+(UVPT>>12)*4),显然这是自映射的特点。

  • 不能。该区域对用户只读不写,若想要增添页表项,需要陷入内核进行操作。

 

Thinking 4.7

page_fault_handler 函数中,你可能注意到了一个向异常处理栈复制Trapframe运行现场的过程,请思考并回答这几个问题:

  • 这里实现了一个支持类似于“中断重入”的机制,而在什么时候会出现这种“中断重入”?

  • 内核为什么需要将异常的现场Trapframe复制到用户空间?

 

  • 缺页中断时再次响应外部中断,在标志有COW的页面被修改时会出现。

  • 该MOS操作系统按照微内核的设计理念, 尽可能地将功能实现在用户空间中,因此主要的处理过程是在用户态下完成的,所以需要将其复制到用户空间。

 

Thinking 4.8

到这里我们大概知道了这是一个由用户程序处理并由用户程序自身来恢复运行现场的过程,请思考并回答以下几个问题:

  • 在用户态处理页写入异常,相比于在内核态处理有什么优势?

  • 从通用寄存器的用途角度讨论,在可能被中断的用户态下进行现场的恢复,要如何做到不破坏现场中的通用寄存器?

 

  • 内核态处理失误产生的影响较大,可能会使得操作系统崩溃。此外,用户状态下不能得到一些在内核状态才有的权限,避免改变不必要的内存空间。

  • 通用寄存器可以通过宏指令全部存入栈中,现场恢复时再取出来即可。(感觉好像又不是这意思 ̄ω ̄=)

 

Thinking 4.9

请思考并回答以下几个问题:

  • 为什么需要将set_pgfault_handler的调用放置在syscall_env_alloc之前?

  • 如果放置在写时复制保护机制完成之后会有怎样的效果?

  • 子进程是否需要对在entry.S定义的字__pgfault_handler赋值?

 

  • 子进程不会调用set_pgfault_handler,且父子进程共享空间,pgfault_handler函数都在UXSTACKTOP上,父子进程的缺页中断都可以被捕捉到。

  • 写时复制保护机制完成后发生缺页中断不能够被捕捉到,导致无法进入缺页中断异常。

  • 不需要,父进程已设置好,子进程共享即可。

实验难点展示

系统调用

系统调用的函数十分简单,以前写了page_alloc,现在继续写mem_alloc,每次都是用一个if语句赋值给一个整形值,判断是否小于零,是的话就返回这个值,这些负数值都是有宏定义的,可以去触发一些异常。

我想说的是syscall.S这个函数的编写,往年貌似就考过这个。这个函数干的事情很明显的揭示了系统调用是如何将用户态的数据跨越到内核态的。值得一提的是在Mars里面编程的系统调用号寄存器为$v0,然后我就复制了这个寄存器,后面是一行一行对着别人的代码才看出来的,在这个操作系统中,系统调用号就是第一个参数对应的寄存器$a0,我们需要把这个寄存器读出来,才能知道是什么系统调用,才能运行对应的系统调用程序(通过jal t2实现,而t2又是由$a0决定的)。系统调用表就在下方sys_call_table处,和lab3处理多种中断的方式一模一样

 lw a0, TF_REG4(sp)
 •    lw a1, TF_REG5(sp)
 •    lw a2, TF_REG6(sp)
 •    lw a3, TF_REG7(sp)
 •    addiu sp, sp, -24
 •    sw t3, 16(sp)
 •    sw t4, 20(sp)   
 ​
 •    jalr    t2                          // Invoke sys_* function
 •    nop

 

(图源PPT)

用户态与内核态函数

这个很容易把人搞晕的,我只能说在lib里的都是内核态函数,在user里的都是用户态函数,内核态函数sys_*,用户态函数是syscall_*。我们补充的几乎都是内核态的系统调用函数(lib/syscall_all.c),而用户态的系统调用函数全是直接调用msyscall(user/syscall_lib.c)。

在做课上实验的时候,实现一个新的系统调用基本上是四步走:

  1. 在头文件中先定义好新的系统调用号系统调用名

  2. 用户态先定义这个syscall_*函数,调用msyscall

  3. 在syscall.S中添加上新的系统调用名

  4. 在内核空间实现这个系统调用sys_*,这才是实现该系统调用功能的主要步骤。

进程间通信

在lab 4-1-Extra中实现了对发送进程的阻塞,这避免了CPU的轮询。最近几次课上都考到了类似于PV操作的知识,而在我看来PV操作与信号量相关的一部分知识是操作系统理论课最灵活的部分了。PV操作本质是对一个整形的信号量进行加加减减的操作,实现起来也是如此。

一个进程能够接收信息之前,可以有多个进程准备发送消息,但他们必须处于阻塞状态,因为要接受消息的进程还没有准备好接收。而若是出现这种情况,接收进程可以直接接收消息,并不再过渡到NOT_RUNNABLE状态。

实现的大致思路是需要建一个结构体来存储要发送进程的信息,在发送进程碰壁时,设置为NOT_RUNNABLE状态,并直接yield,要注意,这个函数会直接导致该进程当前执行的函数return,所以只有在sys_ipc_recv函数中直接接收消息。

创建子进程

这部分是我认为lab 4中最难的。

父进程要做的事:

  1. 设置缺页中断处理函数入口地址

  2. 使用syscall_env_alloc创建子进程

  3. 使用duppage设置PTE_COW标识,并将子进程的虚拟空间和父进程的虚拟空间联合起来,指向相同的物理页面

  4. 为子进程分配异常处理栈,为子进程设置状态

子进程要做的事:

  1. 设置子进程所代表的env指针

  2. 子进程在缺页中断时陷入内核,再返回到设置好的缺页中断处理函数中用户态下的pgfault函数,拷贝完页面后,返回到用户态的中断现场。

缺页中断的处理:

  1. 陷入内核,执行page_fault_handler(lib/trap.c),保存现场并设置异常处理栈,这里面设置了EPC的值tf->cp0_epc = curenv->env_pgfault_handler;

  2. 这个值其实就是__asm_pgfault_handler(user/entry.S)这个函数,lw t1, __pgfault_handler jalr t1,它调用了pgfault函数,缺页处理,拷贝页面。

  3. 然后返回到__asm_pgfault_handler函数中,通过使用lw指令将栈中保存的值放回寄存器中,保存现场,返回到中断现场。

体会与感想

lab 4在思维要求上比前几个实验更难了,最主要做的就是两个部分,内核函数和用户函数的相结合、缺页中断的设置与处理。

一方面,脑子里一定要清楚现在是在改内核还是改用户,因为之前写的都是内核,而内核函数是不能在用户空间调用的。以前写过的page_insert呀,envid2env呀都不能用在用户空间了,只能用user_bcopy和user_bzero来代替bcopy和bzero了。现如今看来,从用户空间到内核空间可以是系统调用或者中断异常的形式,系统调用将环境上下文保存在KERNEL_SP中,中断异常将环境上下文保存在TIMESTACK中。

另一方面,缺页中断是父子进程间实现的一个重要的功能,但我印象最深的是父子进程调用fork产生不同的返回值的实现,这是在int sys_env_alloc(void)(lib/syscall_all.c)中实现的。将父进程的epc设置为子进程的pc值,这让子进程第一次执行时就能够接着调用fork的后一条指令。父进程直接返回子进程进程号,子进程则将0这个返回值保存在PCB里,轮到子进程执行时直接把$v0寄存器设置为0。

写的代码越多,bug也就越多,我课下遇到的一个bug是在fork时会创建两个进程,加上父进程一共三个,试了很久才发现是env_run这个函数的锅,十分的难受。

遗留难点

关于envid2env这个函数里的checkperm参数,这个用于检查是否是当前进程或当前进程子进程的参数显得很莫名其妙,我大致看了一下,只有在删除一个进程中会将这个参数置1 ,或许是为了确保一个进程只能由自己或自己的父进程结束。其他地方应该都是设置为0的。而经过实验发现,如果在补全代码的时候没有设置为0,很可能还过不了课下。

创建一个新进程需要设置堆栈,设置异常处理程序入口,设置页表,分配页面,拷贝页面,设置pc等等操作,太多太杂了,感觉很难整合起来,而且也很容易忘。

标签:调用,函数,北航,用户,内核,lab4,进程,实验报告,页表
来源: https://www.cnblogs.com/emodiary121/p/16306158.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有