fork,vfork,clone

fork

  一個進程,是包括代碼、數據和分配給進程的資源,fork()包含的頭文件<sys/types.h>和<unistd.h>,fork()函數通過系統調用創建一個與原來進程幾乎完全相同的進程,也就兩個進程可以完全做相同的事,但如果初始化參數或者傳入的變量不同,兩個進程也可以做不同的事。一個進程調用fork()函數後,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然後把原來的進程的所有值都複製到新的進程中,只有少數值與原來發的進程的值不同,相當於克隆了一個自己

  在fork()之後exec之前兩個進程用的是相同的物理空間(內存區),先把頁表映射關係建立起來,並不真正將內存拷貝。子進程的代碼段、數據段、堆棧都是指向父進程的物理空間,也就是說,兩者的虛擬空間不同,但其對應的物理空間是同一個。當父進程中有更改相應段的行爲發生時,如進程寫訪問,再爲子進程相應的段分配物理空間,如果不是因爲exec,內核會給子進程的數據段、堆棧段分配相應的物理空間(至此兩者有各自的進程空間,互不影響),而代碼段繼續共享父進程的物理空間(兩者的代碼完全相同)。而如果是因爲exec,由於兩者執行的代碼不同,子進程的代碼段也會分配單獨的物理空間。fork時子進程獲得父進程數據空間、堆和棧的複製所以變量的地址(當然是虛擬地址)是一樣的。

  但實際上,linux爲了提高fork的效率,採用了copy-on-write技術,fork後,這兩個虛擬地址實際上指向相同的物理地址。(內存頁),只有任何一個進程試圖修改這個虛擬地址裏的內容前(而當子進程改變了父進程的變量時候,會通過copy_on_write的手段爲所涉及的頁面建立一個新的副本),兩個虛擬地址纔會指向不同的物理地址。新的物理地址的內容從源物理地址中複製得到。
  問題:fork採用了這種寫時複製的機制,那麼fork出來子進程後,理論上子進程和父進程那個先調度呢?
       fork之後內核一般會通過將子進程放在隊列的前面,以讓子進程先執行,因爲很多情況下子進程要馬上執行exec,會清空棧、堆,這些和父進程共享的空間,加載新的代碼段。。這就避免了父進程“寫時複製”拷貝共享頁面的機會。如果父進程先調度很可能寫共享頁面,而子進程什麼也沒做,會產生“寫時複製”的無用功。所以,一般子進程先調度。避免因無意義的複製而造成效率的下降。而事實上同步執行不分先後

  fork調用的一個奇妙之處就是它僅僅被調用一次,卻能能夠返回兩次,它可能有三種不同的返回值。如果創建成功一個子進程,對於父進程來說是返回子進程的ID.而對於子進程來說就是返回0.而返回-1代表創建子進程失敗.

如fork執行成功。fork返回新創建子進程的進程ID。在子進程中,fork返回0,所以可以通過返回值來判斷當前是子進程還是父進程

如fork執行失敗。1)當前的進程數已經達到了系統規定的上限,這時errno的值被設置爲EAGAIN。

        2)系統內存不足,這時errno的值被設置爲ENOMEM。

父進程和子進程哪個進程先執行要看系統的進程調度策略

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <errno.h>

int main(void)
{
    pid_t pid ;
    signal(SIGCHLD,SIG_IGN);
    printf("before fork pid:%d\n",getpid());
    int abc = 10;      
    pid = fork();          
    if(pid == -1)   //錯誤返回
    {
        perror("tile");
        return -1;
    }
    if(pid > 0)     //父進程空間
    {
        abc++;
        printf("parent:pid:%d \n",getpid());
        printf("abc:%d \n",abc);
        sleep(20);
    }
    else if(pid == 0)
    {   //子進程空間
        abc++;
        printf("child:%d,parent: %d\n",getpid(),getppid());
        printf("abc:%d",abc);
    }
    printf("fork after...\n");
}

總結:

1)fork系統調用之後,父進程和子進程交替執行,並且它們處於不同空間中。

int main()
{
   int pid;
   int num=1;
   pid=fork();
   if(pid>0)
  {
       num++;
       printf("in parent:num:%d addr:%x\n",num,&num);
   }
   else if(pid==0)
  {
       printf("in child:num:%d addr:%x\n",num,&num);
   }
}

答案:父子進程中輸出的num不同,num地址相同 
虛擬地址空間,num地址的值相同,但是其實真實的物理地址卻不一樣。

如果安裝兩個進程各處在獨自的虛擬進程地址空間分析的話,這個題很容易會選擇num地址不相同,但是Linux中資源分配都是虛擬機制,也就是說,他們還是共用一個虛擬的地址,但是映射到物理內存就可能不一樣

 

  2)fork()函數的一次調用返回2次返回,這個有點抽象難理解,此時二個進程處於獨立的空間,它們各自執行者自己的東西,不產生衝突,所以返回2次一次pid ==0,一次pid大於0.而至於是先子進程還是父進程先執行,這沒有確切的規定,是隨機的.

  3)將fork()返回值大於零設置爲父進程,這是因爲子進程獲得父進程的pid(getppid())相對容易,而父進程獲子進程pid叫難,所以在在fork()系統調用中將子進程的pid字節有它自己返回給父進程.

  4)forl()的子執行過程在fork()之後並不是從頭開始,因爲在fork()之前,父進程已經爲子進程搭建好了運行環境了.所以從fork()下一條語句開始。

 

