iT邦幫忙

0

[6.1810][code] xv6 的 Processes (五)

  • 分享至 

  • xImage
  •  

系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念

大綱

  • kernel/proc.c/procinit
  • kernel/proc.c/proc_mapstacks
  • kernel/proc.c/allocpid
  • kernel/proc.c/allocproc
  • kernel/proc.c/freeproc
  • kernel/proc.c/proc_pagetable
  • kernel/proc.c/proc_freepagetable
  • kernel/proc.c/userinit
  • kernel/proc.c/growproc

kernel/proc.c/procinit

// initialize the proc table.
void
procinit(void)
{
  struct proc *p;
  
  initlock(&pid_lock, "nextpid");
  initlock(&wait_lock, "wait_lock");
  for(p = proc; p < &proc[NPROC]; p++) {
      initlock(&p->lock, "proc");
      p->state = UNUSED;
      p->kstack = KSTACK((int) (p - proc));
  }
}
  • cpuid == 0 的 cpu 會在開機時呼叫這個 function
  • 初始化 global-process-table,一開始每個 process 的 state 都是 UNUSED,並且會為每個 process 配置自己的 kernel stack


kernel/proc.c/proc_mapstacks

// Allocate a page for each process's kernel stack.
// Map it high in memory, followed by an invalid
// guard page.
void
proc_mapstacks(pagetable_t kpgtbl)
{
  struct proc *p;
  
  for(p = proc; p < &proc[NPROC]; p++) {
    char *pa = kalloc();
    if(pa == 0)
      panic("kalloc");
    uint64 va = KSTACK((int) (p - proc));
    kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W);
  }
}
  • 這個 function 並不是為了 user-process 而設計的。
  • proc_mapstacks 在 xv6-riscv 裡面,會為每一個 process 的 kernel stack 用 kalloc 配置實體 RAM,並且會為 root-kernel-page-table 配置每一個 kernel process 的 kernel stack 的 pa <-> va 映射。


kernel/proc.c/allocpid

int
allocpid()
{
  int pid;
  
  acquire(&pid_lock);
  pid = nextpid;
  nextpid = nextpid + 1;
  release(&pid_lock);

  return pid;
}
  • 配置一個新的 process ID ( PID )
  • 這邊可以看到,每一個 PID 都是循序分配的。
  • 有趣的是,在極端情況下 ( e.g. integer overflow ),還是會有兩個 process 拿到相同 PID 的時候。


kernel/proc.c/allocproc

