MARK_USE(0); // --- 添加的内容 --- for (i = 1; i < MPENTRY_PADDR / PGSIZE; ++i) MARK_FREE(i); MARK_USE(i++); // ----------------- for (; i < npages_basemem; ++i) MARK_FREE(i); for (; i < EXTPHYSMEM / PGSIZE; ++i) MARK_USE(i); for (; i < bss_end / PGSIZE; ++i) MARK_USE(i); for (; i < boot_alloc_end / PGSIZE; ++i) MARK_USE(i); for (; i < npages; ++i) MARK_FREE(i);
#undef MARK_USE #undef MARK_FREE }
Q: Compare kern/mpentry.S side by side with boot/boot.S. Bearing in mind that kern/mpentry.S is compiled and linked to run above KERNBASE just like everything else in the kernel, what is the purpose of macro MPBOOTPHYS? Why is it necessary in kern/mpentry.S but not in boot/boot.S? In other words, what could go wrong if it were omitted in kern/mpentry.S? Hint: recall the differences between the link address and the load address that we have discussed in Lab 1.
// Acquire the big kernel lock before waking up APs // Your code here: lock_kernel();
// Starting non-boot CPUs boot_aps();
在kern/init.c的mp_main中加锁:
1 2 3 4 5 6 7 8
// Now that we have finished some basic setup, call sched_yield() // to start running processes on this CPU. But make sure that // only one CPU can enter the scheduler at a time! // // Your code here: lock_kernel();
sched_yield();
在kern/trap.c的trap中加锁:
1 2 3 4 5 6
if ((tf->tf_cs & 3) == 3) { // Trapped from user mode. // Acquire the big kernel lock before doing any // serious kernel work. // LAB 4: Your code here. lock_kernel();
在kern/env.c的env_run中解锁:
1 2
unlock_kernel(); env_pop_tf(&e->env_tf);
Q: It seems that using the big kernel lock guarantees that only one CPU can run the kernel code at a time. Why do we still need separate kernel stacks for each CPU? Describe a scenario in which using a shared kernel stack will go wrong, even with the protection of the big kernel lock.
idle = NULL; if (curenv) { size_t eidx = ENVX(curenv->env_id); uint32_t mask = NENV - 1; for (size_t i = (eidx + 1) & mask; i != eidx; i = (i + 1) & mask) { if (envs[i].env_status == ENV_RUNNABLE) { idle = &envs[i]; break; } } if (!idle && curenv->env_status == ENV_RUNNING) idle = curenv; } else { for (size_t i = 0; i < NENV; ++i) { if (envs[i].env_status == ENV_RUNNABLE) { idle = &envs[i]; break; } } } if (idle) env_run(idle); // sched_halt never returns sched_halt(); }
对syscall等的修改比较简单,就不列出了
Q: In your implementation of env_run() you should have called lcr3(). Before and after the call to lcr3(), your code makes references (at least it should) to the variable e, the argument to env_run. Upon loading the %cr3 register, the addressing context used by the MMU is instantly changed. But a virtual address (namely e) has meaning relative to a given address context–the address context specifies the physical address to which the virtual address maps. Why can the pointer e be dereferenced both before and after the addressing switch?
A: 因为所有进程的地址空间在内核部分的映射都是一样的。
Q: Whenever the kernel switches from one environment to another, it must ensure the old environment’s registers are saved so they can be restored properly later. Why? Where does this happen?
// Copy trap frame (which is currently on the stack) // into 'curenv->env_tf', so that running the environment // will restart at the trap point. curenv->env_tf = *tf; // The trapframe on the stack should be ignored from here on. tf = &curenv->env_tf;
Exercise 7
sys_exofork
按要求实现即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
staticenvid_t sys_exofork(void) { int r; structEnv *parent, *child;
parent = curenv; if (r= env_alloc(&child, parent->env_id), r < 0) return r;
bad: // 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); }
// Restore the trap-time registers. After you do this, you // can no longer modify any general-purpose registers. // LAB 4: Your code here. addl $8, %esp popal
// Restore eflags from the stack. After you do this, you can // no longer use arithmetic operations or anything else that // modifies eflags. // LAB 4: Your code here. addl $4, %esp popf
// Switch back to the adjusted trap-time stack. // LAB 4: Your code here. movl (%esp), %esp
// Return to re-execute the instruction that faulted. // LAB 4: Your code here. ret
// Check that the faulting access was (1) a write, and (2) to a // copy-on-write page. If not, panic. // Hint: // Use the read-only page table mappings at uvpt // (see <inc/memlayout.h>).
// LAB 4: Your code here. addr = ROUNDDOWN(addr, PGSIZE);
// Allocate a new page, map it at a temporary location (PFTEMP), // copy the data from the old page to the new page, then move the new // page to the old page's address. // Hint: // You should make three system calls.
// LAB 4: Your code here. if (r = sys_page_alloc(0, PFTEMP, PTE_P | PTE_U | PTE_W), r < 0) PANIC; memmove(PFTEMP, addr, PGSIZE); if (r = sys_page_map(0, PFTEMP, 0, addr, PTE_P | PTE_U | PTE_W), r < 0) PANIC; if (r = sys_page_unmap(0, PFTEMP), r < 0) PANIC; return;
#undef PANIC }
Exercise 13
对我来说只需要在kern/trapentry.inc里添加几项即可:
1 2 3 4 5 6
TH(32) // TIMER TH(33) // KBD TH(36) // SERIAL TH(39) // SPURIOUS TH(46) // IDE TH(51) // ERROR
在kern/env.c的env_alloc里加入:
1 2 3
// Enable interrupts while in user mode. // LAB 4: Your code here. e->env_tf.tf_eflags |= FL_IF;
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store) { // LAB 4: Your code here. // panic("ipc_recv not implemented"); int r; envid_t feid; int perm;
if (r = sys_ipc_recv(pg ? ROUNDDOWN(pg, PGSIZE) : (void*)UTOP), r < 0) { feid = 0; perm = 0; } else { feid = thisenv->env_ipc_from; perm = thisenv->env_ipc_perm; } if (from_env_store) *from_env_store = feid; if (perm_store) *perm_store = perm; return r < 0 ? r : thisenv->env_ipc_value; }
void ipc_send(envid_t to_env, uint32_t val, void *pg, int perm) { // LAB 4: Your code here. // panic("ipc_send not implemented"); int r;
faultread: OK (0.8s) faultwrite: OK (1.2s) faultdie: OK (1.6s) faultregs: OK (2.2s) faultalloc: OK (1.0s) faultallocbad: OK (1.7s) faultnostack: OK (2.2s) faultbadhandler: OK (2.1s) faultevilhandler: OK (1.9s) forktree: OK (1.9s) Part B score: 50/50
spin: OK (2.1s) stresssched: OK (2.3s) sendpage: OK (1.8s) pingpong: OK (1.8s) primes: OK (3.2s) Part C score: 25/25
faultread: OK (0.9s) faultwrite: OK (1.1s) faultdie: OK (1.7s) faultregs: OK (1.5s) faultalloc: OK (1.4s) faultallocbad: OK (1.4s) faultnostack: OK (1.7s) faultbadhandler: OK (2.0s) faultevilhandler: OK (2.3s) forktree: OK (1.6s) Part B score: 50/50
spin: OK (2.9s) stresssched: OK (1.6s) sendpage: OK (1.7s) pingpong: OK (2.1s) primes: OK (3.0s) Part C score: 25/25
#ifdef CONF_MFQ ////////////////////////////////////////////////////////////////////// // Make 'mfqs' point to an array of size 'NMFQ' of 'EmbedLink'. mfqs = (EmbedLink* ) boot_alloc(NMFQ * sizeof(EmbedLink)); memset(mfqs, 0, NMFQ * sizeof(EmbedLink)); for (int i = 0 ; i < NMFQ; ++i) elink_init(&mfqs[i]); #endif
[00000000] new env 00001000 [00001000] new env 00001001 recv dmail 0 at time(0:0) recv dmail 1 at time(0:0) recv dmail 2 at time(0:0) recv dmail 3 at time(0:0) recv dmail 4 at time(0:0) recv dmail 5 at time(0:0) recv dmail 6 at time(0:0) recv dmail 7 at time(0:0) recv dmail 8 at time(0:0) recv dmail 9 at time(0:0) [00001000] exiting gracefully [00001000] free env 00001000 [00001000] free env 00001001 No runnable environments in the system!
EmbedLink env_spst_link; // Embeded link to the snapshot list envid_t env_spst_owner_id; // snapshot's owner env's id envid_t env_spst_id; // rollbacked snapshot's id uint32_t env_spst_dmail; // DeLorean-Mail(这一切都是命运石之门的选择!) #define EMPTY_DMAIL ~0U
if (r = syscall(SYS_env_snapshot, 0, 0, 0, 0, 0, 0), r < 0) PANIC; if (dmail_store) *dmail_store = thisenv->env_spst_dmail; return r;
#undef PANIC }
lib/syscall.c
实现sys_env_rollback的用户态接口:
1 2 3 4 5 6 7 8 9 10
int sys_env_rollback(envid_t eid, uint32_t dmail) { if (dmail == EMPTY_DMAIL) return -E_INVAL; int r = syscall(SYS_env_rollback, 1, (uint32_t)eid, dmail, 0, 0, 0); if (r == 0) panic("rollback should never return"); return r; }