天天看點

fork與vfork函數

一、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與vfork函數

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

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

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

fork與vfork函數

在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;
}
           

執行結果:

fork與vfork函數

由執行結果可以看出,子程序直接改變了父程序的變量值,因為子程序在父程序的位址空間中運作。

繼續閱讀