Lab 4A SMP and collaborative scheduling

Following the virtual memory of Lab 2 and the user-mode environment of Lab 3, Lab 4 introduced multi-processor, scheduling management and interprocess communication mechanism, which also made JOS more suitable for the actual application scenarios and laid a foundation for the following file system and network adapter driver.

In Lab 3, the key point is the staging of CPU state for recovery, and X86 also provides IDT and TSS for protected control transfer. SMP is introduced in LAB 4. In the case of multi-processors, the protected control transfer mechanism remains unchanged or is completed by each CPU’s own TSS and kernel stack. The changes are as follows:

  • Synchronization mechanism when multiple cores access shared resources
  • In user mode, CPU information needs to be recorded to facilitate scheduling

The content of Lab 4 seems to be complex, but if we grasp the unchanged mechanism of Lab 3, we can see that SMP introduces more communication under distributed condition and synchronization mechanism of shared resources, and the rest content is consistent with Lab 3. The information of single structure before is now represented by array and index.

Lab 3 single-core CPU, Figure 1:

Lab 4 Multi-core CPU, Figure 2:

1. Multi-core processor support and collaborative scheduling

The multi-core processor belongs to the distributed scenario, and each CPU can be regarded as a distributed object. The identification and communication of each CPU can be accomplished through the Advanced Programmable Interrupt Controller (LAPIC). LAPIC is also responsible for distributing interrupts in the system, as shown in Figure 3:

In an SMP system, cpus are divided into two types: Bootstrap Processor (BSP) and Application Processor (AP). Also note that in Lab 4, the implementation of the curenv pointer has been changed and is now implemented in macro mode so that curenv refers to the user-mode program environment of the current processor. Thiscpu is also implemented in macro mode to refer to the CPU information in the cpus array. The relevant information is as follows.

#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
Copy the code

Exercsie 1

APIC contents are accessed by JOS through MMIO (Memory-Mapped I/O). LAPIC register contents can be accessed by reading or writing memory addresses

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 is Hardwired Logic fixed-line logic, and its address space is mapped to memory in the code

void *
mmio_map_region(physaddr_t pa, size_t size)
{
	static uintptr_t base = MMIOBASE;
	// Get the corresponding physical page first
	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;
	// Note that there is no need for the following base, but the base in bytes
	base += round_size;			
	return (void *)prev_base;
}
Copy the code

Exercise 2

Exercise 2 prompts you to read boot_aps, mp_main and kern/mpentry.S to figure out how the AP is initialized. Figure 3:

Add the following code to page_init to skip the MMIO mapping physical page

Exercise 3

Add a map to multiple CPU stacks in a loop where the stack addresses grow downward

static void
mem_init_mp(void)
{
	// Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs.
	// A map of multiple CPU stacks, with addresses growing downward, treated as a vector
	for(uint32_t i=0; i<NCPU; i++){uintptr_t kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
		// Virtual address to physical address
		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]); }}Copy the code

Exercise 4

For the initialization of IDT and TSS for each CPU, see figure 2 above.

// Initialize and load the per-CPU TSS and IDT
void
trap_init_percpu(void)
{
	// Set the TSS property of perCPU, non-cyclic assignment
	uint8_t cur_cpuid= thiscpu->cpu_id;
	// The virtual memory has been allocated
	//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;
	// Base Addr,*(cur_cpuID +1
	thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate);
	// Set entry to the GDT
	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);
}

Copy the code

Exercise 5

In the multi-processor scenario, contention for shared resources is introduced, so shared resources need to be synchronized through the lock provided by JOS. The overall process is to enter the kernel to lock and leave the kernel to unlock

Init. C

The trap. C

The unlock part is unlocked before LCR3 because CR3 and subsequent kernel stacks are cpu-local variable updates, not shared resources

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));
}
Copy the code

The locking mechanism in JOS is implemented through inline assembly

Exercise 6

This part requires us to implement round-robin scheduling. Envs can be divided into three parts by cur_envid to find suitable user-mode environment. Envs can also be regarded as a ring queue

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();
}
Copy the code

