fork()、vfork()、clone()的區別

由於生活的複雜,這是一個並行的世界,在同一時刻,會發生不少奇妙的事情,北方下雪,南方下雨,這裏在吃飯,那邊在睡覺,有人在學習,有人在運動,因此這時一個多彩多姿的世界,天天都發生着不少事情,因此要想很好的表現這個世界,協調完成一件事兒,就得用到多進程或者多線程。因此進程是程序猿必定會接觸到的一個東西,他能使咱們的程序效率提升,高效的完成多任務,並行執行。下面主要看看產生進程或線程的三個函數。
       fork,vfork,clone都是linux的系統調用,這三個函數分別調用了sys_fork、sys_vfork、sys_clone,最終都調用了do_fork函數,差異在於參數的傳遞和一些基本的準備工做不一樣,主要用來linux建立新的子進程或線程(vfork創造出來的是線程)。html

       進程的四要素:linux

       (1)有一段程序供其執行(不必定是一個進程所專有的),就像一場戲必須有本身的劇本。
       (2)有本身的專用系統堆棧空間(私有財產)
       (3)有進程控制塊(task_struct)(「有身份證,PID」)
       (4)有獨立的存儲空間。
          缺乏第四條的稱爲線程,若是徹底沒有用戶空間稱爲內核線程共享用戶空間的稱爲用戶線程。
1、fork()多線程

           fork()函數調用成功:返回兩個值; 父進程:返回子進程的PID;子進程:返回0;
                                    失敗:返回-1;函數

          fork 創造的子進程複製了父親進程的資源(寫時複製技術),包括內存的內容task_struct內容(2個進程的pid不一樣)。這裏是資源的複製不是指針的複製。學習

          說到fork(),就不得不說一個技術:(Copy-On-Write)寫時複製技術。ui

          盜用一張圖,感受描述的確實挺到位:spa

 

 

          咱們都知道fork建立進程的時候,並無真正的copy內存(聽着好像矛盾了,資源的賦值爲何有沒有真正的賦值呢?),由於咱們知道,對於fork來說,有一個很討厭的東西叫exec系列的系統調用,它會勾引子進程另起爐竈。若是建立子進程就要內存拷貝的的話,一執行exec,辛辛苦苦拷貝的內存又被徹底放棄了。因爲fork()後會產生一個和父進程徹底相同的子進程,但子進程在此後多會exec系統調用,出於效率考慮,linux中引入了「寫時複製技術-Copy-On-Write」。.net

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

         具體過程是這樣的:
         fork子進程徹底複製父進程的棧空間,也複製了頁表,但沒有複製物理頁面,因此這時虛擬地址相同,物理地址也相同,可是會把父子共享的頁面標記爲「只讀」,若是父子進程一直對這個頁面是同一個頁面,直到其中任何一個進程要對共享的頁面「寫操做」,這時內核會複製一個物理頁面給這個進程使用,同時修改頁表。而把原來的只讀頁面標記爲「可寫」,留給另一個進程使用。這就是所謂的「寫時複製」。
      在理解上:能夠認爲fork後,這兩個相同的虛擬地址指向的是不一樣的物理地址,這樣方便理解父進程之間的獨立性。
      但實際上,linux爲了提升fork的效率,採用了copy-on-write技術,fork後,這兩個虛擬地址實際上指向相同的物理地址。(內存頁),只有任何一個進程試圖修改這個虛擬地址裏的內容前,兩個虛擬地址纔會指向不一樣的物理地址。新的物理地址的內容從源物理地址中複製獲得。
問題:fork採用了這種寫時複製的機制,那麼fork出來子進程後,理論上子進程和父進程那個先調度呢(理論效率分析,我的以爲有必定的道理)?
       fork以後內核通常會經過將子進程放在隊列的前面,以讓子進程先執行,由於不少狀況下子進程要立刻執行exec,會清空棧、堆,這些和父進程共享的空間,加載新的代碼段。。這就避免了父進程「寫時複製」拷貝共享頁面的機會。若是父進程先調度極可能寫共享頁面,而子進程什麼也沒作,會產生「寫時複製」的無用功。因此,通常子進程先調度。避免因無心義的複製而形成效率的降低。
下面來看一個例子:
#include"stdio.h"
int main()
{
int count = 1;
int child;
if(0== fork()) //子進程成功返回0;
{ //開始建立子進程
printf("This is son, his count is: %d. and his pid is: %d\n", ++count, getpid());//子進程的內容
}
else
{
printf("This is father, his count is: %d, his pid is: %d\n", count, getpid());
}
}
運行結果:設計

 

         從結果能夠看出子進程和父進程的PID不一樣,內存資源count是值得複製,子進程改變了count的值,而父進程中的count沒有被改變。有人認爲這樣大批量的複製會致使執行效率太低。其實在複製過程當中,子進程複製了父進程的task_struct,系統堆棧空間和頁面表,這意味着上面的程序,咱們沒有執行count++前,其實子進程和父進程的count指向的是同一塊內存。而當子進程改變了父進程的變量時候,會經過copy_on_write的手段爲所涉及的頁面創建一個新的副本。因此當咱們執行++count後,這時候子進程才新建了一個頁面複製原來頁面的內容,基本資源的複製是必須的,並且是高效的。總體看上去就像是父進程的獨立存儲空間也複製了一遍。這將和下面的vfork有必定的區別。
