本節目標:linux
使用fork函數獲得的子進程從父進程的繼承了整個進程的地址空間,包括:進程上下文、進程堆棧、內存信息、打開的文件描述符、信號控制設置、進程優先級、進程組號、當前工做目錄、根目錄、資源限制、控制終端等。app
子進程與父進程的區別在於:函數
一、父進程設置的鎖,子進程不繼承(由於若是是排它鎖,被繼承的話,矛盾了)性能
二、各自的進程ID和父進程ID不一樣this
三、子進程的未決告警被清除;spa
四、子進程的未決信號集設置爲空集。設計
包含頭文件 <sys/types.h> 和 <unistd.h>3d
函數功能:建立一個子進程指針
函數原型code
pid_t fork(void); //一次調用兩次返回值,是在各自的地址空間返回,意味着如今有兩個基本同樣的進程在執行
參數:無參數。
返回值:
流程圖:
父進程調用fork()系統調用,而後陷入內核,進行進程複製,若是成功:
1,則對調用進程即父進程來講返回值爲剛產生的子進程pid,由於進程PCB沒有子進程信息,父進程只能經過這樣得到。
2,對子進程(剛產生的新進程),則返回0,
這時就有兩個進程在接着向下執行
若是失敗,則返回0,調用進程繼續向下執行
注:fork英文意思:分支,fork系統調用複製產生的子進程與父進程(調用進程)基本同樣:代碼段+數據段+堆棧段+PCB,當前的運行環境基本同樣,因此子進程在fork以後開始向下執行,而不會從頭開始執行。
示例程序:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }\ while (0)\ int main(void) { pid_t pid; printf("before calling fork,calling process pid = %d\n",getpid()); pid = fork(); if(pid == -1) ERR_EXIT("fork error"); if(pid == 0){ printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid()); } if(pid > 0){ //sleep(1); printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid); } return 0; }
運行結果:
當沒給父進程沒加sleep時,因爲父進程先執行完,子進程成了孤兒進程,系統將其託孤給了1(init)進程,
因此ppid =1。
當加上sleep後,子進程先執行完:
此次能夠正確看到想要的結果。
fork系統調用以後,父子進程將交替執行,執行順序不定。
若是父進程先退出,子進程還沒退出那麼子進程的父進程將變爲init進程(託孤給了init進程)。(注:任何一個進程都必須有父進程)
若是子進程先退出,父進程還沒退出,那麼子進程必須等到父進程捕獲到了子進程的退出狀態才真正結束,不然這個時候子進程就成爲僵進程(殭屍進程:只保留一些退出信息供父進程查詢)
示例:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }\ while (0)\ int main(void) { pid_t pid; printf("before calling fork,calling process pid = %d\n",getpid()); pid = fork(); if(pid == -1) ERR_EXIT("fork error"); if(pid == 0){ printf("this is child process and child's pid = %d,parent's pid = %d\n",getpid(),getppid()); } if(pid > 0){ sleep(100); printf("this is parent process and pid =%d ,child's pid = %d\n",getpid(),pid); } return 0; }
以上程序跟前面那個基本一致,就是讓父進程睡眠100秒,好讓子進程先退出
運行結果:
從上能夠看到,子進程先退出,但進程列表中還能夠查看到子進程,[a.out] <defunct>,死的意思,即殭屍進程,若是系統中存在過多的殭屍進程,將會使得新的進程不能產生。
linux系統爲了提升系統性能和資源利用率,在fork出一個新進程時,系統並無真正複製一個副本。
若是多個進程要讀取它們本身的那部分資源的副本,那麼複製是沒必要要的。
每一個進程只要保存一個指向這個資源的指針就能夠了。
若是一個進程要修改本身的那份資源的「副本」,那麼就會複製那份資源。這就是寫時複製的含義
在fork還沒實現copy on write以前。Unix設計者很關心fork以後馬上執行exec所形成的地址空間浪費,因此引入了vfork系統調用。
vfork有個限制,子進程必須馬上執行_exit或者exec函數。
即便fork實現了copy on write,效率也沒有vfork高,可是咱們不推薦使用vfork,由於幾乎每個vfork的實現,都或多或少存在必定的問題
vfork:
Linux Description
vfork(), just like fork(2), creates a child process of the calling pro-
cess. For details and return value and errors, see fork(2).
vfork() is a special case of clone(2). It is used to create new pro-
cesses without copying the page tables of the parent process. It may
be useful in performance-sensitive applications where a child will be
created which then immediately issues an execve(2).
vfork() differs from fork(2) in that the parent is suspended until the
child terminates (either normally, by calling _exit(2), or abnormally,
after delivery of a fatal signal), or it makes a call to execve(2).
Until that point, the child shares all memory with its parent, includ-
ing the stack. The child must not return from the current function or
call exit(3), but may call _exit(2).
Signal handlers are inherited, but not shared. Signals to the parent
arrive after the child releases the parent’s memory (i.e., after the
child terminates or calls execve(2)).
示例程序:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }\ while (0)\ int main(void) { pid_t pid; int val = 1; printf("before calling fork, val = %d\n",val); //pid = fork(); pid = vfork(); if(pid == -1) ERR_EXIT("fork error"); if(pid == 0){ printf("chile process,before change val, val = %d\n",val); val++; //sleep(1); printf("this is child process and val = %d\n",val); _exit(0); } if(pid > 0){ sleep(1); //val++; printf("this is parent process and val = %d\n",val); } return 0; }
當調用fork時:
運行結果:
可知寫時複製
當使用vfork但子進程沒使用exit退出時:
結果出錯了,
使用vfork且exit退出:
結果正常,父子進程共享
fork產生的子進程與父進程相同的文件文件描述符指向相同的文件表,引用計數增長,共享文件文件偏移指針
示例程序:
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #define ERR_EXIT(m) \ do\ {\ perror(m);\ exit(EXIT_FAILURE);\ }\ while (0)\ int main(void) { pid_t pid; int fd; fd = open("test.txt",O_WRONLY); if(fd == -1) ERR_EXIT("OPEN ERROR"); pid = fork(); if(pid == -1) ERR_EXIT("fork error"); if(pid == 0){ write(fd,"child",5); } if(pid > 0){ //sleep(1); write(fd,"parent",6); } return 0; }
運行結果:
可知父子進程共享文件偏移指針,父進程寫完後文件偏移到parent後子進程開始接着寫。