iT邦幫忙

0

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

  • 分享至 

  • xImage
  •  

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

大綱

  • kernel/proc.c/sleep
  • kernel/proc.c/wakeup
  • kernel/exec.c/flags2perm
  • kernel/exec.c/loadseg
  • kernel/exec.c/kexec
  • kernel/exec.c/kfork

kernel/proc.c/sleep

// Sleep on channel chan, releasing condition lock lk.
// Re-acquires lk when awakened.
void
sleep(void *chan, struct spinlock *lk)
{
  struct proc *p = myproc();
  
  // Must acquire p->lock in order to
  // change p->state and then call sched.
  // Once we hold p->lock, we can be
  // guaranteed that we won't miss any wakeup
  // (wakeup locks p->lock),
  // so it's okay to release lk.

  acquire(&p->lock);  //DOC: sleeplock1
  release(lk);
  • (arg) chan : 該 process 要睡在哪個 channel ?
  • (arg) lk : 即將要陷入睡眠的這個 process,想要等待的資源的鎖。sleep 可以允許我們在陷入睡眠的時候放開這個鎖,等醒來之後再嘗試去拿取鎖,拿不到的話就開始 spin。
  • acquire(&p->lock);
    • 因為等等要取用 struct-proc 的內部資訊,所以需要先拿到鎖。
  • release(lk)
    • 暫時釋放該 process 拿取的鎖,讓其他 process 有機會產生該 process 所等待的資源。

  // Go to sleep.
  p->chan = chan;
  p->state = SLEEPING;

  sched();
  • 把 channel 的資訊記錄起來
  • 更改 state 到 SLEEPING,以免被 scheduler 挑選到
  • sched() : 因為這個 process 準備去睡覺了,把 CPU 交給其他 process。

  // Tidy up.
  p->chan = 0;

  // Reacquire original lock.
  release(&p->lock);
  acquire(lk);
}
  • 執行到這裡,表示該 process 被 wakeup 了
  • p->chan = 0 : 表示目前沒有在任何 channel 上等待
  • acquire(lk) : 把睡覺前釋放出來的 lock,在這裡嘗試拿回來,拿不到的話就在 while-loop 裡面等待,直到拿到鎖為止。


kernel/proc.c/wakeup

// Wake up all processes sleeping on channel chan.
// Caller should hold the condition lock.
void
wakeup(void *chan)
{
  struct proc *p;

  for(p = proc; p < &proc[NPROC]; p++) {
    if(p != myproc()){
      acquire(&p->lock);
      if(p->state == SLEEPING && p->chan == chan) {
        p->state = RUNNABLE;
      }
      release(&p->lock);
    }
  }
}
  • 這邊會遍尋所有的 process
  • 假如某個 process 符合條件,就把它的 state 改為 RUNNABLE
    • 正在睡覺 ( state 為 SLEEPING )
    • channel 符合參數 (arg) chan
  • 被改為 RUNNABLE 的 process 有機會被 CPU-scheduler-thread 挑選,被挑選中之後才會真的開始執行被喚醒的 process。


kernel/exec.c/flags2perm

// map ELF permissions to PTE permission bits.
int flags2perm(int flags)
{
    int perm = 0;
    if(flags & 0x1)
      perm = PTE_X;
    if(flags & 0x2)
      perm |= PTE_W;
    return perm;
}
  • this function
    • 希望把 ELF 有關於 segment 讀/寫/執行 權限的 flag,轉換成 xv6-riscv PTE ( page table entry,用於 MMU ) 權限的 flag。
  • (arg) flag
    • ELF 形式的 flag
  • return value
    • xv6-riscv PTE 形式的 flag

ELF flags 的意義 ( 可以在 kernel/elf.h 看到 )

  • ELF_PROG_FLAG_EXEC = 1 (binary 0001 or 0x1 ) :Executable
  • ELF_PROG_FLAG_WRITE = 2 (binary 0010 or 0x2 ) — Writable
  • ELF_PROG_FLAG_READ = 4 (binary 0100 or 0x4 ) — Readable

RISC-V PTE Flags ( 可以在 kernel/riscv.h 看到 )

  • PTE_R = 1L << 1 (binary 0010 ) : Read Permission
  • PTE_W = 1L << 2 (binary 0100 ) : Write Permission
  • PTE_X = 1L << 3 (binary 1000 ) : Execute Permission


kernel/exec.c/loadseg

