【MIT 6.828 Lab4】多处理器,COW与IPC

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:

image-20211012145043381

lab 4 多核CPU,图示2:

image-20211013092638222

1. 多核处理器支持与协作调度

多核处理器属于分布式场景,可以将各个 CPU 看做分布式对象,各 CPU 的标识与通信通过高级可编程中断控制器 LAPIC (Advanced Programmable Interrupt Controller)来完成,LAPIC 也负责在系统中分发中断,图示3:

kvm中断虚拟化| 码农家园

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:

image-20211013114828648

在 page_init 中增加如下代码,跳过 MMIO 映射物理页

image-20211011123453048

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 中

image-20211013100747228

image-20211013100757620

trap.c 中

image-20211013100814217

解锁部分,之所以在 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 中的锁机制通过内联汇编来实现

image-20210817121002573

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() in kern/env.c.

  • 标志位的权限校验通过如下方式进行,因为有些权限标志超过一位,部分权限位中 0 也有特殊含义,对这些权限位的位运算如果直接判断条件,可能导致无意的 bug
if ((perm & (PTE_U | PTE_P))!=(PTE_U | PTE_P)){
    //XXX
}
复制代码

duppage 的流程示意,图示4:

image-20211013110129529

  • 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如下,左侧是普通的中断处理程序,右侧是用户态缺页处理程序,可以看到主要还是栈相关的内容。

image-20210824160933717

函数调用机制如下图所示,The ret works the same as pop %eip.

call-ret.jpg

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 中恢复。

image-20210823161917829

image-20210823165921445

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;
}
复制代码

参考资料

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|0V|0|0 。其中 **V **的值取决于操作系统选择,首先确定 V 的值(In Jos, V is 0x3BD),然后根据 V 的值构造出来 UVPT 与 UVPD 的值,这部分的逻辑是将页目录也看做页表(MMU 无情的查表机器),然后将页目录的起始地址放在页目录的第V项中,这样根据 PDX V 来查表查到的页表就是页目录,在加上 OFFSET 为零,这个地址就代表了页目录的起始地址;

其中查表过程地址计算按如下公式进行,4为 pte 结构的宽度:

p d = l c r 3 ( ) ; p t = ( p d + 4 P D X ) ; p a g e = ( p t + 4 P T X ) ; pd = lcr3(); pt = *(pd+4*PDX); page = *(pt+4*PTX);

UVPD相关图示5:

image-20211013143626825

UVPT相比 UVPD 稍微复杂一点,相关图示6,可以看到 UVPT 可以代表虚拟地址空间中页表一级的首地址,类比于进制(1024进制)与堆索引的计算,这个地方巧妙点在于可以利用由 pdx 与 ptx 组成的 pagenumber 直接访问对应的页表项;这部分的难点在于:尽管虚拟地址空间对应的物理页之间是不连续的,虚拟内存是扁平化且连续的,编程时从虚拟内存角度考虑即可。

image-20210901144401371

JOS 中相关定义如下:

image-20210824165907713

image-20210824165738861

参考资料:

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;
}
复制代码

参考资料:

易错点

汇编操作栈错误

image-20210824142543042

逻辑处理错误

image-20210824092321405

宏混淆导致错误

image-20210824154924129

权限设置,PTE生成后,权限已经修改为067,A(Accessed)D(Ditry)位置位,不能使用raw_perm方式进行赋值

int raw_perm = cur_pte & 0xfff;

image-20210825164658274

读写权限检查部分

image-20210825182117126

权限部分易错点

image-20210826123332921

page_insert 而非 sys_page_map

image-20210826130050284

猜你喜欢

转载自juejin.im/post/7018454082301984781