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
posted @ 2023-08-16 21:34  chumoath  阅读(8)  评论(0)    收藏  举报