6.S081总结
cpu struct
-----------------------------
| satp mmu cpu1 |-----------
----------------------------- |
|
----------------------------- |
| satp mmu cpu2 | pa -> RAM
----------------------------- |
|
----------------------------- |
| satp mmu cpu3 |-----------
-----------------------------
mem layout
user pagetable
trampoline (last page, be mapped)
trapframe (one page, be mapped)
proc->sz (here)
heap
stack (actually allocated)
guard page (PTE_U is clear, but is still allocated)
text + data (actually allocated)
kernel
- each cpu has one scheduler
- trampoline (last page)
- scheduler's stack
- scheduler's guard page
- kstack1
- guard
- kstack2
- guard
- ...
- kstackn
- guard
- PHYSTOP <- the end of physical address
- ...
- <- etext
- kernel text and data
- <- KERNBASE (0x80000000)
- other mapping
pagetable
va
satp is the first pagetable's pa, use 9 bit as index
--------------------------------
| 9 | 9 | 9 | 12 |
--------------------------------
pte
pte to pa, pte >> 10, then << 12
---------------------------------
| 54 | 10 |
---------------------------------
- one pte is 8 B, PAGESIZE = 4KB, so one page can put 512 pte
syscall
-
fork => alloc one process, copy the parent's pagetable
-
exec => change cur process's pagetable, free previous pagetable, and construct new one
-
exit => close all fd, and find new parent
-
wait => freeproc, wait for one child, and actually mem from 0 to p->sz
-
=> including text, data, stack, heap
trap
-
=> allocproc(), will set context.ra -> forkret -
=> context: where one process or scheduler store its context (kernel) -
=> it is used to schedule, to switch context -
=> trapframe: store one user process's all register after trap -
=> and after trap finish, it will restore user process according it -
=> for first process, set its trapframe.epc = 0, it will be execute from 0 -
=> for fork's process, copy parent's trapframe, and modify trapframe->a0 to 0 -
=> trampoline -
=> uservec, in usertrapret, set stvec to uservec -
=> in user mode, ecall, device intr, exception will store pc to epc, and set pc to uservec -
=> store all register, and switch pagetable to kernel pagetable, change to kstack, invoke usertrap -
=> userret set stvec to kernelvec, because in kernel mode, device intr, exception no need to change to kernel pagetable (satp), no need to switch satp and change to kstack -
=> usertrap handle device intr, syscall, exception -
=> kerneltrap handle device intr, syscall
backtrace
-
one process has one page as stack, and follow this page, it is a guard page (PTE_U is clear)
-
s0 / fp
-
layout
- return address <- align page (s0 1)
- previous.s0
- stored register
- local varibales
- return address (s0 2)
- previous.s0 <- point to previous stack pointer => s0 1
- stored register
- local variables
- ...
- return address <- cur s0 point to here (s0 n)
- previous.s0 <- point to previous stack pointer
- stored register
- local variables
multithread
-
tp => store cur cpu's hartid
-
data struct
struct thread { char stack[NSTACKSIZE]; struct context context; int status; }; enum status { RUNNING, RUNNABLE, FREE }; -
thread_init
-
thread_create((void )(fn)(void *))
-
=> set the new thread's context->ra to fn -
=> set the new thread's context->sp to its stack -
=> set status to RUNNABLE -
thread_yield
-
=> set status to RUNNABLE -
=> invoke thread_schedule -
thread_schedule
-
=> find one thread to switch, and set its status to RUNNING -
thread_switch
-
=> switch ra and sp, ra return to where thread_switch is invoked, sp is the new thread's stack -
=> only store the callee's register, because caller's register will be stored at stack by C -
main thread no use the thread's stack, it use process's stack, but must use the thread's context
lock
-
the sequence for acquire lock
-
the lock strategy only is used to sync, but it is not its purpose
-
the main purpose should be reduce race condition -
main idea
-
split all request to different lock, by bucket(hash table) or pre cpu one -
bcache kalloc -
prinple
-
only acquire one lock when necessary -
no need to use it, directly release -
condition variable
// 1.
acquire(&s->lock);
while (s.sem == 0) {
// <- if wakeup here, will lose wakeup, sem != 0, but this process still sleep
sleep(&s->lock);
}
// use data
release(&s->lock);
the reason for use while
- because maybe wakeup all process, only one process can succeed, other process will acquire lock in sleep, and continue to sleep
// 2. barrier
acquire(&s->lock);
if (s.count != num_thread) {
sleep(&s->lock);
} else {
s.bound++;
s.count = 0;
broad();
}
release(&s->lock);
// sleep
1. acquire(proc->lock); // acquire process's lock, to maintain invariant
// meanwhile, can assure that this process sleep before another process invoke wakeup, can avoid lose the wakeup
2. release(&s->lock); // release this lock
3. set status to SLEEPING // sleep
4. yield()
5. acquire(&s->lock); // why first acquire this lock
6. release(proc->lock);
// wakeup
1. acquire(proc->lock);
2. wakeup
3. release(proc->lock);
cow / lazy allocation
-
use page-fault and pagetable
-
exception 0xd 0xf => page-fault
-
no valid or no previledge -
cow use the remain's bit in page-pte, and sign as PTE_COW
-
and set refcnt in every physical page -
kalloc, set this page's refcnt is 1 -
uvmcopy, increase the refcnt of this page (in fork) -
kfree, decrease the refcnt of this page, if refcnt is 1, free actually -
in this way, it can process recursion, child's child -
exception handler:
-
scause => page-fault exception -
stval => the va that raise this exception -
1. walk find this page's pte -
pid => between fork and exec, the proc is the same as the parent
LRU (the least recently use)
- core double-linked

浙公网安备 33010602011771号