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;
}
編譯然後執行:
從結果看,可以達到我們想要的結果的。
功成!