fork函數詳解(附代碼)


雖然篇幅很長,但大多是易懂的代碼,不用擔心看不完

進程內存鏡像

這里的所有操作,都將在下面的代碼中有所體現

fork會拷貝當前進程的內存,並創建一個新的進程。如上圖,fork函數會將整個進程的內存鏡像拷貝到新的內存地址,包括代碼段、數據段、堆棧以及寄存器內容。之后,我們就有了兩個擁有完全一樣內存的進程。fork系統調用在兩個進程中都會返回,在父進程中,fork系統調用會返回子進程的pid。而在新創建的進程中,fork系統調用會返回0。所以即使兩個進程的內存是完全一樣的,我們還是可以通過fork的返回值區分舊進程和新進程。

某種程度上來說這里的拷貝操作浪費了,因為所有拷貝的內存都被丟棄並被exec替換。在大型程序中這里的影響會比較明顯。實際上操作系統會對其進行優化。(比如使用COW(copy on write)技術)

fork創建的新進程從fork語句后開始執行,因為新進程也繼承了父進程的PC程序計數器。

在xv6中唯一不是通過fork創建進程的場景就是創建第一個進程的時候,之后的所有進程都是通過fork創建的。

以下內容以xv6(教學用Linux操作系統)源代碼為例

代碼解析
// Create a new process, copying the parent.
// Sets up child kernel stack to return as if from fork() system call.
int
fork(void)
{
  int i, pid;
  struct proc *np;
  //獲取當前進程(進程只運行在用戶態)
  //fork函數是系統調用,實際上運行在內核態,用戶態的寄存器內容、內存狀態都會被保留下來,所以不用擔心fork函數的運行影響原進程
  struct proc *p = myproc();

  // Allocate process.
  //allocproc實際上從操作系統進程隊列中找到一個未在使用狀態的空進程,
  //為其分配trapframe(用來存儲寄存器內容),page table頁表,並設置了一些寄存器狀態。代碼見附
  if((np = allocproc()) == 0){
    return -1;
  }

  // Copy user memory from parent to child.
  //將父進程的頁表內容拷貝到子進程頁表中
  //uvmcopy user virtual memory copy
  if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){
    freeproc(np);
    release(&np->lock);
    return -1;
  }
  np->sz = p->sz;

  // copy saved user registers.
  //將父進程的寄存器內容拷貝到子進程中
  *(np->trapframe) = *(p->trapframe);

  // Cause fork to return 0 in the child.
  //a0為返回值寄存器,fork函數結束會將a0的值返回,因此子進程fork的返回值為0
  np->trapframe->a0 = 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]);
  np->cwd = idup(p->cwd);

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

  //在這里實現了父進程fork返回子進程pid
  pid = np->pid;
	//下面設置子進程的父進程,將子進程的狀態設置為可運行
  //之后,操作系統會在進程調度時,執行子進程
  release(&np->lock);

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

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

  return pid;
}
附:

alloproc代碼

static struct proc*
allocproc(void)
{
  struct proc *p;

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

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;
  }

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

  // 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;
}

Trapframe代碼:

struct trapframe {
  /*   0 */ uint64 kernel_satp;   // kernel page table
  /*   8 */ uint64 kernel_sp;     // top of process's kernel stack
  /*  16 */ uint64 kernel_trap;   // usertrap()
  /*  24 */ uint64 epc;           // saved user program counter
  /*  32 */ uint64 kernel_hartid; // saved kernel tp
  /*  40 */ uint64 ra;
  /*  48 */ uint64 sp;
  /*  56 */ uint64 gp;
  /*  64 */ uint64 tp;
  /*  72 */ uint64 t0;
  /*  80 */ uint64 t1;
  /*  88 */ uint64 t2;
  /*  96 */ uint64 s0;
  /* 104 */ uint64 s1;
  /* 112 */ uint64 a0;
  /* 120 */ uint64 a1;
  /* 128 */ uint64 a2;
  /* 136 */ uint64 a3;
  /* 144 */ uint64 a4;
  /* 152 */ uint64 a5;
  /* 160 */ uint64 a6;
  /* 168 */ uint64 a7;
  /* 176 */ uint64 s2;
  /* 184 */ uint64 s3;
  /* 192 */ uint64 s4;
  /* 200 */ uint64 s5;
  /* 208 */ uint64 s6;
  /* 216 */ uint64 s7;
  /* 224 */ uint64 s8;
  /* 232 */ uint64 s9;
  /* 240 */ uint64 s10;
  /* 248 */ uint64 s11;
  /* 256 */ uint64 t3;
  /* 264 */ uint64 t4;
  /* 272 */ uint64 t5;
  /* 280 */ uint64 t6;
};


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM