Lab 4A SMP与协作调度
继 lab 2 的虚拟内存以及 lab 3 的用户态环境之后,lab 4 引入多处理器、调度管理以及进程间通讯机制,这部分功能也让 JOS 更契合实际的应用场景,为之后的文件系统以及网卡驱动部分打下基础。
在 lab 3 中,关键点是 CPU 状态的暂存于恢复,X86 也提供了 IDT 与 TSS 来进行受保护的控制转移。lab 4 中引入了 SMP,多处理器情况下受保护的控制转移机制是不变的,还是通过每个 CPU 自己的 TSS 与内核栈来完成,变化的是:
- 多核访问共享资源时的同步机制
- 用户态环境需记录 CPU 信息,方便调度
lab 4 内容看似复杂,但抓住 lab 3 不变的机制可以看到 SMP 引入的更多是分布式情况下的通信以及共享资源的同步机制,其余内容与 lab 3 保持一致,之前是单个结构体的信息现在用数组加索引来表示。
lab 3 单核 CPU,图示1:
lab 4 多核CPU,图示2:
1. 多核处理器支持与协作调度
多核处理器属于分布式场景,可以将各个 CPU 看做分布式对象,各 CPU 的标识与通信通过高级可编程中断控制器 LAPIC (Advanced Programmable Interrupt Controller)来完成,LAPIC 也负责在系统中分发中断,图示3:
SMP 系统中,CPU 被分成两类,BSP(Bootstrap Processor)与 AP(Application Processor),分别代表启动处理器与应用处理器,另外需要注意的是在 lab 4 中 curenv 指针的实现方式发生了改变,现在是通过宏的方式来实现,这样的话 curenv 指向当前处理器的用户态程序环境,其中 thiscpu 也是通过宏的方式实现,指向 cpus 数组的 cpu 信息,相关信息如下。
#define curenv (thiscpu->cpu_env) // Current environment
#define thiscpu (&cpus[cpunum()])
// Per-CPU state
struct CpuInfo {
uint8_t cpu_id; // Local APIC ID; index into cpus[] below
volatile unsigned cpu_status; // The status of the CPU
struct Env *cpu_env; // The currently-running environment.
struct Taskstate cpu_ts; // Used by x86 to find stack for interrupt
};
// Initialized in mpconfig.c
extern struct CpuInfo cpus[NCPU];
extern int ncpu; // Total number of CPUs in the system
复制代码
Exercsie 1
JOS 对 APIC 内容的访问通过 MMIO (memory-mapped I/O)来实现,可以通过读写内存地址的方式来访问 LAPIC 相关的寄存器内容
A processor accesses its LAPIC using memory-mapped I/O (MMIO). In MMIO, a portion of physical memory is hardwired to the registers of some I/O devices, so the same load/store instructions typically used to access memory can be used to access device registers.
MMIO 为 hardwired logic
固定线路逻辑,代码中将其地址空间映射至内存
void *
mmio_map_region(physaddr_t pa, size_t size)
{
static uintptr_t base = MMIOBASE;
//首先得到对应的物理页
physaddr_t start = ROUNDDOWN(pa,PGSIZE);
physaddr_t end = ROUNDUP(pa+size,PGSIZE);
size_t round_size = end-start;
if (base+round_size >= MMIOLIM) {
panic("va_start overflow MMIOLIM,%08x",MMIOBASE);
}
boot_map_region(kern_pgdir, base, round_size, start, PTE_PCD | PTE_PWT | PTE_W);
uintptr_t prev_base = base;
//需注意此处并不需要对其后的base,而是字节单位的base
base += round_size;
return (void *)prev_base;
}
复制代码
Exercise 2
Exercise 2 中提示读以下内容的代码:boot_aps , mp_main 与 kern/mpentry.S,弄清楚 AP 是如何初始化启动的,图示3:
在 page_init 中增加如下代码,跳过 MMIO 映射物理页
Exercise 3
增加多个 CPU 栈的映射,循环映射即可,其中栈地址向下增长
static void
mem_init_mp(void)
{
// Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs.
// 多个CPU栈的映射,地址向下增长,将其视为向量
for(uint32_t i=0;i<NCPU;i++){
uintptr_t kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
//虚拟地址转物理地址
boot_map_region(kern_pgdir,kstacktop_i-KSTKSIZE,KSTKSIZE,PADDR(percpu_kstacks[i]),PTE_W);
cprintf("CPU:%d,Entry:%d,Base VA:%08x\n",i,kstacktop_i-KSTKSIZE,percpu_kstacks[i]);
}
}
复制代码
Exercise 4
针对每个 CPU 进行 IDT 与 TSS 的初始化,可参考上面的图示2。
// Initialize and load the per-CPU TSS and IDT
void
trap_init_percpu(void)
{
//设置percpu的tss属性,非循环赋值
uint8_t cur_cpuid= thiscpu->cpu_id;
//虚拟内存已申请,直接赋值即可
//KSTACKTOP - cur_cpuid * (KSTKSIZE + KSTKGAP);
uintptr_t kstacktop_i = (uint32_t)percpu_kstacks[cur_cpuid] + KSTKSIZE;
thiscpu->cpu_ts.ts_esp0 = kstacktop_i;
thiscpu->cpu_ts.ts_ss0 = GD_KD;
//IO映射的Base Addr,*(cur_cpuid+1),暂不清楚
thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate);
//设置GDT的entry
uint32_t cur_tss_index = (GD_TSS0 >> 3)+cur_cpuid;
gdt[(GD_TSS0 >> 3)+cur_cpuid] = SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts),
sizeof(struct Taskstate) - 1, 0);
gdt[(GD_TSS0 >> 3)+cur_cpuid].sd_s = 0;
ltr(cur_tss_index<<3);
lidt(&idt_pd);
}
复制代码
Exercise 5
多处理器情境下,引入了对共享资源的争用,所以需要通过 JOS 提供的锁来进行共享资源的同步,整体流程是,进入内核加锁,离开内核解锁
init.c 中
trap.c 中
解锁部分,之所以在 lcr3 之前就解锁,是因为 cr3与之后的内核栈都是 CPU 本地的变量更新,非共享资源
void
env_run(struct Env *e)
{
if(curenv!=NULL && curenv->env_status == ENV_RUNNING){
curenv->env_status = ENV_RUNNABLE;
}
curenv = e;
curenv-> env_status = ENV_RUNNING;
curenv-> env_runs++;
unlock_kernel();
lcr3(PADDR(curenv->env_pgdir));
//Step 2
env_pop_tf(&(curenv->env_tf));
}
复制代码
JOS 中的锁机制通过内联汇编来实现
Exercise 6
这部分需要我们实现 Round-Robin 调度,通过cur_envid 将 envs 分为三部分,寻找合适的用户态环境即可,也将 envs 看做一个环形队列,不过环形队列的实现中 corner case 会稍微复杂点,不如下面这个实现直观
void
sched_yield(void)
{
struct Env *idle = curenv;
int cur_envid = (idle == NULL) ? -1 : ENVX(idle->env_id);
int i;
for (i = cur_envid + 1; i < NENV; i++) {
if (envs[i].env_status == ENV_RUNNABLE) {
env_run(&envs[i]);
}
}
for (i = 0; i < cur_envid; i++) {;
if (envs[i].env_status == ENV_RUNNABLE) {
env_run(&envs[i]);
}
}
if(idle != NULL && idle->env_status == ENV_RUNNING) {
env_run(idle);
}
// sched_halt never returns
sched_halt();
}
复制代码
Exercise 7
Exercise 7 中有一大堆系统调用等待实现,这部分内容需要注意的有两点:
- 用户态环境之间的相互操作,如 sys_page_map 等首先要通过权限校验,可以通过调用 JOS 提供的
envid2env()
来进行
For all of the system calls above that accept environment IDs, the JOS kernel supports the convention that a value of 0 means "the current environment." This convention is implemented by
envid2env()
inkern/env.c
.
- 标志位的权限校验通过如下方式进行,因为有些权限标志超过一位,部分权限位中 0 也有特殊含义,对这些权限位的位运算如果直接判断条件,可能导致无意的 bug
if ((perm & (PTE_U | PTE_P))!=(PTE_U | PTE_P)){
//XXX
}
复制代码
duppage 的流程示意,图示4:
- sys_exofork,拷贝父进程的 env_tf,而非简单的寄存器内容(env_tf 才是完整的状态),并注意设置子进程的返回内容为 0
static envid_t
sys_exofork(void)
{
struct Env *child_store;
struct Env *parent = thiscpu->cpu_env;
int res_code = env_alloc(&child_store,parent->env_id);
if(res_code!=0){
return res_code;
}
child_store->env_status = ENV_NOT_RUNNABLE;
//拷贝寄存器
//child_store->env_tf.tf_regs = parent->env_tf.tf_regs; //错误方式
child_store->env_tf = parent->env_tf;
//返回值是通过env_tf.tf_eax来获取
child_store->env_tf.tf_regs.reg_eax = 0;
return child_store->env_id;
}
复制代码
- sys_env_set_status
static int
sys_env_set_status(envid_t envid, int status)
{
struct Env *target_env;
int res_code;
if((res_code = envid2env(envid,&target_env,1))!=0){
return res_code;
}
// 布尔逻辑
if(status!=ENV_RUNNABLE && status!=ENV_NOT_RUNNABLE){
return -E_INVAL;
}
target_env->env_status = status;
return 0;
}
复制代码
- sys_page_alloc,先进性参数校验,申请一个页后将其插入目标 env 的地址空间中
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
struct Env *target_env;
int res_code;
if((res_code = envid2env(envid,&target_env,1))!=0){
return res_code;
}
if((uintptr_t)va>=UTOP||(uintptr_t)va%PGSIZE!=0){
return -E_INVAL;
}
//检查权限
if((perm & (PTE_U | PTE_P))!=(PTE_U | PTE_P) || (perm & ~PTE_SYSCALL)!=0){
return -E_INVAL;
}
struct PageInfo *pp = page_alloc(ALLOC_ZERO);
if(pp==NULL){
return -E_NO_MEM;
}
//此处为目标env的页表地址
res_code = page_insert(target_env->env_pgdir,pp,va,perm);
log("[page alloc]envid:%08x,va:%08x\n",target_env->env_id,va);
if(res_code!=0){
page_free(pp);
return -E_NO_MEM;
}
return 0;
}
复制代码
- sys_page_map,查表,二次映射
static int
sys_page_map(envid_t srcenvid, void *srcva,
envid_t dstenvid, void *dstva, int perm)
{
struct Env *src_env;
int res_code;
if((res_code = envid2env(srcenvid,&src_env,1))!=0){
return res_code;
}
struct Env *dst_env;
if((res_code = envid2env(dstenvid,&dst_env,1))!=0){
return res_code;
}
if((uintptr_t)srcva>=UTOP||(uintptr_t)srcva%PGSIZE!=0){
cprintf("Invalid Parameter srcva:%08x\n",srcva);
return -E_INVAL;
}
if((uintptr_t)dstva>=UTOP||(uintptr_t)dstva%PGSIZE!=0){
cprintf("Invalid Parameter dstva:%08x\n",srcva);
return -E_INVAL;
}
//检查是否有物理内存映射
pde_t *src_pgdir = src_env->env_pgdir;
pte_t *src_pte= pgdir_walk(src_pgdir,srcva,0);
if(!(src_pte&&(*src_pte&PTE_P))){
cprintf("Invalid PTE_P:%08x\n",srcva);
return -E_INVAL;
}
//检查权限
if((perm & (PTE_U | PTE_P))!=(PTE_U | PTE_P) || (perm & ~PTE_SYSCALL)!=0){
cprintf("Error:%08x,%08x\n",(perm & (PTE_U | PTE_P)),(perm & ~PTE_SYSCALL));
cprintf("Invalid PTE 1:%08x\n",srcva);
return -E_INVAL;
}
//读写权限检查,考虑COW的情形
//if((perm & PTE_W) != (*src_pte & PTE_W)){
if((*src_pte & PTE_W)==0 && (perm & PTE_W) != 0){
cprintf("Invalid PTE 2:%08x\n",srcva);
return -E_INVAL;
}
//struct PageInfo *pp = page_alloc(ALLOC_ZERO);
pde_t *dst_pgdir = dst_env->env_pgdir;
pte_t *dst_pte= pgdir_walk(dst_pgdir,dstva,1);
if(dst_pte==NULL){
return -E_NO_MEM;
}
physaddr_t phaddr = PTE_ADDR(*src_pte);
struct PageInfo *pp = pa2page(phaddr);
res_code = page_insert(dst_pgdir,pp,dstva,perm);
log("[page insert]envid:%08x,va:%08x\n",dst_env->env_id,dstva);
if(res_code!=0){
return res_code;
}
return 0;
}
复制代码
- sys_page_unmap,删除映射关系
static int
sys_page_unmap(envid_t envid, void *va)
{
struct Env *target_env;
int res_code;
if((res_code = envid2env(envid,&target_env,1))!=0){
return res_code;
}
if((uintptr_t)va>=UTOP||(uintptr_t)va%PGSIZE!=0){
return -E_INVAL;
}
//检查是否有物理内存映射
pde_t *src_pgdir = target_env->env_pgdir;
pte_t *src_pte= pgdir_walk(src_pgdir,va,0);
if(!(src_pte&&(*src_pte&PTE_P))){
return 0;
}
page_remove(src_pgdir,va);
return 0;
}
复制代码
Lab 4B COW Fork
cow 部分巧妙地利用了操作系统的缺页中断机制,通过提供用户态的缺页异常处理器,来进行灵活的处理,图示5如下,左侧是普通的中断处理程序,右侧是用户态缺页处理程序,可以看到主要还是栈相关的内容。
函数调用机制如下图所示,The ret
works the same as pop %eip
.
Exercise 8
sys_env_set_pgfault_upcall,函数指针赋值
static int
sys_env_set_pgfault_upcall(envid_t envid, void *func)
{
// LAB 4: Your code here.
struct Env *target;
int res_code;
if((res_code = envid2env(envid,&target,1))!=0){
return res_code;
}
target->env_pgfault_upcall = func;
return 0;
}
复制代码
Exercise 9
page_fault_handler,联系 lab 3,这部分内容还是 CPU 状态的暂存与恢复,这儿只是多转了一手,到 UTrapframe 中了,后面的恢复也是从 UTrapFrame 中恢复。
void
page_fault_handler(struct Trapframe *tf)
{
uint32_t fault_va;
// Read processor's CR2 register to find the faulting address
fault_va = rcr2();
// 保护模式下cs中为代码段选择子
uint16_t code_segment_selector= tf->tf_cs;
if(debug){
cprintf("code_segment_selector:%08x,fault_va:%08x\n",code_segment_selector,fault_va);
}
// DPL中0为内核态,3为用户态
if((tf->tf_cs & 0x3) == 0){
print_trapframe(tf);
panic("Page Fault Occured in Kernel Mode");
}
// 用户级缺页处理
void *handler = curenv->env_pgfault_upcall;
//uintptr_t uxtop;
if(handler!=NULL){
struct UTrapframe *utf;
//1.初次用户态pgfault,之前将宏混淆,导致错误难以DEBUG
if(tf->tf_esp>=UXSTACKTOP || tf->tf_esp<UXSTACKTOP-PGSIZE){
utf = (struct UTrapframe *)(UXSTACKTOP-sizeof(struct UTrapframe));
//2.递归pgfault
}else{
utf = (struct UTrapframe *)(tf->tf_esp-4-sizeof(struct UTrapframe));
}
log("utf:%08x\n",utf);
//UXSTACKTOP与权限检查,或者overflow
user_mem_assert(curenv,(void *)utf,sizeof(struct UTrapframe),PTE_W|PTE_U);
//开始写入
//struct UTrapframe *utf = (struct UTrapframe *)uxtop;
utf->utf_fault_va = fault_va;
utf->utf_err = tf->tf_err;
utf->utf_regs = tf->tf_regs;
utf->utf_eip = tf->tf_eip;
utf->utf_eflags = tf->tf_eflags;
utf->utf_esp = tf->tf_esp;
//修改esp与eip,跳转至handler处执行
tf->tf_eip = (uintptr_t)curenv->env_pgfault_upcall;
tf->tf_esp = (uint32_t)utf;//.uxtop;
log("Userspace PGFault,va:%08x\n",fault_va);
env_run(curenv);
}else{
cprintf("Userspace PGFault,No Valid Handler:%08x\n",handler);
}
// Destroy the environment that caused the fault.
cprintf("[%08x] user fault va %08x ip %08x\n",
curenv->env_id, fault_va, tf->tf_eip);
print_trapframe(tf);
env_destroy(curenv);
}
复制代码
Exercise 10
_pgfault_upcall
,负责调用用户态缺页处理程序,以及恢复 CPU 状态,这部分内容涉及到栈状态的维护,在进行 push 与 pop 时,要同时对 esp 指针进行更新
汇编语言变量读写示例如下:
# 读-计算-写
movl 0x30(%esp),%eax
subl $4,%eax
movl %eax,0x30(%esp)
复制代码
.text
.globl _pgfault_upcall
_pgfault_upcall:
// Call the C page fault handler.
pushl %esp // function argument: pointer to UTF
movl _pgfault_handler, %eax
call *%eax
addl $4, %esp // pop function argument
// 在内核处理缺页异常返回时,修改了esp与eip的值,需要恢复
//将eip压到用户栈上,先计算得到用户栈esp,0030为相对偏移48个字节的16位表示
movl 0x30(%esp),%eax //读A的esp,prev_ebp
movl 0x28(%esp),%ebx //读A的eip,方便压栈,prev_eip
//记A为用户栈,B为用户态异常栈
//暂存B的esp
movl %esp,%ecx
//切换至栈A
movl %eax,%esp
//压栈eip,修改A的eip
pushl %ebx
//恢复B的esp
movl %ecx,%esp
//修改A的esp
sub $4,%eax
movl %eax,0x30(%esp)
//跳过tf_err,fault_va
addl $8,%esp
popal
//用户态的算术操作可能改变eflags,所以从UTrapFrame中复原,跳过eip
addl $4,%esp
popfl
//恢复栈指针
pop %esp
//恢复eip值,popl %eip
ret
复制代码
Exercise 11
用户态缺页处理,因为缺页处理程序在用户态进行,所以要专门申请用户栈,然后将函数指针赋值给 _pgfault_handler
,以便汇编语言调用
// Assembly language pgfault entrypoint defined in lib/pfentry.S.
extern void _pgfault_upcall(void);
// Pointer to currently installed C-language pgfault handler.
void (*_pgfault_handler)(struct UTrapframe *utf);
void
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
{
int r;
if (_pgfault_handler == 0) {
//申请栈时,需注意起始位置,UXSTACKTOP-PGSIZE,而非UXSTACKTOP
if((r = sys_page_alloc(0,(void *)UXSTACKTOP-PGSIZE,PTE_W|PTE_U|PTE_P))!=0){
panic("Failed to Alloc UX Stack!\n");
}
//可直接传0,表示当前环境
sys_env_set_pgfault_upcall(0,_pgfault_upcall);
}
// Save handler pointer for assembly to call.
_pgfault_handler = handler;
}
复制代码
参考资料
- [leal 指令][blog.csdn.net/farmwang/ar…]
lea传送的是寄存器里面的值,mov传送的是主存中以寄存器的值为地址里面的值。 例如。 leal 18(%eax),%ebx movl 18(%eax) ,%ebx 这样,两条指令,传送的值是不一样的 leal 是传送 18+%eal(值)到 寄存器%ebx movl 是传送的是在主存中以18+%eax为地址的存储序列里面存的值到%ebx
uvpd与uvpt
在进行 Exercise 12 之前, uvpd 与 uvpt 相关的内容是绕不开的一关,刚开始看这部分给人一种无处下手的迷惑,补充完相关内容才明白其中设计的巧妙。
首先是官方的相关资料: pdos.csail.mit.edu/6.828/2018/… UVPD 和 UVPT,按**PDX|PTX|OFFSET **的组织形式分别为: V|V|0, V|0|0 。其中 **V **的值取决于操作系统选择,首先确定 V 的值(In Jos, V is 0x3BD),然后根据 V 的值构造出来 UVPT 与 UVPD 的值,这部分的逻辑是将页目录也看做页表(MMU 无情的查表机器),然后将页目录的起始地址放在页目录的第V项中,这样根据 PDX V 来查表查到的页表就是页目录,在加上 OFFSET 为零,这个地址就代表了页目录的起始地址;
其中查表过程地址计算按如下公式进行,4为 pte 结构的宽度:
UVPD相关图示5:
UVPT相比 UVPD 稍微复杂一点,相关图示6,可以看到 UVPT 可以代表虚拟地址空间中页表一级的首地址,类比于进制(1024进制)与堆索引的计算,这个地方巧妙点在于可以利用由 pdx 与 ptx 组成的 pagenumber 直接访问对应的页表项;这部分的难点在于:尽管虚拟地址空间对应的物理页之间是不连续的,虚拟内存是扁平化且连续的,编程时从虚拟内存角度考虑即可。
JOS 中相关定义如下:
参考资料:
- 6.828 Lab4(上) - 孟永康的文章 - 知乎 zhuanlan.zhihu.com/p/50169125
Exercise 12
Exercise 12 要求完成 fork,duppage 与 pgfault 函数,通过用户态缺页处理机制,完成写时复制的 fork 。
- fork,与之前的 dumbfork 的区别在于,增加了用户态缺页处理程序
envid_t
fork(void)
{
//1.
int r;
set_pgfault_handler(pgfault); //封装函数,会申请用户异常栈
//2.
envid_t envid = sys_exofork();
if(envid<0){
panic("exofork failed");
}
//fix "thisenv" in the child process.
if(envid==0){
thisenv = &envs[ENVX(sys_getenvid())];
return 0;
}
//3.
uintptr_t start =USTACKTOP-PGSIZE; //UTOP
unsigned pn;
for (pn=PGNUM(UTEXT); pn<PGNUM(USTACKTOP); pn++){
if ((uvpd[pn >> 10] & PTE_P) && (uvpt[pn] & PTE_P))
if ((r = duppage(envid, pn)) < 0)
return r;
}
//4.为子env设置缺页处理器
if((r = sys_page_alloc(envid,(void *)UXSTACKTOP-PGSIZE,PTE_W|PTE_U|PTE_P))!=0){
panic("Failed to Alloc UX Stack!\n");
}
//upcall而非handler,封装
extern void _pgfault_upcall(void);
if((r = sys_env_set_pgfault_upcall(envid,_pgfault_upcall))!=0){
panic("Failed to Set PgFault Handler for Child:%08x!\n",envid);
}
//5.
if((r = sys_env_set_status(envid,ENV_RUNNABLE)!=0)){
panic("Failed to Set PgFault Handler for Child:%08x!\n",envid);
}
return envid;
}
复制代码
- duppage,这部分函数负责虚拟地址空间的映射,对于 PTE_W 权限以及 PTE_COW 权限的页,都标记为 PTE_COW 权限
static int
duppage(envid_t envid, unsigned pn)
{
int r;
cprintf("duppage:%08x\n",pn);
pte_t cur_pte = uvpt[pn];
int raw_perm = cur_pte & 0xfff;
//权限部分,可写部分用new_perm,其余使用PTE_U|PTE_P
int new_perm = PTE_COW|PTE_U|PTE_P;
//此处权限已经修改为067,A(Accessed)D(Ditry)位置位,不能使用raw_perm方式进行赋值
uintptr_t va = pn*PGSIZE;
if(cur_pte & PTE_W || cur_pte & PTE_COW){
if((r=sys_page_map(0,(void *)va,envid,(void *)va,new_perm))!=0){
panic("duppage error for child!va:%08x,error:%08x\n",va,r);
}
//mapping self
if((r=sys_page_map(0,(void *)va,0,(void *)va,new_perm))!=0){
panic("duppage error for parent!va:%08x,error:%08x\n",va,r);
}
}else{
if((r=sys_page_map(0,(void *)va,envid,(void *)va,PTE_U|PTE_P))!=0){
panic("duppage error!va:%08x,error:%08x\n",va,r);
}
}
return 0;
}
复制代码
- pgfault,整体流程与图示4 类似
static void
pgfault(struct UTrapframe *utf)
{
void *addr = (void *) utf->utf_fault_va;
uint32_t err = utf->utf_err;
int r;
uintptr_t pdx = PDX(addr);
uint32_t flat_pg_index = PGNUM(addr); //类比于进制
pde_t cur_pde = uvpd[pdx];
pte_t cur_pte = uvpt[flat_pg_index];
int raw_perm = cur_pte & 0xfff;
uint32_t pg_num = (uint32_t)addr/PGSIZE;
uintptr_t round_ptr = pg_num*PGSIZE;
if(!((err & FEC_WR) && (cur_pte & PTE_W || cur_pte & PTE_COW))){
panic("Not a write or copy-on-write page!va:%08x,pn:%d\n",addr,pg_num);
}
// LAB 4: Your code here.
if((r=sys_page_alloc(0,PFTEMP,PTE_W | PTE_U | PTE_P))!=0){
panic("failed to allocate page for copy on write!va:%08x,pn:%d\n",addr,pg_num);
}
//拷贝内容
memcpy((void *)PFTEMP,(void *)round_ptr,PGSIZE);
if((r=sys_page_map(0,(void *)PFTEMP,0,(void *)round_ptr,PTE_W | PTE_U | PTE_P))!=0){
panic("failed to map!va:%08x,pn:%d\n",addr,pg_num);
}
//WARNING:最后unmap掉PFTEMP
if ((r = sys_page_unmap(0, (void *)PFTEMP)) != 0) {
panic("failed to unmap!va:%08x,pn:%d\n",addr,pg_num);
}
}
复制代码
Lab 4C 抢占调度与IPC
时钟中断与抢占,为了避免类似与 user/spin 的用户态环境占用 CPU 时间片,JOS 引入了时钟中断;
External interrupts (i.e., device interrupts) are referred to as IRQs. There are 16 possible IRQs,
Exercise 13
按练习的提示,在 env_alloc 中开启中断请求,增加 IDT 注册项,以及对应的处理程序
![image-20211013151828171](MIT 6.828 Lab4 多处理器与调度管理.assets/image-20211013151828171.png)
//增加IRQ中断处理
SETGATE(idt[IRQ_OFFSET+IRQ_TIMER],0,GD_KT,irq_timer_handler,0);
SETGATE(idt[IRQ_OFFSET+IRQ_KBD],0,GD_KT,irq_kbd_handler,0);
SETGATE(idt[IRQ_OFFSET+IRQ_SERIAL],0,GD_KT,irq_serial_handler,0);
SETGATE(idt[IRQ_OFFSET+IRQ_SPURIOUS],0,GD_KT,irq_spurious_handler,0);
SETGATE(idt[IRQ_OFFSET+IRQ_IDE],0,GD_KT,irq_ide_handler,0);
SETGATE(idt[IRQ_OFFSET+IRQ_ERROR],0,GD_KT,irq_error_handler,0);
复制代码
TRAPHANDLER_NOEC(irq_timer_handler, IRQ_OFFSET+IRQ_TIMER);
TRAPHANDLER_NOEC(irq_kbd_handler, IRQ_OFFSET+IRQ_KBD);
TRAPHANDLER_NOEC(irq_serial_handler, IRQ_OFFSET+IRQ_SERIAL);
TRAPHANDLER_NOEC(irq_spurious_handler, IRQ_OFFSET+IRQ_SPURIOUS);
TRAPHANDLER_NOEC(irq_ide_handler, IRQ_OFFSET+IRQ_IDE);
TRAPHANDLER_NOEC(irq_error_handler,IRQ_OFFSET+IRQ_ERROR);
复制代码
Exercise 14
增加对时钟中断的处理,调用 sched_yield ,进行 envs 的暂存与恢复,调度
case IRQ_OFFSET+IRQ_TIMER:
lapic_eoi();
sched_yield();
return;
复制代码
Exercise 15
进程间通信机制,经过之前的准备,IPC 机制的实现就是水到渠成了,可以通过修改 env 状态(变量)以及 pagemap的方式进行通信
- sys_ipc_recv,设置标志位,将状态设置为:ENV_NOT_RUNNABLE
static int
sys_ipc_recv(void *dstva)
{
// LAB 4: Your code here.
//隐藏信息之,curenv指向当前环境,且处于ENV_RUNNING状态
if((uintptr_t)dstva<UTOP && (uintptr_t)dstva%PGSIZE!=0){
cprintf("[IPC_RECV]dstva:%08x invalid address,not page aligned!\n",dstva);
return -E_INVAL;
}
curenv->env_ipc_recving = 1;
if((uintptr_t)dstva<UTOP){
curenv->env_ipc_dstva = dstva;
}else{
//关于非标准值的处理
curenv->env_ipc_dstva = dstva;
}
curenv->env_status = ENV_NOT_RUNNABLE;
return 0;
}
复制代码
- sys_ipc_try_send,检查权限,传递信息,传送Page(可选)
static int
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
{
// LAB 4: Your code here.
struct Env *target_env;
int res_code;
if((res_code = envid2env(envid,&target_env,0))!=0){
return -E_BAD_ENV;
}
//阻塞状态,其他环境已经赋值
if(!( target_env->env_ipc_recving)){
log("srcva:%08x target doesn't supposed to recv a value!\n",srcva);
return -E_IPC_NOT_RECV;
}
pte_t *src_pte;
void *send_va = srcva;
src_pte = pgdir_walk(curenv->env_pgdir,srcva,0);
if((uintptr_t)srcva<UTOP){
if((uintptr_t)srcva%PGSIZE!=0){
cprintf("srcva:%08x invalid address,not page aligned!\n",srcva);
return -E_INVAL;
}
//检查权限
if((perm & (PTE_U | PTE_P))!=(PTE_U | PTE_P) || (perm & ~PTE_SYSCALL)!=0){
cprintf("srcva:%08x invalid permission!\n",srcva);
return -E_INVAL;
}
if(!(src_pte!=NULL && (*src_pte)&PTE_P)){
cprintf("srcva:%08x not mapped in caller address space!\n",srcva);
return -E_INVAL;
}
//if((perm & PTE_W) && (*src_pte & ~PTE_W)){
//add by Alan 8-31,防止传入UTOP通不过权限检查
if((perm & PTE_W) && (*src_pte & PTE_W) != PTE_W){
cprintf("srcva:%08x can't write to read-only page!\n",srcva);
return -E_INVAL;
}
}else{
log("Addr>UTOP:%08x\n",srcva);
send_va = NULL;
}
//设置值
target_env->env_ipc_recving = 0;
target_env->env_ipc_from = curenv->env_id;
target_env->env_ipc_value = value;
//设置映射
if(target_env->env_ipc_dstva!=NULL && send_va!=NULL){
target_env->env_ipc_perm = perm;
struct PageInfo *pp = pa2page(PTE_ADDR(*src_pte));
//使用page_insert,而非page_map
if((res_code = page_insert(target_env->env_pgdir,pp,target_env->env_ipc_dstva,perm))!=0){
cprintf("srcva:%08x->dstva:%08x sys_page_map error!\n",srcva,target_env->env_ipc_dstva);
return res_code;
}
}else{
target_env->env_ipc_perm = 0;
log("srcva:%08x target doesn't expect to receive a page!\n",srcva);
}
//切记恢复状态
target_env->env_status = ENV_RUNNABLE;
return 0;
}
复制代码
- ipc_send,循环发起系统调用,成功后结束
void
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
{
// LAB 4: Your code here.
int r;
void *send_pg = pg;
if(pg==NULL){
send_pg= (void *)UTOP;
}
do{
r = sys_ipc_try_send(to_env,val,send_pg,perm);
if(r!=0){
if(r!= -E_IPC_NOT_RECV){
panic("failed to send ipc!\n");
}else{
//cprintf("failed to send ipc:%08x\n",r);
}
sys_yield();
}
}while(r!=0);
}
复制代码
- ipc_recv,发起 sys_ipc_recv 系统调用,返回后处理自己接收到的信息
int32_t
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
{
// LAB 4: Your code here.
int r;
void *recv_pg = pg;
if(pg==NULL){
recv_pg = (void *)UTOP;
}
if((r = sys_ipc_recv(recv_pg))!=0){
if(from_env_store!=NULL){
*from_env_store = 0;
}
if(perm_store!=NULL){
*perm_store = 0;
}
return r;
}
if(from_env_store!=NULL){
*from_env_store = thisenv->env_ipc_from;
}
if(perm_store!=NULL){
*perm_store = thisenv->env_ipc_perm;
}
return thisenv->env_ipc_value;
}
复制代码
参考资料:
- [MIT 6.828 Lab04 : Preemptive Multitasking][www.cnblogs.com/cindycindy/…]
易错点
汇编操作栈错误
逻辑处理错误
宏混淆导致错误
权限设置,PTE生成后,权限已经修改为067,A(Accessed)D(Ditry)位置位,不能使用raw_perm方式进行赋值
int raw_perm = cur_pte & 0xfff;
读写权限检查部分
权限部分易错点
page_insert 而非 sys_page_map