// Look in the process table for an UNUSED proc.
// If found, initialize state required to run in the kernel,
// and return with p->lock held.
// If there are no free procs, or a memory allocation fails, return 0.
static struct proc*
allocproc(void)
{
  struct proc *p;
  • this function
    • 去 global-process-table 裡面去尋找有沒有 UNUSED 的 struct-proc。假如有找到的話,就會該 struct-proc 進行初始化 ( allocates a PID, a trapframe page, a trampoline page, an empty page table )。
  • return value
    • 失敗 : 0
    • 成功 : 回傳新 allocate 出來的 struct-proc 指標。重要的是,這邊回傳的 struct-proc,是已經取得 struct-proc->lock 的狀態! 呼叫這個 function 的 caller,有義務要解這個鎖

  for(p = proc; p < &proc[NPROC]; p++) {
    acquire(&p->lock);
    if(p->state == UNUSED) {
      goto found;
    } else {
      release(&p->lock);
    }
  }
  return 0;

遍尋 global-process-table,看有沒有 UNUSED 的 struct-proc。


found:
  p->pid = allocpid();
  p->state = USED;

  // Allocate a trapframe page.
  if((p->trapframe = (struct trapframe *)kalloc()) == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }
  • p->pid = allocpid();
    • 配置一個 PID 給這個 process。
  • p->state = USED;
    • 標示為 USED,免得這個 process 又在被 allocate 一次
    • 此時還不會被 CPU-scheduler-thread 挑選,因為還不是 RUNNABLE
  • p->trapframe = (struct trapframe *)kalloc()
    • 實際配置一塊 RAM 給這個 process 的 trapframe
  • freeproc(p);
    • 假如 kalloc 失敗 ( 很有可能是記憶體不足 ),就需要呼叫這個 function,再把這個 struct-proc 給釋放掉,並且將該 struct-proc 標示回 UNUSED

  // An empty user page table.
  p->pagetable = proc_pagetable(p);
  if(p->pagetable == 0){
    freeproc(p);
    release(&p->lock);
    return 0;
  }

為這個新的 process 配置一塊新的 pagetable。


  // Set up new context to start executing at forkret,
  // which returns to user space.
  memset(&p->context, 0, sizeof(p->context));
  p->context.ra = (uint64)forkret;
  p->context.sp = p->kstack + PGSIZE;

  return p;
}
  • memset(&p->context, 0, sizeof(p->context));
    • 把新的 process 的 context 清空
  • p->context.ra = (uint64)forkret;
    • 當這個新的 process 誕生以來第一次被 CPU-scheduler-process 挑選到的話,就會去執行 forkret。
    • 跟 trapframe 可能會搞混 …
      • context : 用來做 context-switch 而保存的資訊
      • trapframe : 用來做 trap 而保存的資訊。在 kfork 的時候,會設定的是 trapframe 的資訊 ( e.g. trapframe->a0 指向 argc, trapframe->a1 指向 argv )。而這裡設定的是 context.ra。
      • context.ra : 設定 context.ra 決定的是該 process 第一次被 CPU-scheduler-thread 挑選的時候,在 kernel space 會跳去的 PC ( program counter ) 值。
      • trapframe.epc : 設定 trapframe.epc 決定的是該 process 第一次從 kernel-space 跳到 user-space 的時候的 PC ( program counter ) 值。


kernel/proc.c/freeproc

// free a proc structure and the data hanging from it,
// including user pages.
// p->lock must be held.
static void
freeproc(struct proc *p)
{
  • this function
    • deallocate 一個 struct-proc 所持有的資源 ( e.g. trapframe, user-page-table … 等等 )
    • 把 struct-proc 內部的資料清空 ( e.g. PID )
    • 讓這個 struct-proc 可以再被 allocate
    • 在使用這個 function 之前,需要先取得該 struct-proc->lock,這個 function 也不會去 release struct-proc->lock,caller 要記得去 release。
  • (arg) struct proc *p
    • 即將被 deallocate 的 struct-proc

  if(p->trapframe)
    kfree((void*)p->trapframe);
  p->trapframe = 0;

假如 trapframe 存在,則 free 掉 trapfame。


  if(p->pagetable)
    proc_freepagetable(p->pagetable, p->sz);
  p->pagetable = 0;

假如 pagetable 存在,則 free 掉 pagetable。


  p->sz = 0;
  p->pid = 0;
  p->parent = 0;
  p->name[0] = 0;
  p->chan = 0;
  p->killed = 0;
  p->xstate = 0;
  p->state = UNUSED;
}

reset 所有相關的 struc-proc 的 field。



kernel/proc.c/proc_pagetable