vfork 

  vfork也是創建一個子進程,但是子進程共享父進程的空間。在vfork創建子進程之後,父進程阻塞,直到子進程執行了exec()或者exit()。vfork最初是因爲fork沒有實現COW機制,而很多情況下fork之後會緊接着exec,而exec的執行相當於之前fork複製的空間全部變成了無用功,所以設計了vfork。而現在fork使用了COW機制,唯一的代價僅僅是複製父進程頁表的代價,所以vfork不應該出現在新的代碼之中。      

  1. vfork創建出來的不是真正意義上的進程,而是一個線程,因爲它缺少經常要素有獨立的存儲空間
  2. 由vfork創建的子進程要先於父進程執行,子進程執行時,父進程處於掛起狀態,子進程執行完,喚醒父進程。除非子進程exit或者execve纔會喚起父進程
  3. 用vfork()創建的子進程必須顯示調用exit()來結束,否則子進程將不能結束,而fork()則不存在這個情況。
  4. vfork的好處是在子進程被創建後往往僅僅是爲了調用exec執行另一個程序,因爲它就不會對父進程的地址空間有任何引用,所以對地址空間的複製是多餘的 ,因此通過vfork共享內存可以減少不必要的開銷。

clone

  clone是Linux爲創建線程設計的(雖然也可以用clone創建進程)。所以可以說clone是fork的升級版本,不僅可以創建進程或者線程,還可以指定創建新的命名空間(namespace)、有選擇的繼承父進程的內存、甚至可以將創建出來的進程變成父進程的兄弟進程等等。

        clone函數功能強大,帶了衆多參數,它提供了一個非常靈活自由的常見進程的方法。因此由他創建的進程要比前面2種方法要複雜。clone可以讓你有選擇性的繼承父進程的資源,你可以選擇想vfork一樣和父進程共享一個虛存空間,從而使創造的是線程,你也可以不和父進程共享,你甚至可以選擇創造出來的進程和父進程不再是父子關係,而是兄弟關係

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
  1. fn爲函數指針,此指針指向一個函數體,即想要創建進程的靜態程序(我們知道進程的4要素,這個就是指向程序的指針,就是所謂的“劇本", );
  2. child_stack爲給子進程分配系統堆棧的指針(在linux下系統堆棧空間是2頁面,就是8K的內存,其中在這塊內存中,低地址上放入了值,這個值就是進程控制塊task_struct的值);
  3. arg就是傳給子進程的參數一般爲(0);
  4. flags爲要複製資源的標誌,描述你需要從父進程繼承那些資源(是資源複製還是共享,在這裏設置參數:

下面是flags可以取的值
    標誌                      含義
  CLONE_PARENT      創建的子進程的父進程是調用者的父進程,新進程與創建它的進程成了“兄弟”而不是“父子”
  CLONE_FS               子進程與父進程共享相同的文件系統,包括root、當前目錄、umask
  CLONE_FILES         子進程與父進程共享相同的文件描述符(file descriptor)表
  CLONE_NEWNS      在新的namespace啓動子進程,namespace描述了進程的文件hierarchy
  CLONE_SIGHAND   子進程與父進程共享相同的信號處理(signal handler)表
  CLONE_PTRACE     若父進程被trace,子進程也被trace
  CLONE_VFORK       父進程被掛起,直至子進程釋放虛擬內存資源
  CLONE_VM              子進程與父進程運行於相同的內存空間
  CLONE_PID             子進程在創建時PID與父進程一致
  CLONE_THREAD     Linux 2.4中增加以支持POSIX線程標準,子進程與父進程共享相同的線程羣

區別:

  1. fork不對父子進程的執行次序進行任何限制,fork返回後,子進程和父進程都從調用fork函數的下一條語句開始行,但父子進程運行順序是不定的,它取決於內核的調度算法
  2. vfork調用中,子進程先運行,父進程掛起,直到子進程調用了exec或exit之後,父子進程的執行次序纔不再有限制
  3. clone中由標誌CLONE_VFORK來決定子進程在執行時父進程是阻塞還是運行,若沒有設置該標誌,則父子進程同時運行,設置了該標誌,則父進程掛起,直到子進程結束爲止。
  4.  clone和fork的調用方式很不相同,clone調用需要傳入一個函數,該函數在子進程中執行。

  5. clone和fork最大不同在於clone不再複製父進程的棧空間,而是自己創建一個新的。 (void *child_stack,)也就是第二個參數,需要分配棧指針的空間大小,所以它不再是繼承或者複製,而是全新的創造。

轉自:https://www.cnblogs.com/tianzeng/p/9309759.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章