linux系統調用fork, vfork, clone

fork,vfork,clone都是linux的系統調用,用來創建子進程的(確切說vfork創造出來的是線程)。

先介紹下進程必須的4要點:

a.要有一段程序供該進程運行,就像一場戲劇要有一個劇本一樣。該程序是可以被多個進程共享的,多場戲劇用一個劇本一樣。

b.有起碼的私有財產,就是進程專用的系統堆棧空間。

c.有“戶口”,既操作系統所說的進程控制塊,在linux中具體實現是task_struct

d.有獨立的存儲空間。

當一個進程缺少d條件時候,我們稱其爲線程。

1.fork 創造的子進程複製了父親進程的資源,包括內存的內容task_struct內容(2個進程的pid不同)。這裏是資源的複製不是指針的複製。下面的例子可以看出

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) { //開始創建子進程
                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());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
This is son, his count is: 2. and his pid is: 3019
This is father, his count is: 1, his pid is: 3018
[root@liumengli program]# 
從代碼裏面可以看出2者的pid不同,內存資源count是值得複製,子進程改變了count的值,而父進程中的count沒有被改變。有人認爲這樣大批量的複製會導致執行效率過低。其實在複製過程中,子進程複製了父進程的task_struct,系統堆棧空間和頁面表,這意味着上面的程序,我們沒有執行count++前,其實子進程和父進程的count指向的是同一塊內存。而當子進程改變了父進程的變量時候,會通過copy_on_write的手段爲所涉及的頁面建立一個新的副本。所以當我們執行++count後,這時候子進程才新建了一個頁面複製原來頁面的內容,基本資源的複製是必須的,而且是高效的。整體看上去就像是父進程的獨立存儲空間也複製了一遍。

 

其次,我們看到子進程和父進程直接沒有互相干擾,明顯2者資源都獨立了。我們看下面程序

[root@liumengli program]# cat testFork.c
#include"stdio.h"

int main() {
        int count = 1;
        int child;

        if(!(child = fork())) {
                int i;
                for(i = 0; i < 200; i++) {
                        printf("This is son, his count is: %d. and his pid is: %d\n", i, getpid());
                }
        } else {
                printf("This is father, his count is: %d, his pid is: %d\n", count, getpid());
        }
}
[root@liumengli program]# gcc testFork.c -o testFork
[root@liumengli program]# ./testFork
...

This is son, his count is: 46. and his pid is: 4092
This is son, his count is: 47. and his pid is: 4092
This is son, his count is: 48. and his pid is: 4092
This is son, his count is: 49. and his pid is: 4092
This is son, his count is: 50. and his pid is: 4092
This is father, his count is: 1, his pid is: 4091
[root@liumengli program]# This is son, his count is: 51. and his pid is: 4092
This is son, his count is: 52. and his pid is: 4092
...

(運氣很衰,非要200多個纔有效果,鬱悶)從結果可以看出父子2個進程是同步運行的。這和下面的vfork有區別。

 

2.vfork創建出來的不是真正意義上的進程,而是一個線程,因爲它缺少了我們上面提到的進程的四要素的第4項,獨立的內存資源,看下面的程序

[root@liumengli program]# cat testVfork.c
#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);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork 
Before create son, the father's count is:1
This is son, his pid is: 4185 and the count is: 2
After son, This is father, his pid is: 4184 and the count is: 2, and the child is: 4185
[root@liumengli program]# 
從運行結果可以看到vfork創建出的子進程(線程)共享了父進程的count變量,這一次是指針複製,2者的指針指向了同一個內存,所以子進程修改了count變量,父進程的 count變量同樣受到了影響。另外由vfork創造出來的子進程還會導致父進程掛起,除非子進程exit或者execve纔會喚起父進程,看下面程序:

[root@liumengli program]# cat testVfork.c
#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);
                        if(i == 70)
                                exit(1);
                }
                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);
        }
}
[root@liumengli program]# gcc testVfork.c -o testVfork
[root@liumengli program]# ./testVfork 
...

This is son, The i is: 68
This is son, The i is: 69
This is son, The i is: 70
After son, This is father, his pid is: 4433 and the count is: 1, and the child is: 4434
[root@liumengli program]# 
從這裏就可以看到父進程總是等子進程執行完畢後纔開始繼續執行。

 

3.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的值),flags就是標誌用來描述你需要從父進程繼承那些資源, arg就是傳給子進程的參數)。下面是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線程標準,子進程與父進程共享相同的線程羣

下面的例子是創建一個線程(子進程共享了父進程虛存空間,沒有自己獨立的虛存空間不能稱其爲進程)。父進程被掛起當子線程釋放虛存資源後再繼續執行。

[root@liumengli program]# cat test_clone.c
#include "stdio.h"
#include "sched.h"
#include "signal.h"
#define FIBER_STACK 8192
int a;
void * stack;
int do_something(){
        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);
}
[root@liumengli program]# gcc test_clone.c -o test_clone
[root@liumengli program]# ./test_clone
creating son thread!!!
This is son, the pid is:7326, the a is: 2
This is father, my pid is: 7325, the a is: 2
[root@liumengli program]#

讀者可以試試其它的資源繼承方式。


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