// Create a user page table for a given process, with no user memory,
// but with trampoline and trapframe pages.
pagetable_t
proc_pagetable(struct proc *p)
{
  pagetable_t pagetable;
  • this function
    • 會為特定的 struct-proc ( process ) 建立一個新的 pagetable。
      • 會配置該 process 的 trampoline
      • 會配置該 process 的 trapframe
  • (arg) struct proc *p
    • 因為需要該 process 的 trampframe 資訊,所以需要把 struct-proc 當作參數。
  • return value
    • 失敗 : 0
    • 成功 : 回傳 pagetable_t 的指標

  // An empty page table.
  pagetable = uvmcreate();
  if(pagetable == 0)
    return 0;
  • 建立一個空的 pagetable structure
  • 假如 pagetable == 0,表示 uvmcreate 失敗了。

  // map the trampoline code (for system call return)
  // at the highest user virtual address.
  // only the supervisor uses it, on the way
  // to/from user space, so not PTE_U.
  if(mappages(pagetable, TRAMPOLINE, PGSIZE,
              (uint64)trampoline, PTE_R | PTE_X) < 0){
    uvmfree(pagetable, 0);
    return 0;
  }
  • 去將 trampoline code 映射到特定的 virtual address。
  • 每一個 process 的 trampoline code 的 virtual address 都相同

  // map the trapframe page just below the trampoline page, for
  // trampoline.S.
  if(mappages(pagetable, TRAPFRAME, PGSIZE,
              (uint64)(p->trapframe), PTE_R | PTE_W) < 0){
    uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    uvmfree(pagetable, 0);
    return 0;
  }

  return pagetable;
}
  • 去將 trapframe 映射到特定的 virtual address
  • 每一個 process 的 trapframe 映射到的 virtual address 都相同


kernel/proc.c/proc_freepagetable

// Free a process's page table, and free the
// physical memory it refers to.
void
proc_freepagetable(pagetable_t pagetable, uint64 sz)
{
  uvmunmap(pagetable, TRAMPOLINE, 1, 0);
  uvmunmap(pagetable, TRAPFRAME, 1, 0);
  uvmfree(pagetable, sz);
}
  • this function
    • 這個 function 會把一個 pagetable 給釋放掉。
  • (arg) pagetable
    • 想要釋放哪一個 pagetable
  • (arg) sz
    • 該 process 佔用多大的 virtual memory
  • uvmunmap(pagetable, TRAMPOLINE, 1, 0);
    • 因為我們並不會真的為 TRAMPOLINE 多 kalloc 一段空間,所以這邊我們不需要 kfree,而是只需要把 pagetable 上的映射抹除就可以了
  • uvmunmap(pagetable, TRAPFRAME, 1, 0);
    • 理由同上,只需要把 pagetable 上的映射抹除。
  • uvmfree
    • freewalk function 會真的去 free 掉這個 pagetable
    • e.g. kfree((void*)pagetable);


kernel/proc.c/userinit

// Set up first user process.
void
userinit(void)
{
  struct proc *p;

  p = allocproc();
  initproc = p;
  
  p->cwd = namei("/");

  p->state = RUNNABLE;

  release(&p->lock);
}
  • 這邊會去創造第一個 user process
  • 會在開機的時候,被 cpuid == 0 的 cpu 執行
  • 我可以在 user/init.c 看到第一個 process 會做哪些事情
  • forkret function 裡面,假如看到是第一個 user process,會讓該 user process kexec(“/init”...)


kernel/proc.c/growproc

// Shrink user memory by n bytes.
// Return 0 on success, -1 on failure.
int
growproc(int n)
{
  uint64 sz;
  struct proc *p = myproc();

  sz = p->sz;
  if(n > 0){
    if((sz = uvmalloc(p->pagetable, sz, sz + n, PTE_W)) == 0) {
      return -1;
    }
  } else if(n < 0){
    sz = uvmdealloc(p->pagetable, sz, sz + n);
  }
  p->sz = sz;
  return 0;
}
  • this function
    • 這個 function 可以調整一個 struct-proc 想要使用多少的 virtual memory,可以增多,也可以減少。
    • 想要增多的時候,會 allocate 更多 RAM
    • 想要減少的時候,會 deallocate,並釋放 RAM
  • (arg) n
    • 單位是 bytes
    • 大於 0,表示我們想要更多的 RAM,會呼叫 uvmalloc 進行 allocate。
    • 小於 0,表示要付放出多少 |n| bytes 的 RAM,會呼叫 uvmdealloc 進行 deallocate。
  • return value
    • 成功 : 0
    • 失敗 : -1



圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言