摘要:本文詳解介紹fork()函數的基本使用,以及父子進程之間的關係.子進程對變量的改變不會影響到父進程、子進程對父進程文件流緩衝區的處理和子進程對父進程打開的文件描述符的處理.
創建進程
1.fork()函數
函數定義:#include <unistd.h>
pid_t fork(void);
返回值:如果返回值大於零,表明處於父進程上下文環境中,返回值是子進程的ID.如果返回值是零,表明處於子進程上下文環境中.其他返回值(小於零)表明調用fork()函數出錯,仍處於父進程上下文環境中.
函數說明:
由fork()函數創建的新進程被稱爲子進程.fork()函數被調用一次,但返回兩次,兩次的返回值不同,子進程的返回值是0,父進程的返回值是新進程的進程ID.
一個進程的子進程可以有多個,並且沒有一個函數使一個進程可以獲得其所有子進程的進程ID.
一個進程只會有一個父進程,所以任意一個子進程都可以通過調用getppid()函數獲取其父進程的進程ID.
fork()函數調用成功後,將爲子進程申請PCB和用戶內存空間.子進程是父進程的副本.在用戶空間將複製父進程用戶空間所有數據(代碼段、數據段、BBS、堆、棧),複製父進程內核空間PCB中的絕大多數信息.子進程從父進程繼承下例屬性:有效用戶、組號、進程組號、環境變量、對文件的執行時關閉標誌、信號處理方式設置、信號屏蔽集合、當前工作目錄、根目錄、文件模式掩碼、文件大小限制和打開的文件描述符(特別注意:共用同一文件表項).
子進程在創建後和父進程同時執行,競爭系統資源,誰先誰後,取決於內核所使用調度算法.子進程的執行位置爲fork返回位置.
2.創建子進程
例子1:演示fork函數的基本使用方法.#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
printf("fork error\n");
printf("a example of fork,pid = %d\n",getpid());
return 0;
}
輸出:
:a example of fork,pid = 2798:a example of fork,pid = 2797
從例子1可以看出,fork()函數後的代碼在子進程中也被執行.實際上,其他代碼也在子進程的代碼段中,只是子進程執行的位置爲fork返回位置,其之前的代碼無法執行罷了.
例子2:返回值大於0(返回PID)的代碼在父進程執行,返回值爲0則在子進程執行.
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
if((pid=fork())==-1)
{
printf("fork error\n");
}
else if(pid == 0 )
{
printf("pid:%d in the child process \n",pid);
}
else
{
printf("pid:%d in the parent process\n",pid);
}
return 0;
}
輸出:
:pid:2923 in the parent process:pid:0 in the child process
3.子進程對變量的改變不會影響到父進程
例子3:子進程和父進程各有一份變量的副本#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int glob = 10;
int main()
{
int var = 100;
pid_t pid = getpid();
printf("before fork:\n");
printf("pid=%d, glob=%d, var=%d\n",pid,glob,var);
printf("after fork:\n");
if((pid=fork())<0)
{
printf("error fork:%m\n");
exit(-1);
}
else if(pid==0)
{
glob++;
var++;
}
else
{
sleep(2);
}
printf("pid = %d, glob = %d, var = %d\n",getpid(),glob,var);
return 0;
}
輸出:
:before fork::pid=2664, glob=10, var=100
:after fork:
:pid = 2665, glob = 11, var = 101
:pid = 2664, glob = 10, var = 100
可以看出,對於變量glob和var,在子進程中進行了自加,但是在父進程中,變量的值沒有改變;顯然,父子進程各自擁有這一變量的副本,互不影響.
4.子進程對父進程文件流緩衝區的處理
文件流緩衝區的資源位於用戶空間,因此,在創建子進程時,子進程的用戶空間將複製父進程的用戶空間所有信息,顯然,也包含流緩衝區的內容.如果留緩衝區中有臨時的信息,則通同樣複製到子進程的用戶空間流緩衝中.例子4:子進程對父進程文件流緩衝區的處理.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t pid;
//有回車,先輸出
printf("before fork,have enter\n");
//沒有回車,先輸出到緩衝區
printf("before fork,no enter:pid=%d\t",getpid());
pid = fork();
if(pid == 0)
{
printf("\nchild,after fork:pid=%d\n",getpid());
}
else
{
printf("\nparent,after fork: pid=%d\n",getpid());
}
return 0;
}
輸出:
:before fork,have enter:before fork,no enter:pid=2977
:parent,after fork: pid=2977
:before fork,no enter:pid=2977
:child,after fork:pid=2978
爲什麼“before fork,have enter”只輸出一次?
首先明確一點,如果標準輸出連到終端設備,則它是行緩衝或者全緩衝.“before fork,have enter”只輸出一次,是因爲標準輸出緩衝區由換行符沖洗,在下面創建子進程時,複製的緩衝區已經沒有該數據了.
爲什麼“before fork,no enter:pid=2977”輸出兩次?
子進程和父進程都輸出了“before fork,no enter:pid=2977”,但是,爲什麼子進程會輸出呢?子進程開始執行的位置是在fork函數返回處,不會執行fork函數之前的代碼,雖然該代碼被複制到子進程中.之所以出現兩次輸出,是因爲父進程中的“printf("before fork,no enter:pid=%d\t",getpid());”沒有回車,就是這條語句輸出的結果還在緩衝區,緩衝區沒有沖洗,沒有真正輸出.在創建子進程時,這父進程的緩衝區也會被複制到子進程的進程空間中,所以子進程在輸出時,刷新緩衝區時,也會將“printf("before fork,no enter:pid=%d\t",getpid());”的結果輸出.
如果程序是這樣子運行 ./a.out >out.txt.查看out.txt文件,可得到以下結果.
before fork,have enter
before fork,no enter:pid=2716
parent,after fork: pid=2716
before fork,have enter ====》多了這一行
before fork,no enter:pid=2716
child,after fork:pid=2717
爲什麼?“before fork,have enter”輸出了兩次.
標準輸出重定向到文件時,在調用fork函數時,該數據還在緩衝區,沒有輸出,這裏的換行符沒有像上面那樣起到刷新緩衝區的作用,只是簡單的換行,所以當父進程將數據複製到子進程時,該緩衝區的數據也被複制到子進程了.
5.子進程對父進程打開的文件描述符的處理
fork函數創建子進程後,子進程將複製父進程的數據段、BBS段.代碼段.堆空間、棧空間和文件描述符,而對於文件描述符關聯的內核文件表項,則是此採用共享的方式.例子5: 子進程對父進程打開的文件描述符的處理
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main()
{
pid_t pid;
int fd;
int i=1;
int status;
char *ch1="advanced";
char *ch2=" programming";
char *ch3=" int the unix Environment";
fd = open("test.txt",O_RDWR|O_CREAT,0644);
if(fd==-1)
{
printf("open or creat file error:%m\n");
exit(-1);
}
write(fd,ch1,strlen(ch1));
pid=fork();
if(pid==-1)
{
printf("error fork\n");
exit(-1);
}
else if(pid==0)
{
i=2;
printf("in child process\n");
printf("i=%d\n",i);
if(write(fd,ch2,strlen(ch2))==-1)
{
printf("child write error:%m\n");
exit(-1);
}
}
else
{
sleep(1);
printf("int parent process\n");
printf("i=%d\n",i);
if(write(fd,ch3,strlen(ch3))==-1)
{
printf("parent wirte error%m\n");
exit(-1);
}
wait(&status);
}
return 0;
}
輸出:cat test.txt
:advanced programming int the unix Environment可以看出,父子進程共同對一個文件操作,且寫入數據不交叉覆蓋,說明父子進程共享同一個文件偏移量,共享文件表項.如圖1子進程對打開文件的處理方式.
圖1 子進程對打開文件的處理方式
筆者:個人能力有限,只是學習參考...讀者若發現文中錯誤,敬請提出.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------勿在浮沙築高臺,靜下心來,慢慢地沉澱---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------