Exercise 7

There are a lot of system calls waiting to be implemented in Exercise 7. Two things to note about this section:

  • Interoperations between user-mode environments, such as SYS_PAGe_map, must first pass permission verification, which can be provided by calling JOSenvid2env()To carry out

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.

  • The permission verification of the flag bit is carried out in the following way. Because some permission bits exceed one bit and some permission bits have special meanings of 0, the bit calculation of these permission bits may cause unintended bugs if the conditions are directly judged
if((perm & (PTE_U | PTE_P))! =(PTE_U | PTE_P)){//XXX
}
Copy the code

Schematic diagram of Duppage process, Figure 4:

  • Sys_exofork, copy the parent process’s ENv_TF instead of the simple register contents (env_TF is the complete state), and set the child’s return contents to 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;
	// Copy the register
	//child_store->env_tf.tf_regs = parent->env_tf.tf_regs; // Wrong way
	child_store->env_tf = parent->env_tf;
	// The return value is obtained from env_tf.tf_eax
	child_store->env_tf.tf_regs.reg_eax = 0;
	return child_store->env_id;
}
Copy the code
  • 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;
	}
	// Boolean logic
	if(status! =ENV_RUNNABLE && status! =ENV_NOT_RUNNABLE){return -E_INVAL;
	}
	target_env->env_status = status;
	return 0;
}
Copy the code
  • Sys_page_alloc, advanced parameter verification, applies for a page and inserts it into the address space of the target 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;
	}
	// Check permissions
	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;
	}
	// Here is the page table address of the target 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;
}
Copy the code
  • Sys_page_map, table lookup, secondary mapping
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;
	}
	// Check if there is a physical memory map
	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;
	}
	// Check permissions
	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;
	}
	// Read/write permission check, consider 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;
}
Copy the code
  • Sys_page_unmap, delete the mapping
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;
	}
	// Check if there is a physical memory map
	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;
}
Copy the code

Lab 4B COW Fork

Cow part makes clever use of the page missing interrupt mechanism of the operating system, and provides a user-mode page missing exception processor to carry out flexible processing. Figure 5 is as follows, with the common interrupt handler on the left and the user-mode page missing handler on the right. It can be seen that the main contents are stack related.

The function call mechanism is shown below, The RET works The same as pop %eip.

Exercise 8

Sys_env_set_pgfault_upcall, function pointer assignment

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;
}
Copy the code

Exercise 9

Page_fault_handler, contact Lab 3. This section is still about the temporary storage and restore of CPU state. This is just a bit of an extra transfer to 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();
	// Select a code segment for cs in protected mode
	uint16_t code_segment_selector= tf->tf_cs;
	if(debug){
		cprintf("code_segment_selector:%08x,fault_va:%08x\n",code_segment_selector,fault_va);
	}
	// In DPL, 0 is kernel mode, and 3 is user mode
	if((tf->tf_cs & 0x3) = =0){
		print_trapframe(tf);
		panic("Page Fault Occured in Kernel Mode");
	}
	// User-level page missing processing
	void *handler = curenv->env_pgfault_upcall;
	//uintptr_t uxtop;
	if(handler! =NULL) {struct UTrapframe *utf;
		//1. When the user is in pgFAULT state for the first time, the macro is confused before and the error is difficult to DEBUG
		if(tf->tf_esp>=UXSTACKTOP || tf->tf_esp<UXSTACKTOP-PGSIZE){
			utf = (struct UTrapframe *)(UXSTACKTOP-sizeof(struct UTrapframe));
		/ / 2. The recursive pgfault
		}else{
			utf = (struct UTrapframe *)(tf->tf_esp4 --sizeof(struct UTrapframe));
		}
		log("utf:%08x\n",utf);
		//UXSTACKTOP with permission check, or overflow
		user_mem_assert(curenv,(void *)utf,sizeof(struct UTrapframe),PTE_W|PTE_U);
		// Start writing
		//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;
		// To modify esp and EIP, switch to 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);
}
Copy the code

