fork與vfork函式

mi_rencontre發表於2016-06-28

一、fork函式

pid_t fork(void);            返回值:子程式中返回0,父程式中返回子程式ID,出錯返回-1。

一個現有程式可以呼叫fork建立一個新程式。

子程式是父程式的副本。例如:子程式獲得父程式資料空間、堆和棧的副本(主要是資料結構的副本)。父子程式不共享這些儲存空間部分。父子程式共享正文段。 由於fork之後經常歸屬exec,所以現在很多實現並不執行一個父程式資料段、棧和堆的完全複製。作為替代,使用了寫時複製(Copy-On-Write)技術。這些區域由父子程式共享,而且核心將他們的訪問許可權改變為只讀的。如果父子程式中的任一個試圖修改這些區域,則核心只為修改區域的那塊記憶體製作一個副本。

演示fork函式:

#include<stdio.h>
#include<unistd.h>

int glob = 6;
char buf[] = "a write to stdout\n";

int main()
{
	int var;
	pid_t pid;

	var = 88;
	if(write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
		perror("write error");
	printf("before fork\n");

	pid = fork();
	if(pid < 0)
	{
		perror("fork error");
	}
	else if(pid == 0) //child
	{
		glob++;
		var++;
	}
	else //parent
	{
		sleep(2);
	}

	printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
	return 0;
}

執行結果:

由執行結果可以看出,子程式對變數所作的改變並不影響父程式中該變數的值。

一般來說fork之後父程式和子程式的執行順序是不確定的,這取決於核心的排程演算法。在上面的程式中,父程式是自己休眠2秒鐘,以使子程式先執行。程式中fork與I/O函式之間的關係:write是不帶緩衝的,因為在fork之前呼叫write,所以其資料只寫到標準輸出一次。標準I/O是緩衝的,如果標準輸出到終端裝置,則它是行緩衝,否則它是全緩衝。當以互動方式執行該程式時,只得到printf輸出的行一次,因為標準輸出到終端緩衝區由換行符沖洗。但將標準輸出重定向到一個⽂檔案時,由於緩衝區是全緩衝,遇到換行符不輸出,當呼叫fork時,其printf的資料仍然在緩衝區中,該資料將被複制到子程式中,該緩衝區也被複制到子程式中。於是父子程式的都有了帶改行內容的標準 I/O緩衝區,所以每個程式終止時,會沖洗其緩衝區中的資料,得到第一個printf輸出兩次。

fork的一個特性是父程式的所有開啟檔案描述符都被複制到子程式中。父子程式的每個相同的開啟描述符共享一個檔案表項。假設一個程式有三個不同的開啟檔案,在從fork返回時,有如下所示結構:

在fork之後處理的檔案描述符有兩種常見的情況:

1. 父程式等待子程式完成。在這種情況下,父程式無需對其描述符做任何處理。當子程式終止後,子程式對檔案偏移量的修改已執行的更新。

2. 父子程式各自執行不同的程式段。這種情況下,在fork之後,父子程式各自關閉他們不需要使用的檔案描述符,這樣就不會干擾對方使用檔案描述符。這種方法在網路服務程式中經常使用。

父子程式之間的區別:

1. fork的返回值

2. 程式ID不同

3. 具有不同的父程式ID

4. 子程式的tms_utime、tms_stime、tms_cutime及tms_ustime均被設定為0

5. 父程式設定的檔案鎖不會被子程式繼承

6. 子程式的未處理鬧鐘被清除

7. 子程式的未處理訊號集被設定為空集

fork有下面兩種用法:

1. 一個父程式希望複製自己,使父子程式同時執行不同的程式碼段。例如,父程式等待客戶端請求,生成子程式來處理請求。

2. 一個程式要執行一個不同的程式。例如子程式從fork返回後,呼叫exec函式。

fork呼叫失敗的原因:

1. 系統中已經有了太多的程式

2. 實際使用者ID的程式總數超過了系統限制

二、vfork函式

vfork函式的呼叫序列和返回值與fork相同。但兩者的語義不同。

vfork用於建立一個新程式,而該新程式的目的是exec一個新程式。vfork與fork都建立一 個子程式,但它不將父程式的地址空間複製到子程式中,因為子程式會立即呼叫 exec,於是不會存訪問該地址空間。相反,在子程式呼叫exec或exit之前,它在父程式的空間中執行,也就是說會更改父程式的資料段、棧和堆。vfork和fork另一區別在於 :vfork保證子程式先執行,在它呼叫exec或(exit)之後父程式才可能被排程執行。

演示vfork函式:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>

int g_val = 0;

void fun()
{
	printf("child exit\n");
}

int main()
{
	int val = 0;
	pid_t id = vfork();
	if(id < 0)
	{
		exit(1);
	}
	else if(id == 0) //child
	{
		atexit(fun);
		printf("this is child process.\n");
		++g_val;
		++val;
		sleep(3);
		exit(0);
	}
	else
	{
		printf("this is father process\n");
		printf("father exit, g_val = %d, val = %d\n", g_val, val);
	}
	return 0;
}

執行結果:

由執行結果可以看出,子程式直接改變了父程式的變數值,因為子程式在父程式的地址空間中執行。

相關文章