本節目標:
- 複製程式映像
- fork系統呼叫
- 孤兒程式、殭屍程式
- 寫時複製
一,程式複製(或產生)
使用fork函式得到的子程式從父程式的繼承了整個程式的地址空間,包括:程式上下文、程式堆疊、記憶體資訊、開啟的檔案描述符、訊號控制設定、程式優先順序、程式組號、當前工作目錄、根目錄、資源限制、控制終端等。
子程式與父程式的區別在於:
1、父程式設定的鎖,子程式不繼承(因為如果是排它鎖,被繼承的話,矛盾了)
2、各自的程式ID和父程式ID不同
3、子程式的未決告警被清除;
4、子程式的未決訊號集設定為空集。
二,fork系統呼叫
包含標頭檔案 <sys/types.h> 和 <unistd.h>
函式功能:建立一個子程式
函式原型
pid_t fork(void); //一次呼叫兩次返回值,是在各自的地址空間返回,意味著現在有兩個基本一樣的程式在執行
引數:無引數。
返回值:
- 如果成功建立一個子程式,對於父程式來說返回子程式ID
- 如果成功建立一個子程式,對於子程式來說返回值為0
- 如果為-1表示建立失敗
流程圖:
父程式呼叫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 和vfork:
在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之後父子程式共享檔案:
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後子程式開始接著寫。