天天看點

程序篇——fork()與vfork()建立程序

文章目錄

    • 1.什麼是程序
    • 2.父子程序
    • 3.fork()建立程序
    • 4.vfork()建立并執行程序

1.什麼是程序

程序是執行中的程式。當一個程式被加載到記憶體之後就變為了程序。 程序=程式+執行

2.父子程序

  • 建立程序稱為父程序,而新的程序稱為子程序
  • 當程序建立新程序時,可有兩種執行可能: 父程序與子程序并發執行。 父程序等待,直到某個或全部子程序執行完。
  • 新程序的位址空間也有兩種可能: 子程序是父程序的複制品(它具有與父程序同樣的程式和資料)。子程序加載另一個新程式。

3.fork()建立程序

pid_t fork(void);

函數的傳回值:

<0      出錯
	    =0     子程序
        >0     父程序(其值為子程序的程序号)
           
  • 先傳回誰是不确定的,父子程序的排程的順序是由排程器決定的,與程序的建立順序無關。
  • fork函數所建立的子程序是父程序的完整副本,複制了父程序的資源,包括記憶體的task_struct内容。
  • 子程序擁有自己的虛拟位址空間,父子程序資料獨有,(寫時複制)代碼共享。
  • fork函數的傳回值起到分流的作用,可以用fork的傳回值判斷哪個是父程序或子程序。

下面的兩張圖就表示父程序和相對應的子程序的記憶體映射:

程式篇——fork()與vfork()建立程式

程序的資料區也就是未初始化或者初始化為0的資料(.bss)、已初始化且不為0的資料(.data);程序的棧區也就是程序的使用者棧和堆;程序程式代碼就是程序的程式檔案(.text);除此之外還有程序的系統堆棧區

代碼測試:用fork()建立子程序,在子程序中修改變量的值,然後在父子程序中分别列印輸出

//fork

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    int count = 1;
    pid_t pid ;
    pid = fork();
    if(pid < 0)
    {
        perror("fork error:");
        return -1;
    }
    else if(pid == 0)
    {
        count++;
        printf("this is child, pid=%d,count=%d(%p)\n",getpid(),count,&count);
    }
    else
    {
        printf("this is father, pid=%d,count=%d(%p)\n",getpid(),count,&count);
    }
    return 0;
}
           

測試結果:

程式篇——fork()與vfork()建立程式

代碼分析:

  • 父子程序pid是不一樣的;count的值也不一樣,但是它們的位址卻相同。
  • count值相等,因為父子兩個程序是各自擁有自己的資料空間,也就是他們的資料是不共享的。
  • 兩個count值不相等,位址卻相等(這裡列印出來位址其實是虛拟位址),原因在于虛拟位址和計算機記憶體中實體位址之間是通過MMU和頁表聯系在一塊的,(虛拟位址映射到實體位址的方法不一樣)雖然虛拟位址一樣,他們映射到記憶體中的實體位址不一樣,列印出來的count值也就不一樣。

4.vfork()建立并執行程序

pid_t vfork(void);

函數的傳回值:

< 0  出錯
			 =0 子程序
			 >0父程序(其值為子程序的程序号)
           

在使用fork函數建立一個新程序之後,可以不使用exec系列函數來執行新的程式,如果要執行新的程式則必須手動調用exec系列函數,在這種情況下可以使用vfork函數,vfork函數在建立完一個新的程序之後自動實作exec系列函數的功能

  • vfork函數相比fork函數更加粗暴,核心連子程序的虛拟位址都不建立了,而是直接共享父程序的,進而實體位址也就被理所當然的共享了。
  • 父程序會保證子程序先運作,在子程序調用exec(程序替換)或exit後才可能被排程運作
  • vfork()建立的程序并不将父程序的位址空間完全複制到子程序中,因為子程序會立即調用exec (或exit)于是也就不會存放該位址空間。
  • 用 vfork() 建立程序,子程序裡一定要調用 exec(程序替換) 或 exit(退出程序),否則,程式會出問題,發生死鎖,沒有意義。

用vfork()建立子程序且調用execve()之後,子程序的記憶體映像如下所示:

程式篇——fork()與vfork()建立程式

把上面fork的測試代碼改成vfork,列印結果又會怎麼樣呢?

//vfork

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>

int main()
{
      int count = 1;
      pid_t pid ;
      pid = vfork();
      if(pid < 0)
      {
          perror("fork error:");
          return -1;
      }
      else if(pid == 0)
      {
          count++;
          printf("this is child, pid=%d,count=%d(%p)\n",getpid(),count,&count);
          exit(0);
      }
      else
      {
  
          printf("this is father, pid=%d,count=%d(%p)\n",getpid(),count,&count);
      }
      return 0;
}

           

測試結果:

程式篇——fork()與vfork()建立程式

代碼分析

  • pid,count值以及它們的虛拟位址都是相同的。因為vfork函數的父子程序是共享虛拟位址空間的,是以count的值肯定是一樣的
  • 但是vfork函數的代碼裡面,子程序最後加了一條語句exit(0);這是因為vfork這個函數在建立了子程序之後,父程序它會一直等待子程序退出,如果子程序沒有退出,那麼父程序将一直等待,直到子程序退出之後,父程序才會運作
  • 如果不加exit(0)這個函數的話,或者在子程序中用return

    0傳回的話,那麼建立的子程序在傳回後,會再次回到vfork函數調用處,進而會再次建立子程序,一直到把系統的pid用完,無法建立,然後報錯退出。

總結:

fork()與vfork()差別:

(1)fork: 子程序拷貝父程序的資料段
    vfork: 子程序與父程序共享資料段

(2)fork: 父子程序的執行次序不确定
    vfork: 子程序先運作,子程序運作退出後父程序再運作
           

繼續閱讀