fork()函数详解
目錄
1.基本了解:
2.fork函數的了解:
3.僵死進程:?
4. fork和多線程:
1.多線程中某個線程調用 fork(),子進程會有和父進程相同數量的線程嗎?
2.父進程被加鎖的互斥鎖 fork 后在子進程中是否已經加鎖?
5.寫時拷貝:
1.基本了解:
一個進程,包括代碼、數據和分配給進程的資源。fork 函數會新生成一個進程,調用 fork 函數的進程為父進程,新生成的進程為子進程。在父進程中返回子進程的 pid,在子進程中返回 0,失敗返回-1。
為什么兩個進程的fpid不同呢,這與fork函數的特性有關。fork調用的一個奇妙之處就是它僅僅被調用一次,卻能夠返回兩次,它可能有三種不同的返回值:
????1)在父進程中,fork返回新創建子進程的進程ID;
??? 2)在子進程中,fork返回0;
??? 3)如果出現錯誤,fork返回一個負值;
一個進程調用fork()函數后,系統先給新的進程分配資源,例如存儲數據和代碼的空間。然后把原來的進程的所有值都復制到新的新進程中,只有少數值與原來的進程的值不同。相當于克隆了一個自己。
2.fork函數的了解:
pid_t? fork(void);
函數返回類型 pid_t 實質是 int 類型,Linux 內核 2.4.0 版本的定義是:
typedef? int? _kenrnel_pid_t;
typedef??_kenrnel_pid_t? pid_t;
?fork 函數會新生成一個進程,調用 fork 函數的進程為父進程,新生成的進程為子進程。
在父進程中返回子進程的 pid,在子進程中返回 0,失敗返回-1(此時才能更好理解他的返回值)。
舉個例子:
1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <assert.h>6 7 int main()8 { char * s = NULL;9 int n = 0;10 11 pid_t pid = fork();12 assert( pid != -1 );13 if ( pid == 0 )14 {15 s = "child";16 n = 4;17 }18 else19 {20 s = "parent";21 n = 10;22 }23 24 int i = 0;25 26 for(; i < n; i++ )27 {28 printf("pid=%d,s=%s\n",getpid(),s);29 sleep(1);30 }31 32 exit(0);33 }來看看運行結果:
?共打印了4次child,10次parent。fork()產生的子進程打印了4次child.
此時還應注意fork()產生的子進程和父進程之間的并發問題。
3.僵死進程:?
(1) 僵死進程概念:子進程先于父進程結束,父進程沒有調用 wait 獲取子進程退出碼。
(2)僵死進程的危害:
- 僵死進程的PID還占據著,意味著海量的子進程會占據滿進程表項,會使后來的進程無法fork.
- 僵尸進程的內核棧無法被釋放掉,為啥會留著它的內核棧,因為在棧的最低端,有著thread_info結構,它包含著 struct_task 結構,這里面包含著一些退出信息
(3)如何處理僵死進程:父進程通過調用 wait()完成。
wait函數:
?pid_t? wait(int *status);
??進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵尸的子進程,wait就會收集這個子進程的信息,并把它徹底銷毀后返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這里,直到有一個出現為止。?
下面看例子:
1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <assert.h>6 #include <sys/wait.h>7 int main( int argc, char* argv[], char* envp[])8 { char * s = NULL;9 int n = 0;10 11 pid_t pid = fork();12 assert( pid != -1 );13 if ( pid == 0 )14 {15 s = "child";16 n = 4;17 }18 else19 {20 s = "parent";21 n = 10;22 23 int val = 0;24 int id = wait(&val);25 26 if ( WIFEXITED(val) )27 {28 printf("id=%d,val=%d\n",id,WEXITSTATUS(val));29 }30 }31 int i = 0;32 33 for(; i < n; i++ )34 {35 printf("pid=%d,s=%s\n",getpid(),s);36 sleep(1);37 }38 39 exit(0);40 }?運行結果如圖:
引入wait函數可以使先處理完子進程,再去處理父進程。可以有效避免僵死進程。
4. fork和多線程:
1.多線程中某個線程調用 fork(),子進程會有和父進程相同數量的線程嗎?
在Linux中,fork的時候只復制當前線程到子進程,也就是說除了調用fork的線程外,其他線程在子進程中“蒸發”了。
2.父進程被加鎖的互斥鎖 fork 后在子進程中是否已經加鎖?
調用fork的時候,會復制父進程的所有鎖到子進程中。
假設在fork之前,一個線程對某個鎖進行的lock操作,即持有了該鎖,然后另外一個線程調用了fork創建子進程。可是在子進程中持有那個鎖的線程卻"消失"了,從子進程的角度來看,這個鎖被“永久”的上鎖了,因為它的持有者“蒸發”了。
5.寫時拷貝:
傳統的fork()系統調用直接把所有的資源復制給新創建的進程。這種實現過于簡單并且效率低下,因為它拷貝的數據也許并不共享,更糟的情況是,如果新進程打算立即執行一個新的映像,那么所有的拷貝都將前功盡棄。Linux的fork()使用寫時拷貝(copy-on-write)頁實現。
寫時拷貝是-一種可以推遲甚至免除拷貝數據的技術。內核此時并不復制整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。只有在需要寫入的時候,數據才會被復制,從而使各個進程擁有各自的拷貝。也就是說,資源的復制只有在需要寫入的時候才進行,在此之前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在頁根本不會被寫入的情況下——舉例來說,fork()后立即調用exec()—-它們就無需復制了。fork()的實際開銷就是復制父進程的頁表以及給子進程創建惟一的進程描述符。在一般情況下,進程創建后都會馬上運行一個可執行的文件,這種優化可以避免拷貝大量根本就不會被使用的數據(地址空間里常常包含數十兆的數據)。由于Unix強調進程快速執行的能力,所以這個優化是很重要的。?
總結
以上是生活随笔為你收集整理的fork()函数详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 数学建模——主成分分析及spss软件操作
- 下一篇: Linux fork函数