Exercise 10

_pgFAULt_upCall, which is responsible for calling the user-mode page-missing handler and restoring the CPU state. This part involves the maintenance of the stack state and updating the ESP pointer during push and POP

The following is an example of reading and writing assembly language variables:

Movl 0x30(%esp),% eAX subl $4,% eAX movl %eax,0x30(%esp)Copy the code
.text .globl _pgfault_upcall _pgfault_upcall: // Call the C page fault handler. pushl %esp // function argument: // pop function argument; // pointer to UTF movL _pgFAULt_handler, %eax Call *%eax addL $4, %esp Movl 0x30(% ESP),%eax // read A esp, Prev_ebp movL 0x28(%esp),%ebx prev_eBP movL 0x28(%esp),%ebx // switch to A movl % eAX,% ESP // push eIP, Change A's ESP sub $4,% eAX MOVl % eAX,0x30(% ESP) // skip tF_Err, fault_VA addL $8,%esp Popal // User mode arithmetic operation may change eFLAGS, so restore from UTrapFrame, skip eIP addl $4,%esp popFL // restore stack pointer pop %esp // restore eIP value, popL %eip retCopy the code

Exercise 11

User-mode missing page handling, because the missing page handler is in user-mode, we apply for the user stack specifically, and then assign the function pointer to _PGFAULt_handler for assembly language invocation

// 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) {
		// When applying a stack, pay attention to the starting position, UXSTACKTOP -pgsize, not UXSTACKTOP
		if((r = sys_page_alloc(0, (void*)UXSTACKTOP-PGSIZE,PTE_W|PTE_U|PTE_P))! =0){
			panic("Failed to Alloc UX Stack! \n");
		}
		// You can directly pass 0 to indicate the current environment
		sys_env_set_pgfault_upcall(0,_pgfault_upcall);
	}
	// Save handler pointer for assembly to call.
	_pgfault_handler = handler;
}
Copy the code

The resources

  • [leal directive] [blog.csdn.net/farmwang/ar…].

Lea transfers register values, MOV transfers register values in main memory. For example. Leal 18(% eAX),% ebX MOVL 18(% eAX),%ebx Leal transfers 18+%eal (value) to register %ebx MOVL transfers the value stored in the 18+%eax storage sequence in main memory to %ebx

Uvpd and uvpt

Before Exercise 12, the content related to UVPD and UVPT was an inextricable difficulty. At the beginning, people felt confused when they read this part, but only after supplementing relevant content did they understand the ingenious design.

The first is the official information: pdos.csail.mit.edu/6.828/2018/… UVPD and UVPT, press * * symbol | PTX | OFFSET the organization form of * *, respectively: | V | 0 V, V | | 0 0. In Jos, V is 0x3BD, V is 0x3BD, V is 0x3BD, V is 0x3BD, V is 0x3BD, V is 0x3BD. Then put the start address of the page directory in the V item of the page directory, so that according to PDX V lookup table to find the page table is the page directory, add OFFSET is zero, this address represents the start address of the page directory;

Where address calculation of table lookup process is carried out according to the following formula, 4 is the width of PTE structure:


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 correlation Diagram 5:

UVPT is a bit more complex than UVPD, as shown in figure 6. UVPT can represent the head address of the page table level in the virtual address space, analogous to the calculation of base (1024) and heap indexes. The clever point here is that you can use pagenumber composed of PDX and PTX to directly access the corresponding page table entries; The difficulty of this part lies in that although the physical pages corresponding to the virtual address space are discontinuous, the virtual memory is flat and continuous, and the programming can be considered from the perspective of virtual memory.

The definitions in JOS are as follows:

References:

  • 6.828 Lab4 (on) – Meng Yongkang articles – zhihu zhuanlan.zhihu.com/p/50169125

Exercise 12

Exercise 12 requires the completion of fork, Duppage, and PGFault functions, with user-mode missing page handling to complete the fork copied at write time.

  • Fork, unlike its predecessor, dumbfork, adds a user-mode page-missing handler
