Lab3 User Environments

Lab3 User Environments Part A

Note:在本lab中需要注意,进程和环境的意思是相同的。
在 kern/env.c文件中定义了三个与环境有关的全局变量:
struct Env *envs = NULL; // All environments
struct Env *curenv = NULL; // The current env
static struct Env *env_free_list; // Free environment list

像Unix进程一样,JOS进程将“线程”和“地址空间”的概念结合在一起。 线程主要由保存的寄存器(env_tf字段)定义,地址空间由env_pgdir指向的页目录和页表定义。 要运行环境,内核必须使用保存的寄存器和适当的地址空间来设置CPU。

exercise1:修改mem_init()函数,完成envs数组的分配。

//
 // Make 'envs' point to an array of size 'NENV' of 'struct Env'.
 // LAB 3: Your code here.
 envs=(struct Env *) boot_alloc(NENV * sizeof(struct Env));
 memset(envs,0,NENV * sizeof(struct Env));

仿照页的分配,能比较轻松的写出如下的代码,先找到分配的虚拟地址位置,然后使用memset函数进行实际分配赋值。

第二步建立UNEV线性地址和实际物理地址的链接,仿造lab2的代码可以轻松实现出:

//
 // Map the 'envs' array read-only by the user at linear address UENVS
 // (ie. perm = PTE_U | PTE_P).
 // Permissions:
 //    - the new image at UENVS  -- kernel R, user R
 //    - envs itself -- kernel RW, user NONE
 // LAB 3: Your code here.
 
 boot_map_region(kern_pgdir, UENVS, NENV * sizeof(struct Env), PADDR(envs), PTE_U);

调用boot_map_region函数,该函数可以先根据UENVS找到对应的页,再将该页表项指向对应的物理地址,即完成了映射。
到此为止exercise1完成。

exercise2:完成env.c里面的以下几个功能。
1.env_init()

void env_init(void)
{
 // Set up envs array
 // LAB 3: Your code here.
 for(int i=NENV-1; i>=0; i--){
  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_free_list指向envs[0]的位置。
2.env_setup_vm()
该函数的功能是为新环境分配页面目录,并初始化新环境的地址空间的内核部分。
在这里插入图片描述
使用page_alloc()分配一页空间,把分配到的这一页空间的地址赋给e->env_pgdir,然后内核空间的分配,用memcpy直接完成.

3.region_alloc()
该函数把环境e,映射到起始地址为va,大小为len的虚拟地址处。

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.)
 //
 // 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!)
 uint32_t st=ROUNDDOWN((uint32_t)va,PGSIZE);
 uint32_t end=ROUNDUP((uint32_t)(va+len),PGSIZE);
 for(int i=st; i<end; i+=PGSIZE){
  struct PageInfo *p=page_alloc(ALLOC_ZERO);
  if(!p) {
   panic("region_alloc: page_alloc failed!");
  }
  if(page_insert(e->env_pgdir,p,i,PTE_U) == -E_NO_MEM) {
   panic("region_alloc: page_insert failed!");
  }
  
 }
}

4. load_icode()
该函数加载elf中定义的所有可以加载的段到elf指定的虚拟地址处。
在这里插入图片描述
将elf文件中包含的需要加载的段,通过region_alloc函数映射到环境e的页目录中。需要注意的是,一个环境的内存通常由两部分组成,一部分是环境自己私有的,一部分是环境间共有的。因此首先用lcr3切换到当前进程的页目录表初始化该进程私有的内存,然后使用lcr3切换到内核的页目录表映射该进程公有的内存。完整代码见上图。

5.env_create()

void env_create(uint8_t *binary, enum EnvType type)
{
 // LAB 3: Your code here.
 struct Env *env;
 // Allocates a new env with env_alloc
 if(env_alloc(&env,0) < 0){
  panic("env_create: env_alloc failed!");
 }
 //loads the named elf binary into it with load_icode
 load_icode(env,binary);
 //sets its env_type
 env->env_type=type;
}

env_create()函数较为容易实现,分配环境,加载elf binary中定义的段,设置进程的状态。

6.env_run()
在这里插入图片描述该函数实现较为简单,按着提示步骤一步一步实现即可。

exercise3: 阅读x86手册的Chapter 9 Exceptions and Interrupts。
有两个外部中断源和异常源:
1.中断:
(1)可屏蔽中断,通过INTR引脚发出信号。
(2)不可屏蔽中断,通过NMI引脚发出信号。

2.异常:
(1)处理器级别的异常。比如: faults(故障), traps(陷阱), and aborts(中止).
(2)程序级别的异常。INTO,INT 3,INT n和BOUND指令可以触发异常。这些指令也经常被叫做“软件中断”,处理器把这些指令当作异常处理。

9.1 Identifying Interrupts
处理器用数字编号来识别不同类型的中断和异常:
在这里插入图片描述
三种异常的区别:
错误(Faults):是在导致异常的指令“之前”报告的异常。在指令开始执行之前或指令执行期间检测到错误。 如果在指令执行过程中检测到故障,则会报告故障,并且机器将恢复为允许重新启动指令的状态。
陷阱(Traps):是在检测到异常的指令之后立即在指令边界报告的异常。
中止(abort):是一种异常,它既不允许精确定位导致异常的指令,也不允许重启导致异常的程序。 中止用于报告严重错误,例如硬件错误以及系统表中的不一致或非法值。

9.2 Enabling and Disabling Interrupts
处理器仅在一条指令的结束与下一条指令的开始之间处理中断和异常。然而某些条件和标志设置会导致处理器在指令边界禁止某些中断和异常。

1.在执行NMI处理程序时,处理器将忽略NMI引脚上的其他中断信号,直到执行下一条IRET指令为止。

