linux下利用fork創建進程,進程運行內存說明,與同時創建多個進程的方法及分析

1 進程及進程的創建

在linux編程中,用來創建用戶進程的函數時fork。首先來說明什麼是進程。

1 進程

什麼是進程,引用百度百科的說明:

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據及其組織形式的描述,進程是程序的實體。

通俗的說,一個程序在計算機中運行起來了,就是一個進程。

打個比方:程序好比郭德綱老師相聲的劇本,而進程就好比郭德綱老師在臺上表演。進程的生命週期就是郭德綱老師從上臺開始表演,到表演結束。

2 fork函數

在創建進程之前,需要linux環境安裝了gcc。

首先認識fork(),在linux中用man fork查看說明,由於說明很多,這裏就大概截取一點,具體的可以自行查閱man說明文檔
在這裏插入圖片描述從圖1的說明文檔中可以知道,fork函數的功能是創建一個子進程,需要包含的頭文件是#include<sys/types.h> #include <unistd.h>,沒有參數。還有很多描述,比如子進程擁有獨立的進程ID,父子進程運行在獨立的內存中等等,可以具體讀下man的說明文檔。
在這裏插入圖片描述
從圖2,可以看出返回值,如果是父進程返回值爲創建的子進程的PID;如果是子進程,返回爲0;如果創建失敗,返回-1。我們可以通過這一點來判斷當前進程是父還是子。

3 進程運行內存說明

在圖1中我們看到,父子進程運行的內存是隔離的,也就父子進程運行在相互獨立的內存中

當子進程被創建出來的時候,相應的子進程的內存空間也被創建出來了,彷彿克隆了一份父進程的內存空間。

如果是運行在32位的機子上,那麼fork後,內存如下圖,
在這裏插入圖片描述
當fork()後,子進程彷彿copy一份父進程的內存空間,作爲自己的內存空間。具體有以下內容:

全局變量,.text段,.rodata段,.data段,.bss段,heap,stack,env(環境變量),用戶ID, 進程工作目錄,信號處理方式等。

那我們計算機的內存都是固定的,比如4G/8G/16G等等。進程的內存如果是獨立的,如果有很多進程,怎麼夠用呢?

哈哈,其實創建子進程的時候不是真正的copy了一份內存空間,而是遵循了讀書共享寫時複製的原則,這樣就可以節省內存開銷了。太優秀了!

其實進程用到的虛擬內存到物理內存的映射關係,還涉及到MMU內存管理單元,他實現了我們物理內存到虛擬內存的映射。這裏邊的關係,後面我單獨寫個博客來分析下,這裏只需要知道父子進程之間內存是相互獨立的,以及父子進程遵循讀時共享寫時複製的原則

4 利用fork()創建進程

這個很簡單,直接上code

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

int main()
{
    pid_t pid;
    pid = fork();
    // if creat fail 
    if (pid == -1) {
        perror("creat child process fail!\n");
		exit(1);
    }
    // 利用返回值來判單是父還是子進程
    if (pid == 0){
        printf("I am the child, pid:%d\n", getpid());
    } else {
        printf("I am the parent, pid:%d\n", getpid());
    }

    return 0;
}

直接利用fork函數來創建子進程。

通過返回值的不同來判斷是父進程還是子進程。依據上面圖2中返回值得說明。

用gcc編譯

gcc creat_process.c -o creat_process

然後執行結果如下:
在這裏插入圖片描述
從結果看,有父進程也有子進程,函數功能ok。

2 利用fork同時創建多個進程

1 錯誤的方法及分析

如果要創建多個進程,我們很容易想到的就是,直接加個for循環,就可以了。那麼很容易想到的寫法:

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

int main(void)
{
    pid_t pid;
    printf("this is a fork test code\n");
    int i;

    for (i = 0; i < 5; i++) {
        pid = fork();
        if (pid == 0) {
            printf("i am %d child, pid = %u\n", i + 1, getpid());
        } else {
            printf("I am parent, pid = %u\n", getpid());
		}
    }

    return 0;
}

我們編譯執行下:

hxf@hxf-VirtualBox:thread$ gcc fork_test.c -o fork_test             
hxf@hxf-VirtualBox:thread$ ./fork_test
this is a fork test code
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
I am parent, pid = 26654
i am 1 child, pid = 26655
I am parent, pid = 26655
I am parent, pid = 26655
hxf@hxf-VirtualBox:thread$ I am parent, pid = 26655
I am parent, pid = 26655
i am 2 child, pid = 26660
I am parent, pid = 26660
I am parent, pid = 26660
I am parent, pid = 26660
i am 3 child, pid = 26661
I am parent, pid = 26661
I am parent, pid = 26661
i am 4 child, pid = 26662
I am parent, pid = 26662
i am 2 child, pid = 26656
i am 5 child, pid = 26663
I am parent, pid = 26656
I am parent, pid = 26656
i am 5 child, pid = 26666
I am parent, pid = 26656
i am 4 child, pid = 26671
i am 3 child, pid = 26657
I am parent, pid = 26657
i am 5 child, pid = 26669
I am parent, pid = 26657
i am 5 child, pid = 26672
I am parent, pid = 26671
i am 4 child, pid = 26665
I am parent, pid = 26665
i am 3 child, pid = 26664
I am parent, pid = 26664
I am parent, pid = 26664
i am 5 child, pid = 26676
i am 4 child, pid = 26673
I am parent, pid = 26673
i am 5 child, pid = 26674
i am 4 child, pid = 26658
i am 4 child, pid = 26677
i am 5 child, pid = 26675
I am parent, pid = 26677
i am 5 child, pid = 26680
i am 5 child, pid = 26659
i am 3 child, pid = 26670
I am parent, pid = 26670
I am parent, pid = 26670
I am parent, pid = 26658
i am 5 child, pid = 26682
i am 5 child, pid = 26668
i am 4 child, pid = 26667
i am 5 child, pid = 26679
i am 5 child, pid = 26683
i am 5 child, pid = 26678
I am parent, pid = 26667
i am 4 child, pid = 26681
I am parent, pid = 26681
i am 5 child, pid = 26684
i am 5 child, pid = 26685

發現根本不對,出錯在哪裏呢?

首先我們想要的結果是,由父進程創建5個子進程,如下圖:
在這裏插入圖片描述

哈哈,這裏其實是沒有理解創建子進程後,子進程和父進程是同時存在的,並且子進程是從創建的那一刻開始,擁有了父進程的執行程序的一切,比如全局變量,.text段,.rodata段,.data段,.bss段,heap,stack,env(環境變量) 等。所以其實當第一個子進程創建出來後,同樣會繼續執行fork(),所以子進程會創建出自己的子進程,這個時候子變成了父,生出了自己的子。這樣,子生孫,孫生重孫,無窮盡也。

分析上面的結果,我們發現如下規律:

i am 1 child  出現了`1`=  (2^0)
i am 2 child  出現了`2`=  (2^1)
i am 3 child  出現了`4`=  (2^2)
i am 4 child  出現了`8`=  (2^3)
i am 5 child  出現了`16`=  (2^4)
I am parent   出現了`31`=  (2^0) + (2^1) + (2^2) + (2^3) + (2^4)  所有子進程之和

所以每生成一個子進程,就會對應一個父進程。也就是子必有父(感覺像廢話,哈哈)。

上面的代碼其實是創建了 2 * (1 + 2 + 4 + 8 + ... + 2^(N-1)) 個進程。並不是我們想要的N個進程。
這裏用N = 3(上面的例子N=5,5太多啦,這裏展示下過程)作爲例子畫下上面的創建過程:
在這裏插入圖片描述嗯,這個圖,就一目瞭然了。

2 正確的方法

所以正確的做法是,不要讓子再生子了,方法如下:

//fork.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    pid_t pid;
    printf("this is a fork test code\n");
    int i;

    for (i = 0; i < 5; i++) {
        pid = fork();
        //不讓子再生子了
        if (pid == 0) {
            break;
        }
    }

    if (i < 5) {
        sleep(i);
        printf("i am %d child, pid = %u\n", i + 1, getpid());
    } else {
        sleep(i);
        printf("I am parent, pid = %u\n", getpid());
    }
    return 0;
}

編譯然後執行:
圖3
從結果看,可以達到我們想要的結果的。

功成!

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