// Load an ELF program segment into pagetable at virtual address va.
// va must be page-aligned
// and the pages from va to va+sz must already be mapped.
// Returns 0 on success, -1 on failure.
static int
loadseg(pagetable_t pagetable, uint64 va, struct inode *ip, uint offset, uint sz)
{
  • this function
    • 這個 function 會把某個 ELF file 的某個 segment 從 disk-hardware 載入到特定的 user-space-virtual-address。
    • 在使用之前,需要先把 va ~ va + sz 的映射設定在 page table。
  • (arg) pagetable_t pagetable
    • 因為這裡我們需要找到 va 相對應的 pa,所以需要 pagetable
  • (arg) uint64 va
    • 想要放資料的 user-space virtual address,
  • (arg) struct inode *ip
    • 代表該 ELF 檔案的 in-memory-struct-inode
  • (arg) uint offset
    • 在檔案內的偏移量,我們該從這個偏移量開始複製資料。
  • (arg) uint sz
    • 我們需要從檔案複製多少 bytes
  • return value
    • 0 : 代表成功
    • -1 : 代表失敗

  uint i, n;
  uint64 pa;

  for(i = 0; i < sz; i += PGSIZE){
    pa = walkaddr(pagetable, va + i);
    if(pa == 0)
      panic("loadseg: address should exist");
    if(sz - i < PGSIZE)
      n = sz - i;
    else
      n = PGSIZE;
    if(readi(ip, 0, (uint64)pa, offset+i, n) != n)
      return -1;
  }
  
  return 0;
}
  • for(i = 0; i < sz; i += PGSIZE){
    • 開始複製 sz bytes,假如可以的話,每次盡量複製 PGSIZE ( 4096 ) 的 bytes
  • walkaddr(pagetable, va + i)
    • 嘗試在某一個特定的 page-table,從 virtual address 轉換成 physical address
    • 假如該 virtual address 在這個 page-table 並沒有映射的話,就 return 0
  • if(pa == 0)
    • 該 va 在這個 page table 沒有映射, return 0
  • readi(ip, 0, (uint64)pa, offset+i, n)
    • (arg) ip
      • 想要讀取的檔案的 in-memory-struct-inode
    • (arg) 0
      • 0 : dst 是 kernel space 的 address
      • 1 : dst 是 user space 的 address
    • (arg) pa
      • destination,會把資料放在這個位址
    • (arg) offset + i
      • source,我們會從檔案的這個偏移量拿取資料
    • (arg) n
      • 要讀取多少 bytes
  • return 0 表示該 function 成功執行,已經把 ELF 相對應的 segment 載入到特定的 virtual address 。


kernel/exec.c/kexec

//
// the implementation of the exec() system call
//
int
kexec(char *path, char **argv)
{
  char *s, *last;
  int i, off;
  uint64 argc, sz = 0, sp, ustack[MAXARG], stackbase;
  struct elfhdr elf;
  struct inode *ip;
  struct proghdr ph;
  pagetable_t pagetable = 0, oldpagetable;
  struct proc *p = myproc();
  • this function
    • 這個 function 會替換當前的 process 成指定的程式。 memory space, instructions, static data, stack … 都會被替換成新的程式。
  • (arg) char *path
    • 該執行檔在 filesystem 中的路徑
  • (arg) char **argv
    • 給予新的 program 的 command-line arguments。
  • return value
    • value == -1 : 該 function 失敗
    • value == argc : 該 function 執行成功,會 return argc ( 給予新的 program 的參數的個數 )。

  begin_op();

  // Open the executable file.
  if((ip = namei(path)) == 0){
    end_op();
    return -1;
  }
  ilock(ip);
  • begin_op
    • 因為稍後會 iput,有機會對 disk 做出操作,所以這邊先 begin_op 開啟一段 file-system-logging-layder-transaction。
  • if((ip = namei(path)) == 0){
    • 根據給予的 path,取出相對應的 struct-inode
  • end_op(); return -1;
    • 找不到該 inode,於是找不到相對應的 ELF,return -1 表示執行失敗
  • ilock(ip)
    • 取得該 struct-inode 的 sleeplock。假如需要的話,會從 disk-hardware 載入 struct-dinode 的 metadata 進 RAM 的 struct-inode。

  // Read the ELF header.
  if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
    goto bad;

這邊會去讀取該 ELF 檔案的 header ( struct-elfhdr ),取得該 ELF 檔案的 metadata。


  // Is this really an ELF file?
  if(elf.magic != ELF_MAGIC)
    goto bad;

檢查 header 內的 magic value 是否為合法值,若不是合法值,表示該檔案不是一個合法的 ELF 檔案,需到 bad 標籤,表示該次 kexec 執行失敗。


  if((pagetable = proc_pagetable(p)) == 0)
    goto bad;
  • proc_pagetable(p)
    • 替一個特定的 process 創建一個新的 pagetable。
    • allocate 空間給 page table,並把該空間清成 0
    • 把 TRAMPOLINE 的映射設定在這個 pagetable
    • 把 TRAPFRAME 的映射設定在這個 pagetable
    • return 0 表示沒記憶體了,kexec 執行失敗

  // Load program into memory.
  for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){
    if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph))
      goto bad;
    if(ph.type != ELF_PROG_LOAD)
      continue;
    if(ph.memsz < ph.filesz)
      goto bad;
    if(ph.vaddr + ph.memsz < ph.vaddr)
      goto bad;
    if(ph.vaddr % PGSIZE != 0)
      goto bad;
    uint64 sz1;
    if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz, flags2perm(ph.flags))) == 0)
      goto bad;
    sz = sz1;
    if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0)
      goto bad;
  }
  • for(...)
    • 這邊開始遍尋該 ELF 檔案的所有 program header,並嘗試載入所有 section 到 RAM 裡面。
  • elf.phoff
    • 第一個 program header 的偏移量
  • elf.phnum
    • program header 的數目
  • off+=sizeof(ph)
    • 每一個 program header 的大小為 sizeof(struct-proghdr)
  • off
    • 表示當前檔案內的偏移量
  • readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph)
    • 載入一個 program header
  • ph.type != ELF_PROG_LOAD
    • 檢查該區段是否是需要載入到 RAM 裡面來的,不用的話,就用 continue 跳過這個 program header。
  • ph.memsz < ph.filesz
    • ph.filesz
      • 代表這個 section 在檔案中的大小
      • 代表了會包含已經初始化的資料,但不會包含未初始化的資料 ( e.g. int array[10000] ),理由很簡單,包含初始化的資料根本是浪費空間,只要知道該 array 的大小就足夠了。
    • ph.memsz 代表了真的要載入到 RAM 裡面的空間,包含未初始化的資料。
    • 所以理論上 ph.memsz >= ph.filesz
    • ph.memsz < ph.filez 顯然錯誤
  • ph.vaddr + ph.memsz < ph.vaddr
    • ph.vaddr
      • 代表該 section 想要載入的 user-space virtual address
    • 這種情況發生,表示發生了 overflow 了,需要避免。
  • ph.vaddr % PGSIZE != 0
    • user-space-virtual-address 這邊需要對齊 ( align ) 一個 page 的大小 ( 4096 bytes )。
  • uvmalloc(pagetable, sz, ph.vaddr + ph.memsz, flags2perm(ph.flags))
    • this function
      • 這個 function 會在 pagetable 裡面,從 offset:sz 的地方,配置虛擬記憶體到 offset(ph.vaddr + ph.memsz) 的地方,並且 PTE 權限為 flags2perm(ph.flags)
    • (arg) pagetable
      • 想新增 va->pa 映射的 page table
    • (arg) sz
      • 想新增 va->pa 映射的 virtual address 的開頭
    • (arg) ph.vaddr + ph.memsz
      • 想新增 va->pa 映射的 virtual address 的結尾
    • (arg) flags2perm(ph.flags)
      • 從 ELF flags 轉換成 RISC-V PTE flags
  • loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz)
    • (arg) ph.vaddr
      • 目標 user-space-virtual-address
    • (arg) ip
      • 指向 ELF 檔案的 struct-inode
    • (arg) ph.off
      • 這個 section 在 ELF 檔案中的 offset
    • (arg) ph.filesz
      • 這個 section 在 ELF 中佔用的大小

  iunlockput(ip);
  end_op();
  ip = 0;
  • iunlockput(ip)
    • 之後不需要再使用這個 struct-inode 了,所以把這個 struct-inode 釋放掉
  • end_op()
    • 結束這一段 transaction
  • ip = 0
    • 這邊記得要歸 0,因為在 bad 標籤那邊,會需要依照這個值來看要不要 iunlockput

  p = myproc();
  uint64 oldsz = p->sz;

  // Allocate some pages at the next page boundary.
  // Make the first inaccessible as a stack guard.
  // Use the rest as the user stack.
  sz = PGROUNDUP(sz);
  uint64 sz1;
  if((sz1 = uvmalloc(pagetable, sz, sz + (USERSTACK+1)*PGSIZE, PTE_W)) == 0)
    goto bad;
  sz = sz1;
  uvmclear(pagetable, sz-(USERSTACK+1)*PGSIZE);
  sp = sz;
  stackbase = sp - USERSTACK*PGSIZE;
  • p = myproc();
    • TODO : 不懂為什麼這裡還要再 myproc 一次 …
  • sz = PGROUNDUP(sz);
    • 對齊 PGSIZE ( 4096 bytes ) ,向上取整。
  • if((sz1 = uvmalloc(pagetable, sz, sz + (USERSTACK+1)*PGSIZE, PTE_W)) == 0)
    • 替該 process 配置 USERSTACK ( 預設為 1 ) 個 PGSIZE 的 stack 空間
    • + 1 是為了 Guard Page,當有人讀/寫超出 stack 的空間,並對 Guard Page 進行 讀/寫 的話,就會觸發 page fault,讓我們知道發生了 buffer overflow
    • uvmalloc 回傳 0 表示 uvmalloc 失敗,該 kexec 執行失敗,進入 bad 標籤。
  • sz = sz1
    • 用 sz 來追蹤,目前我們 allocate virtual memory 的 upper bound
  • uvmclear(pagetable, sz-(USERSTACK+1)*PGSIZE);
    • uvmclear 可以讓特定的 page 沒辦法被 user space 使用
    • 這邊就是製造一個 user space 不能碰觸的 guard page
  • sp = sz
    • 設定 stack pointer register, stack 會從這個位置 往低位擴展
    • 注意這個 sp 會是一個 user-space-virtual-address
  • stackbase = sp - USERSTACK*PGSIZE;
    • 設定 stack 最低可以到哪個位址
    • 這個位址以下就是 Guard Page 開始的地方

  // Copy argument strings into new stack, remember their
  // addresses in ustack[].
  for(argc = 0; argv[argc]; argc++) {
    if(argc >= MAXARG)
      goto bad;
    sp -= strlen(argv[argc]) + 1;
    sp -= sp % 16; // riscv sp must be 16-byte aligned
    if(sp < stackbase)
      goto bad;
    if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
      goto bad;
    ustack[argc] = sp;
  }
  ustack[argc] = 0;
  • for(argc = 0; argv[argc]; argc++) {
    • 這邊嘗試遍尋每一個參數
    • 每一個 argv[i] 都是指向一個字串的指標
    • 只要遇到 argv[i] == 0,就停止遍尋。 ( null-terminated )
  • if(argc >= MAXARG)
    • 參數的個數不可以超過 MAXARG ( 預設為 32 )
  • sp -= strlen(argv[argc]) + 1;
    • 在 user stack 上,為參數字串預留空間。
    • +1 是為了 null-terminator ( string ending charactor \0 )
  • sp -= sp % 16;
    • RISC-V 的 ABI 規定,sp register 需要是 16 bytes align,這邊可以把 sp 修整成可以被 16 整除。
  • if(sp < stackbase)
    • 假如最後 sp 掉到了 Guard Page 以下,表示用掉太多空間了 ! kexec 失敗。
  • if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0)
    • pagetable
      • 記錄著 va->pa 的映射的 pagetable
    • sp
      • user-space-virtual-address,目前會指向為了 argv[argc] 指向的字串 在 user-stack 空出的一個空間。
    • argv[argc]
      • 一個代表參數的字串
    • strlen(argv[argc]) + 1
      • 代表要複製多少 bytes,+1 代表 null-terminator。
    • 把 argv[argc] ( kernel space address ) 指向的字串,複製到 sp ( user-space-virtual-address ) 的位址。
  • ustack[argc] = sp;
    • 紀錄每一個參數字串在 user-space 的指標。
  • ustack[argc] = 0;
    • 將這個指標設為 0,代表之後沒有其他參數了。

  // push a copy of ustack[], the array of argv[] pointers.
  sp -= (argc+1) * sizeof(uint64);
  sp -= sp % 16;
  if(sp < stackbase)
    goto bad;
  if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0)
    goto bad;
  • 這段程式碼是希望把 ustack 這個指標陣列,也複製到 user-stack 上。
  • ustack 的每一個元素都是一個指向字串的指標,而每個字串也都在 user-stack 上。上一段程式碼就是把字串參數通通複製到 user-stack

  // a0 and a1 contain arguments to user main(argc, argv)
  // argc is returned via the system call return
  // value, which goes in a0.
  p->trapframe->a1 = sp;

  // Save program name for debugging.
  for(last=s=path; *s; s++)
    if(*s == '/')
      last = s+1;
  safestrcpy(p->name, last, sizeof(p->name));
  • p->trapframe->a1 = sp;
    • 把當前的 sp 交給 trapframe->a1。此時 sp 會指向已經複製到 user-space-stack 的 ustack
    • kexec 本身會回傳 argc,而最後這個回傳值會交給 p->trapframe->a0 (link)
    • 當我們執行完 kexec 的時候,就會像一般的系統呼叫結束一樣,跳回到 user-space,但此時 user-space 已經煥然一新,變成了新的 ELF 所期待的模樣。
    • 而新的 program 的 int main(int argc, char *argv[])
      • int argc 就會是 p->trapframe->a0
      • char *argv[] 就會是 p->trapframe->a1
  • for(last=s=path; *s; s++)
    • 尋找 path 中的最後一個 token
    • 例如一個 path 是 /abc/def/hhh.elf,那 last 所指向的字串便是 hhh.elf
  • safestrcpy(p->name, last, sizeof(p->name));
    • 把這個檔案名稱當作該 process 的 name,用以 debug。

  // Commit to the user image.
  oldpagetable = p->pagetable;
  p->pagetable = pagetable;
  p->sz = sz;
  p->trapframe->epc = elf.entry;  // initial program counter = main
  p->trapframe->sp = sp; // initial stack pointer
  proc_freepagetable(oldpagetable, oldsz);

  return argc; // this ends up in a0, the first argument to main(argc, argv)
  • oldpagetable = p->pagetable;
    • 等一下要把舊有的 pagetable 清空。
  • p->pagetable = pagetable;
    • 把當前的 process 的 pagetable,換成全新的 pagetable。
  • p->sz = sz;
    • 更新該 process 的 size
  • p->trapframe->epc = elf.entry; // initial program counter = main
    • 把 trapframe->epc 換成 ELF 的 entry point ( main function )。
    • 在 kernel/trap.c/prepare_return 的最後,會有 w_sepc(p->trapframe->epc); ,這讓我們可以控制從 kernel space 回到 user-space 的時候,program counter 該跳到哪裡。於是這邊換成 ELF 的 entry point,在我們從 kernel space 回到 user space 的時候,就會在 user space 的 ELF entry point 開始執行。
  • p->trapframe->sp = sp; // initial stack pointer
    • 換上新的 stack pointer。
  • proc_freepagetable(oldpagetable, oldsz);
    • 把舊有的 pagetable 釋放掉。
  • return argc; // this ends up in a0, the first argument to main(argc, argv)
    • 這邊會回傳 argc,也就是參數的數量。
    • kernel/syscall.c/syscall,會把這個回傳值放到 p->trapframe->a0
    • 於是當我們從 kernel-space 回到 user-space 的時候,program counter 會被設定成 ELF 的 entry-point ( main function ),並且把 p->trapframe->a0 視為第一個參數 (argc),並把 p->trapframe->a1 視為第二個參數 ( char *argv[] )。

 bad:
  if(pagetable)
    proc_freepagetable(pagetable, sz);
  if(ip){
    iunlockput(ip);
    end_op();
  }
  return -1;
}
  • bad
    • 當 kexec 發生錯誤的時候,就會跳到這邊。假如執行 kexec 的過程中有 allocate 任何的資源,在這邊都需要歸還。
  • proc_freepagetable(pagetable, sz);
    • 會把用 proc_pagetable 所創建出來的 pagetable 釋放回去
  • if(pagetable)
    • 假如在發生錯誤之前,有 allocate pagetable 的話,要歸還回去
  • if(ip){
    • 假如在發生錯誤之前,有取得 struct-inode 指標的話,這邊要把 reference count 減回去
  • return -1
    • 表示 kexec 發生錯誤。

總結

這邊稍微總結一下 kexec 所發生的事情。

  • 從 disk-hardware 讀取 ELF 檔案的 header ( struct elfhdr ),驗證這是否是一個合法的 ELF 檔案。
  • 使用 proc_pagetable 建立一個新的 pagetable
  • 遍尋這個 ELF 檔案的所有 program section header ( struct proghdr ),並把全部需要載入到 RAM 裡面的 section,載入到 RAM 裡面,並在新的 pagetable 加上這些區段的 virtual-address -> physical address 映射
  • 建立 user-stack,並且多 allocate 一個 page 給 Guard Page
  • 把給這個新的 program 參數 ( argc, argv ),從 kernel space 複製到 user-stack。並且把這兩個參數放到 p->trapframe->a0 以及 p->trapframe->a1
  • 替這個 process 換上新的 pagetable,並把舊的 pagetable 的資源是放掉
  • 設定 p->trapframe
  • return,準備回到 user space,並從新的 program 的 main function 開始執行。


kernel/proc.c/kfork

// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
kfork(void)
{

這個 function 會複製當前的 user process ( parent ),創造出一個相同的 user process ( child )。

  • return value
    • 對於 parent process 來說,fork 的 return value 會是 child process 的 process ID ( PID )。
    • 對於 child process 來說,fork 的 return value 會是 0。
    • 假如 return value == -1,表示該 kfork 執行失敗。

  int i, pid;
  struct proc *np;
  struct proc *p = myproc();

  // Allocate process.
  if((np = allocproc()) == 0){
    return -1;
  }
  • np = allocproc()
    • 建立一個新的 process ( struct-proc ),並且會給這個 process 一個新的 pagetable。
  • 要注意的是,allocproc 回傳 struct-proc 的時候,會是已經拿到 struct-proc->lock 的狀態,用以避免其他人會去使用這個還在中間狀態的新的 struct-proc。

  // Copy user memory from parent to child.
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  • 把 parent process ( p ) 的 pagetable,複製給 child process ( np ) 的 pagetable。
  • uvmcopy 會從 virutal address 0 開始,複製 0 ~ sz 的 p->pagetable 到 np->pagetable。

  np->sz = p->sz;

因為目前 parent-process 跟 child-process 的 pagetable 一模一樣,所以 size 自然而然也是一樣


  // copy saved user registers.
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  np->trapframe->a0 = 0;
  • 把所有 parent-process 的 trapframe 也都複製給 child process
  • 唯一不同的地方是,np->trapframe->a0 被設為 0,也就是說,當 child-process 從 kernel-space 回到 user-space 的時候,這個 syscall 的 return value 會是 0。

  // increment reference counts on open file descriptors.
  for(i = 0; i < NOFILE; i++)
    if(p->ofile[i])
      np->ofile[i] = filedup(p->ofile[i]);
  • 複製 parent-process 的 file-descriptor-table 到 child-process。
  • filedup 會新增一個 file 的 reference count,意指多一個 process 正開著這個檔案。

  np->cwd = idup(p->cwd);

  safestrcpy(np->name, p->name, sizeof(p->name));

  pid = np->pid;
  • np->cwd = idup(p->cwd);
    • 讓 child-process 的 current working directory 與 parent-process 相同。
  • safestrcpy(np->name, p->name, sizeof(p->name));
    • 將 parent-process 的 name 也複製給 child-process。
  • pid = np->pid
    • 取得 child-process 的 pid ( 這個 pid 會在 allocproc 的時候 allocate )

  release(&np->lock);

  acquire(&wait_lock);
  np->parent = p;
  release(&wait_lock);

  acquire(&np->lock);
  np->state = RUNNABLE;
  release(&np->lock);

  return pid;
}
  • release(&np->lock);
    • 目前不會去使用 np ,先 release 這個 lock。
  • acquire(&wait_lock);
    • 需要操作 parent-child relationship,所以先拿取 global wait_lock
  • np->parent = p;
    • 把 child-process 的 parent 設為 p
  • acquire(&np->lock);
    • 需要操作 child-process 的內部狀態,拿取 child-process->lock
  • np->state = RUNNABLE;
    • 把 child-process 的狀態設定為 RUNNABLE,讓 CPU-scheduler-thread 有機會挑選到這個 child-process
    • TODO : 或許可以把這一行放在上面的 release(&np->lock) 之前,這樣就不用再 acquire(&np->lock) ?
  • return pid;
    • return pid,之後會將這個值放到 p->trapframe->a0,變成 syscall 的 return value。於是 parent process 呼叫 fork syscall 的時候,return value 會是 child-process 的 process-ID ( PID )。



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

尚未有邦友留言

立即登入留言