2.IF(中断使能标志)控制通过INTR引脚发出的外部中断的接收。 当IF = 0时,禁止INTR中断。 当IF = 1时,允许INTR中断。 与其他标志位一样,处理器响应RESET信号清除IF。 CLI和STI指令更改了IF的设置。
CLI(清除中断使能标志)和STI(设置中断使能标志)显式更改IF(标志寄存器中的位9)。 仅当CPL <= IOPL时才能执行这些指令。 如果在CPL> IOPL时执行保护异常,则会发生保护异常。

IF还受到以下操作的隐式影响:
(1)指令PUSHF将所有标志(包括IF)存储在堆栈中,以便对其进行检查。
(2)任务开关和指令POPF和IRET加载标志寄存器; 因此,它们可用于修改IF。
(3)通过中断门的中断会自动重置IF,从而禁用中断。

3.EFLAGS中的RF位控制调试故障的识别。 这允许给定指令最多引发一次调试错误,而不管指令重新启动了多少次。

9.3 Priority Among Simultaneous Interrupts and Exceptions
中断和异常之间存在优先级,当在指令之间存在多个中断或异常时,处理器会处理优先级最高的异常或中断。

9.4 Interrupt Descriptor Table
中断描述符表(IDT)将每个中断或异常标识符与服务于相关事件的指令的描述符相关联。
在这里插入图片描述
9.5 IDT Descriptors
IDT可以包含三种描述符中的任意一种:Task gates,Interrupt gates和Trap gates。
在这里插入图片描述

9.6 Interrupt Tasks and Interrupt Procedures
就像CALL指令可以调用过程或任务一样,中断或异常也可以“调用”过程或任务的中断处理程序。 当响应中断或异常时,处理器使用中断或异常标识符为IDT中的描述符建立索引。 如果处理器索引到中断门或陷阱门,则它以类似于CALL到调用门的方式调用处理程序。 如果处理器找到任务门,它将以类似于CALL的方式导致任务切换到任务门。
在这里插入图片描述
通过中断门或陷阱门的向量中断会在TF的当前值作为EFLAGS的一部分保存到堆栈后,导致TF(陷阱标志)被复位。 通过此操作,处理器可防止使用单步调试活动影响中断响应。 随后的IRET指令将TF恢复为堆栈上EFLAGS映像中的值。

中断门和陷阱门之间的差异在于对IF(中断使能标志)的影响。 通过中断门引导的中断会将IF复位,从而防止其他中断干扰当前的中断处理程序。 随后的IRET指令将IF恢复为堆栈上EFLAGS映像中的值。 通过陷阱门的中断不会改变IF。

Basics of Protected Control Transfer

异常和中断都是“受保护的控制传递”,它们导致处理器从用户模式切换到内核模式(CPL = 0),而没有给用户模式代码任何干扰内核或其他环境功能的机会。
1.The Interrupt Descriptor Table.用来描述内核的入口地址。

2.The Task State Segment.用来存储处理器原来的状态(old state)。

exercise 4:中断的处理大致如下图,在IDT中定义中断处理器的地址,结合前面的章节,IDT可以包含三种描述符:事务门,中断门和陷阱门。然后在定义各种中断处理器来对不同的中断进行处理。通过编辑trapentry.S 和 trap.c 文件来实现以上内容。
在这里插入图片描述
按照题目的提示首先在trapentry.S中定义入口指针。代码如下:

/*
 * Lab 3: Your code here for generating entry points for the different traps.
 */
TRAPHANDLER_NOEC(divide_entry, T_DIVIDE);
TRAPHANDLER_NOEC(debug_entry, T_DEBUG);
TRAPHANDLER_NOEC(nmi_entry, T_NMI);
TRAPHANDLER_NOEC(brkpt_entry, T_BRKPT);
TRAPHANDLER_NOEC(oflow_entry, T_OFLOW);
TRAPHANDLER_NOEC(bound_entry, T_BOUND);
TRAPHANDLER_NOEC(illop_entry, T_ILLOP);
TRAPHANDLER_NOEC(device_entry, T_DEVICE);
TRAPHANDLER(dblflt_entry, T_DBLFLT);
TRAPHANDLER(tss_entry, T_TSS);
TRAPHANDLER(segnp_entry, T_SEGNP);
TRAPHANDLER(stack_entry, T_STACK);
TRAPHANDLER(gpflt_entry, T_GPFLT);
TRAPHANDLER(pgflt_entry, T_PGFLT);
TRAPHANDLER_NOEC(fperr_entry, T_FPERR);
TRAPHANDLER(align_entry, T_ALIGN);
TRAPHANDLER_NOEC(mchk_entry, T_MCHK);
TRAPHANDLER_NOEC(simderr_entry, T_SIMDERR);
TRAPHANDLER_NOEC(syscall_entry, T_SYSCALL);

trapentry.S 文件中定义了两种宏:TRAPHANDLER_NOEC(不push error code)和TRAPHANDLER(push error code)。然后会跳转到 _alltraps,下面补充_alltraps的代码:
在这里插入图片描述然后编辑trap.c文件中的trap_init()函数:
在这里插入图片描述
注意此处用到了SETGATE函数来设置IDT中的三个门,该函数的参数解释如下:
gate:要设置的中断描述符项
istrap:该位为1表示该描述符为陷入门,为0表示为中断门,两者区别在于中断门会将IF位置0来阻止嵌套
中断的发生,而陷入门不会。
sel:中断/陷入处理程序的代码段选择器。
off:中断/陷入处理程序的代码段偏移量。
dpl:特权级别描述符,软件调用所需的特权等级。

代码填充完毕后,make grade测试通过,至此Lab3 Part A全部完成。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值