其次,咱們看到子進程和父進程直接沒有互相干擾,明顯2者資源都獨立了。咱們看下面程序
#include"stdio.h"
int main() {
int count = 1;
int child;
int i;
if(!(child = fork()))
{

for(i = 0; i <20; i++)
{
printf("This is son, his count is: %d. and his pid is: %d\n", i, getpid());
}
}
else
{
for(i=0;i<20;i++)
printf("This is father, his count is: %d, his pid is: %d\n", count, getpid());
}
}
運行結果:

 

 

從運行的結果能夠看出父子2個進程是同步運行的,其實不分前後。
2、vfork() 

    vfork是一個過期的應用,vfork也是建立一個子進程,可是子進程共享父進程的空間。在vfork建立子進程以後,父進程阻塞,直到子進程執行了exec()或者exit()。vfork最初是由於fork沒有實現COW機制,而不少狀況下fork以後會緊接着exec,而exec的執行至關於以前fork複製的空間所有變成了無用功,因此設計了vfork。而如今fork使用了COW機制,惟一的代價僅僅是複製父進程頁表的代價,因此vfork不該該出如今新的代碼之中。      

     vfork建立出來的不是真正意義上的進程,而是一個線程,由於它缺乏常常要素(4),獨立的內存資源,看下面的程序:

#include "stdio.h"
int main() {
int count = 1;
int child;
printf("Before create son, the father's count is:%d\n", count);
if(!(child = vfork()))
{
printf("This is son, his pid is: %d and the count is: %d\n", getpid(), ++count);
exit(1);
}
else
{
printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d\n", getpid(), count, child);
}
}
運行結果:

 

      從運行結果能夠看到vfork建立出的子進程(線程)共享了父進程的count變量,這一次是指針複製,2者的指針指向了同一個內存,因此子進程修改了count變量,父進程的 count變量一樣受到了影響。

    另外由vfork建立的子進程要先於父進程執行,子進程執行時,父進程處於掛起狀態,子進程執行完,喚醒父進程。除非子進程exit或者execve纔會喚起父進程,看下面程序:

#include "stdio.h"
int main()
{
int count = 1;
int child;
printf("Before create son, the father's count is:%d\n", count);
if(!(child = vfork()))
{
int i;
for(i = 0; i < 100; i++)
{
printf("This is son, The i is: %d\n", i);
count++;
if(i == 20)
{
printf("This is son, his pid is: %d and the count is: %d\n", getpid(), ++count);
exit(1);
}
}


}
else
{
printf("After son, This is father, his pid is: %d and the count is: %d, and the child is: %d\n", getpid(), count, child);
}
}
運行結果:

 

從運行的結果能夠看到父進程老是等子進程執行完畢後纔開始繼續執行。
3.clone

Linux上建立線程通常使用的是pthread庫 實際上linux也給咱們提供了建立線程的系統調用,就是clone

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

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

      int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
     fn爲函數指針,此指針指向一個函數體,即想要建立進程的靜態程序(咱們知道進程的4要素,這個就是指向程序的指針,就是所謂的「劇本", );
     child_stack爲給子進程分配系統堆棧的指針(在linux下系統堆棧空間是2頁面,就是8K的內存,其中在這塊內存中,低地址上放入了值,這個值就是進程控制塊task_struct的值);
     arg就是傳給子進程的參數通常爲(0);
     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線程標準,子進程與父進程共享相同的線程羣
下面的例子是建立一個線程(子進程共享了父進程虛存空間,沒有本身獨立的虛存空間不能稱其爲進程)。父進程被掛起當子線程釋放虛存資源後再繼續執行。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
#define FIBER_STACK 8192
int a;
void * stack;
int do_something(){
a=10;
printf("This is son, the pid is:%d, the a is: %d\n", getpid(), a);
free(stack);
exit(1);
}
int main() {
void * stack;
a = 1;
stack = malloc(FIBER_STACK);//爲子進程申請系統堆棧
if(!stack) {
printf("The stack failed\n");
exit(0);
}
printf("creating son thread!!!\n");
clone(&do_something, (char *)stack + FIBER_STACK, CLONE_VM|CLONE_VFORK, 0);//建立子線程
printf("This is father, my pid is: %d, the a is: %d\n", getpid(), a);
exit(1);
}
運行的結果:

son的PID:10692;

father的PID:10691;

parent和son中的a都爲10;因此證實他們公用了一份變量a,是指針的複製,而不是值的複製。

問題:clone和fork的區別:

     (1) clone和fork的調用方式很不相同,clone調用須要傳入一個函數,該函數在子進程中執行

      (2)clone和fork最大不一樣在於clone再也不復制父進程的棧空間,而是本身建立一個新的。 (void *child_stack,)也就是第二個參數,須要分配棧指針的空間大小,因此它再也不是繼承或者複製,而是全新的創造。

 

 

博客資料參考:

http://blog.csdn.net/xy010902100449/article/details/44851453

http://www.cnblogs.com/blankqdb/archive/2012/08/23/2652386.html

http://blog.chinaunix.net/uid-24774106-id-3361500.html
http://www.linuxidc.com/Linux/2015-03/114888.htm
http://igaozh.iteye.com/blog/1677969

http://blog.chinaunix.net/uid-24410388-id-195503.html

http://blog.chinaunix.net/uid-18921523-id-265538.html

http://blog.csdn.net/wdjhzw/article/details/25614969

感謝各位博主的分享!
---------------------
做者:塵虛緣_KY
來源:CSDN
原文:https://blog.csdn.net/gogokongyin/article/details/51178257
版權聲明:本文爲博主原創文章,轉載請附上博文連接!

感謝各位博主的分享!

相關文章
相關標籤/搜索