lab3实验报告
一、实验思考题
Thinking3.1
为了保证在envs中顺序与在Env块的顺序相同。
Thinking3.2
- 低10位表示Env在envs中的位置,高位表示调用分配函数的次数。
- 如果只有低位,
Thinking3.3
操作系统采用的布局没有真正的内核进程,用户可以通过临时变成内核态来获得内核空间的管理权限。所以保存boot_pgdir
可以实现访问相应内核区域。
UTOP
是用户可以使用空间的最高地址ULIM
往上是内核空间,是用户空间的最高地址,用户不能修改这之间的内存空间。
env_cr3
存储进程页目录的物理地址,通过这条语句可以实现页目录自映射。
虚拟地址可以让各进程更加安全地运行在自己的空间,只关注于逻辑的地址,而不必考虑复杂的物理地址转换。物理地址是真实连续的内存空间地址。
Thinking3.4
user_data
参数传递了信息
struct Env *env = (struct Env *)user_data;
如果没有这个参数就得不到页目录的信息,无法加载二进制镜像文件。
Thinking3.5
- 内容不能完全填充满一页
bin_size < BY2PG-offset
- 内容填充满整个页面,但是结尾处不是整页,即
% BY2PG != 0
。
bin_size >= BY2PG && (bin_size - BY2PG + offset) % BY2PG != 0
- 内容填充满整个页面,且结尾处是BY2PG的整数倍。
bin_size >= BY2PG && (bin_size - BY2PG + offset) % BY2PG == 0
当segment size
大于bin_size
时需要自动填充.bss
段。
Thinking3.6
虚拟空间,虚拟地址中指令可以认为是连续存放的,所以可以按照顺序执行指令,但如果是物理地址,则有可能是分散在内存空间中的。
每一个进程从elf
文件中读取的entry_point
相同,但储存的物理地址不同,从而能保护进程的内存空间。
Thinking3.7
设为epc
的值,在处理程序运行完毕之后,需要返回中断异常发生的现场,正是epc
所保存的位置。
Thinking3.8
TIMESTACK
是发生时钟中断异常时,内核中保存保存现场所用的栈指针。
#include <asm/regdef.h>
#include <asm/cp0regdef.h>
#include <asm/asm.h>
#include <trap.h>
.macro STI
mfc0 t0, CP0_STATUS
li t1, (STATUS_CU0 | 0x1)
or t0, t1
mtc0 t0, CP0_STATUS
.endm
.macro CLI
mfc0 t0, CP0_STATUS
li t1, (STATUS_CU0 | 0x1)
or t0, t1
xor t0, 0x1
mtc0 t0, CP0_STATUS
.endm
.macro SAVE_ALL
mfc0 k0,CP0_STATUS
sll k0,3 /* extract cu0 bit */
bltz k0,1f
nop
/*
* Called from user mode, new stack
*/
//lui k1,%hi(kernelsp)
//lw k1,%lo(kernelsp)(k1) //not clear right now
1:
move k0,sp
get_sp
move k1,sp
subu sp,k1,TF_SIZE
sw k0,TF_REG29(sp)
sw $2,TF_REG2(sp)
mfc0 v0,CP0_STATUS
sw v0,TF_STATUS(sp)
mfc0 v0,CP0_CAUSE
sw v0,TF_CAUSE(sp)
mfc0 v0,CP0_EPC
sw v0,TF_EPC(sp)
mfc0 v0, CP0_BADVADDR
sw v0, TF_BADVADDR(sp)
mfhi v0
sw v0,TF_HI(sp)
mflo v0
sw v0,TF_LO(sp)
sw $0,TF_REG0(sp)
sw $1,TF_REG1(sp)
//sw $2,TF_REG2(sp)
sw $3,TF_REG3(sp)
sw $4,TF_REG4(sp)
sw $5,TF_REG5(sp)
sw $6,TF_REG6(sp)
sw $7,TF_REG7(sp)
sw $8,TF_REG8(sp)
sw $9,TF_REG9(sp)
sw $10,TF_REG10(sp)
sw $11,TF_REG11(sp)
sw $12,TF_REG12(sp)
sw $13,TF_REG13(sp)
sw $14,TF_REG14(sp)
sw $15,TF_REG15(sp)
sw $16,TF_REG16(sp)
sw $17,TF_REG17(sp)
sw $18,TF_REG18(sp)
sw $19,TF_REG19(sp)
sw $20,TF_REG20(sp)
sw $21,TF_REG21(sp)
sw $22,TF_REG22(sp)
sw $23,TF_REG23(sp)
sw $24,TF_REG24(sp)
sw $25,TF_REG25(sp)
sw $26,TF_REG26(sp)
sw $27,TF_REG27(sp)
sw $28,TF_REG28(sp)
sw $30,TF_REG30(sp)
sw $31,TF_REG31(sp)
.endm
/*
* Note that we restore the IE flags from stack. This means
* that a modified IE mask will be nullified.
*/
.macro RESTORE_SOME
.set mips1
mfc0 t0,CP0_STATUS
ori t0,0x3
xori t0,0x3
mtc0 t0,CP0_STATUS
lw v0,TF_STATUS(sp)
li v1, 0xff00
and t0, v1
nor v1, $0, v1
and v0, v1
or v0, t0
mtc0 v0,CP0_STATUS
lw v1,TF_LO(sp)
mtlo v1
lw v0,TF_HI(sp)
lw v1,TF_EPC(sp)
mthi v0
mtc0 v1,CP0_EPC
lw $31,TF_REG31(sp)
lw $30,TF_REG30(sp)
lw $28,TF_REG28(sp)
lw $25,TF_REG25(sp)
lw $24,TF_REG24(sp)
lw $23,TF_REG23(sp)
lw $22,TF_REG22(sp)
lw $21,TF_REG21(sp)
lw $20,TF_REG20(sp)
lw $19,TF_REG19(sp)
lw $18,TF_REG18(sp)
lw $17,TF_REG17(sp)
lw $16,TF_REG16(sp)
lw $15,TF_REG15(sp)
lw $14,TF_REG14(sp)
lw $13,TF_REG13(sp)
lw $12,TF_REG12(sp)
lw $11,TF_REG11(sp)
lw $10,TF_REG10(sp)
lw $9,TF_REG9(sp)
lw $8,TF_REG8(sp)
lw $7,TF_REG7(sp)
lw $6,TF_REG6(sp)
lw $5,TF_REG5(sp)
lw $4,TF_REG4(sp)
lw $3,TF_REG3(sp)
lw $2,TF_REG2(sp)
lw $1,TF_REG1(sp)
.endm
.macro RESTORE_ALL
RESTORE_SOME
lw sp,TF_REG29(sp) /* Deallocate stack */
.endm
.set noreorder
.macro RESTORE_ALL_AND_RET
RESTORE_SOME
lw k0,TF_EPC(sp)
lw sp,TF_REG29(sp) /* Deallocate stack */
jr k0
rfe
.endm
.macro get_sp
mfc0 k1, CP0_CAUSE
andi k1, 0x107C
xori k1, 0x1000
bnez k1, 1f
nop
li sp, 0x82000000
j 2f
nop
1:
bltz sp, 2f
nop
lw sp, KERNEL_SP
nop
2: nop
.endm
在这段代码中,.macro get_sp
段中,先从CP0
中读取信息,如果读到原因是4号中断,就把TIMESTACK
的值存到sp
中,保存上下文。与KERNEL_SP
不同在于TIMESTACK
栈指针保存了时钟产生的中断异常,而KERNEL_SP
栈指针是非时钟中断异常时使用。
Thinking3.9
li t0, 0x01 // 把1存入$t0
sb t0, 0xb5000100 // 在0xb5000100地址中写入0x01
sw sp, KERNEL_SP // 栈指针设置为KERNEL_SP
setup_c0_status STATUS_CU0|0x1001 0 // 调用宏函数设置CP0_STATUS的值
jr ra // 返回
nop
Thinking3.10
设置进程就绪队列,每一个进程中添加一个时间片,从而起到计时的功能。当时间片用完后,该进程执行时钟中断操作,这个进程会被移动到就绪队列的队尾,并且复位时间片。之后让就绪队列的队首进程继续执行相应的时间片。循环此过程,做到了时钟周期切换进程。
二、实验难点图示
加载二进制镜像
load_icode
,进行内存的分配,然后将二进制代码加载至已经分配好的内存中,这一步会调用load_elf
load_elf
,首先解析ELF
的结构,然后将ELF
的内容复制到内存中,这一步会调用load_icode_mapper
load_icode_mapper
,进行复制,需要将load_elf
中返回的entry_point
设置为正确的PC
值
中断
start.S
中分发异常,开启时钟中断。
进程调度通过
- 设置两个队列
- 判断当前所在队列的队首进程的状态
ENV_FREE
,则移除ENV_NOT_RUNNABLE
,加入另一个队列的尾部ENV_RUNNABLE
,如果时间片用完,则复原时间片后插入另一个队列的尾部
- 如果当前队列为空,转到另一个队列。
进程地址空间
UTOP
以下可操作UVPT
与用户进程页目录相关,需要用自映射机制进行单独处理。
三、体会与感想
本lab实验总体上内容比较多,课堂上有很多进程调度相关的算法,在这次lab中也实现了一个简单的时间片算法,首次深入体会操作系统如何管理进程。
遇到比较大的困难在于代码的理解和debug方面,要阅读相关的代码并且进行补充实现。在要实现的代码部分一般有比较详细的注释说明了核心用法、前置条件和后置条件,有些还有提示。但是自己在实现时可能还是有一些细节没有考虑足够,所以在实现时出现了bug。但由于调试手段相对匮乏,通过多个带标记的printf
才将bug定位出来,最终成功修复bug。
- 输出
there is xxx
定位代码运行位置 - 输出关键的变量值
进程调度的算法是第二部分比较核心的内容,这部分相关的汇编代码理解比较重要,需要理解如何分发异常和处理中断。另外在很多函数中调用了lab2中的内存相关的函数,对于复杂的函数,应该尽量考虑全面,操作系统各个部分联系在一起,所以如果漏掉某种情况,就很可能在其他部分出现一些错误,给debug的工作带来一定困难。
标签:v0,sp,sw,t0,lab3,lw,TF,实验报告 From: https://www.cnblogs.com/ddl789/p/17215428.html