envid_t
fork(void)
{
	/ / 1.
	int r;
	set_pgfault_handler(pgfault);			// Encapsulate the function, which will request the user exception stack
	/ / 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. Set the missing page handler for the child 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 instead of 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;
}
Copy the code
  • Duppage, which maps the virtual address space, is marked as PTE_COW for pages with PTE_W permission and PTE_COW permission
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;
	/ / permission to part, can be used to write some new_perm, the remaining use PTE_U | PTE_P
	int new_perm = PTE_COW|PTE_U|PTE_P;
	Ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear
	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;
}
Copy the code
  • Pgfault, the overall process is similar to figure 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);	// Analogies to base
	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);
	}
	// Copy the content
	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 removes PFTEMP at last
	if ((r = sys_page_unmap(0, (void*)PFTEMP)) ! =0) {
        panic("failed to unmap! va:%08x,pn:%d\n",addr,pg_num); }}Copy the code

Lab 4C preemption scheduling with IPC

Clock interrupts and preemption. JOS introduces clock interrupts to avoid CPU time slices in user-mode environments such as user/ SPIN.

External interrupts (i.e., device interrupts) are referred to as IRQs. There are 16 possible IRQs,

Exercise 13

Turn on interrupt requests in env_alloc, add the IDT registry, and the corresponding handler, as prompted in the exercise

! [image-20211013151828171](MIT 6.828 Lab4 Multi-processor and Scheduling Management. Assets /image-20211013151828171.png)

	// Add IRQ interrupt handling
	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);
Copy the code
	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);
Copy the code

Exercise 14

Added handling of clock interrupts with sched_yield, staging and recovery of ENVS, and scheduling

		case IRQ_OFFSET+IRQ_TIMER:
			lapic_eoi();
			sched_yield();
			return;
Copy the code

Exercise 15

Interprocess communication mechanism, after the preparation, the implementation of IPC mechanism is natural, can change the env state (variable) and pagemap to communicate

  • Sys_ipc_recv, set the flag bit and set the status to ENV_NOT_RUNNABLE
static int
sys_ipc_recv(void *dstva)
{
	// LAB 4: Your code here.
	// To hide information, curenv points to the current environment and is in ENV_RUNNING state
	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{
		// Handle nonstandard values
		curenv->env_ipc_dstva = dstva;
	}
	curenv->env_status = ENV_NOT_RUNNABLE;
	return 0;
}
Copy the code
  • Sys_ipc_try_send, check permissions, send information, send Page (optional)
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;
	}
	// Block state, other environment already assigned value
	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;
		}
		// Check permissions
		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 to prevent incoming UTOP from passing the permission check
		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;
	}
	/ / set the value
	target_env->env_ipc_recving = 0;
	target_env->env_ipc_from = curenv->env_id;
	target_env->env_ipc_value = value;
	// Set the mapping
	if(target_env->env_ipc_dstva! =NULL&& send_va! =NULL){
		target_env->env_ipc_perm = perm;
		struct PageInfo *pp = pa2page(PTE_ADDR(*src_pte));
        // Use page_insert instead of 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);
			returnres_code; }}else{
		target_env->env_ipc_perm = 0;
		log("srcva:%08x target doesn't expect to receive a page! \n",srcva);
	}
	// Remember to restore the state
	target_env->env_status = ENV_RUNNABLE;
	return 0;
}
Copy the code
  • Ipc_send, the loop initiates a system call and ends when the call succeeds
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);
}
Copy the code
  • Ipc_recv, launches the sys_IPC_RECv system call and returns to process the information it received
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;
}
Copy the code

References:

  • [MIT Lab04 6.828: Preemptive Multitasking] [www.cnblogs.com/cindycindy/]…

Easy wrong points

Assembly operation stack error

Logical processing error

Macro confusion leads to an error

Ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear, ear and ear

int raw_perm = cur_pte & 0xfff;

Check the read and write permissions

The permission part is prone to error

Page_insert rather than sys_page_map