天天看點

linux 程序控制 fork

1.fork函數建立一個新程序

#include <unistd.h>

pid_t fork(void);

傳回值:子程序中傳回0,父程序中傳回子程序ID,出錯傳回-1

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc,char *argv[])
{
	pid_t pid;
	pid = fork();
	
	if(pid == -1)
	{
		perror("fork error");
	}
	else if(pid == 0)
	{
		printf("this is child process\n");
		printf("child process gets parent processID by getpid() %d\n",getpid());
	}
	else
	{
		printf("this is parent process\n");
		printf("parent process gets parent processID by pid %d\n",pid);
	}
	
	pause();
	
	return 0;
}
           

結論:父程序的pid和子程序getpid相同

2.fork與僵屍程序

僵屍程序:簡單來記憶就是父程序沒有給子程序收屍

僵屍程序:一個程序使用fork建立子程序,如果子程序退出,而父程序并沒有調用wait或waitpid擷取子程序的狀态資訊,那麼子程序的程序描述符仍然儲存在系統中。這種程序稱之為僵死程序。

轉載部分了解:

僵屍程序的産生:

當一個程序建立了一個子程序時,他們的運作時異步的。即父程序無法預知子程序會在什麼時候結束,那麼如果父程序很繁忙來不及wait 子程序時,那麼當子程序結束時,會不會丢失子程序的結束時的狀态資訊呢?處于這種考慮unix提供了一種機制可以保證隻要父程序想知道子程序結束時的資訊,它就可以得到。(不管父程序先于還是後于子程序調用wait,系統都會告訴你子程序的資訊)

這種機制是:在每個程序退出的時候,核心釋放該程序所有的資源,包括打開的檔案,占用的記憶體。但是仍然保留了一些資訊(如程序号pid 退出狀态 運作時間等)。這些保留的資訊直到程序通過調用wait/waitpid時才會釋放。這樣就導緻了一個問題,如果沒有調用wait/waitpid的話,那麼保留的資訊就不會釋放。比如程序号就會被一直占用了。但系統所能使用的程序号的有限的,如果産生大量的僵屍程序,将導緻系統沒有可用的程序号而導緻系統不能建立程序。是以我們應該避免僵屍程序

這裡有一個需要注意的地方。如果子程序先結束而父程序後結束,即子程序結束後,父程序還在繼續運作但是并未調用wait/waitpid那子程序就會成為僵屍程序。

但如果子程序後結束,即父程序先結束了,但沒有調用wait/waitpid來等待子程序的結束,此時子程序還在運作,父程序已經結束。那麼并不會産生僵屍程序。因為每個程序結束時,系統都會掃描目前系統中運作的所有程序,看看有沒有哪個程序是剛剛結束的這個程序的子程序,如果有,就有init來接管它,成為它的父程序。

同樣的在産生僵屍程序的那種情況下,即子程序結束了但父程序還在繼續運作(并未調用wait/waitpid)這段期間,假如父程序異常終止了,那麼該子程序就會自動被init接管。那麼它就不再是僵屍程序了。應為intit會發現并釋放它所占有的資源。(當然如果程序表越大,init發現它接管僵屍程序這個過程就會變得越慢,是以在init為發現他們之前,僵屍程序依舊消耗着系統的資源)

一個僵屍程序的例子:

#include <sys/types.h>
#include <unistd.h>

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

#include <signal.h>
#include <errno.h>
#include <signal.h>

int main(void )
{
    pid_t pid;

    printf("befor fork pid:%d \n", getpid());
    int abc = 10;
    pid = fork(); //errno
    if (pid == -1)
    {
        perror("fork error");
        return -1;
    }
    if (pid > 0)
    {
        abc ++;
        printf("parent: pid:%d \n", getpid());
        printf("abc: %d \n", abc);
        sleep(20);//父程序沒有調用wait方法去收屍,故這20s内,子程序是僵屍程序
    }
    else if (pid == 0)
    {
        abc ++;
        printf("child: %d, parent: %d \n", getpid(), getppid());
        printf("abc: %d \n", abc);
    }

    printf("fork after....\n");
    return 0;
}
           

在fork調用之後,子程序和父程序繼續執行fork調用之後的指令。子程序獲得父程序資料空間、堆和棧的副本,以及程序控制塊PCB,但是并不共享這些存儲空間部分。

fork的一個特性是父程序的所有打開檔案描述符都被複制到子程序中。父、子程序的每個相同的打開描述符共享一個檔案表項。

這種共享檔案的方式使父、子程序對同一檔案使用了一個檔案偏移量

這裡需要注意一下,父、子程序是共享正文段的,但是fork之後,子程序擷取到的PC(程式計數器)已經指向了fork之後的内容,是以,子程序隻執行fork之後的代碼。

fork有兩種用法:

(1)一個父程序希望複制自己,使父、子程序同時執行不同的代碼段。例如,父程序等待用戶端的服務請求,然後fork一個子程序處理這個請求,自己則繼續等待下一個服務請求。當請求達到時,父程序調用fork,子程序處理此請求。父程序繼續等待下一個服務請求。

(2)一個程序要執行一個不同的程式,這對shell是常見的情況。在這種情況下,子程序從fork傳回後立即調用exec。

fork的特殊應用:fork兩次可以避免僵死程序,父程序先fork一個子程序,子程序繼續fork一個孫子程序,然後就直接退出。這樣,父程序就可以很快的wait到子程序,釋放其資源,不需要阻塞,繼續自己的操作;子程序先于孫程序退出也不會産生僵屍程序,孫子程序交由了init程序托管,執行自己的操作而不用擔心了。

子程序成全了父程序,因為子程序的退出父程序不會阻塞在那兒,父程序成功收屍并退出;

子程序成全了孫程序,因為子程序先于孫程序退出,故孫程序托孤給init程序。

#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc,char *argv[])
{
	pid_t pid;
	pid = fork();
	
	if(pid == -1)
	{
		perror("fork error");
	}
	else if(pid == 0)
	{
		printf("this is child process\n");
		printf("child process gets parent processID by getpid() %d\n",getpid());
		
		pid = fork();
		if(pid == -1)
		{
			perror("fork error");
		}
		else if(pid == 0)
		{
			while(1)
			{
				sleep(1);
				printf("this is child's child process\n");
			}
		}
		else
		{
			//子程序先有孫程序退出,孫程序托孤給init
			exit(110);
		}
	}
	else
	{
		printf("this is parent process\n");
		printf("parent process gets parent processID by pid %d\n",pid);
	}
	//父程序等待子程序退出
	wait(NULL);

	return 0;
}
           

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status)

程序一旦調用了wait,就立即阻塞自己,由wait自動分析是否目前程序的某個子程序已經退出,如果讓它找到了這樣一個已經變成僵屍的子程序,wait就會收集這個子程序的資訊,并把它徹底銷毀後傳回;如果沒有找到這樣一個子程序,wait就會一直阻塞在這裡,直到有一個出現為止。

參數status用來儲存被收集程序退出時的一些狀态,它是一個指向int類型的指針。但如果我們對這個子程序是如何死掉的毫不在意,隻想把這個僵屍程序消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為NULL,就象下面這樣:

pid = wait(NULL);

如果成功,wait會傳回被收集的子程序的程序ID,如果調用程序沒有子程序,調用就會失敗,此時wait傳回-1,同時errno被置為ECHILD。

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>


int main(int argc, char *argv[])
{
    pid_t pid;
    pid = fork();
    int status = 0;
    if (pid < 0)
    {
        printf("error occurred!\n");
    }
    else if (pid == 0)
    {
        printf("This is child \n");
        exit(10);
    }
    else
    {
        sleep(5);
        pid_t pc = wait(&status);//就算子程序在父程序前面退出了,wait依然可以收集到子程序的狀态,讓子程序不在是僵屍狀态
        //5秒前子程序是僵屍程序,但是5秒後不是
        //傳回的值為子程序的ID
        printf("status is %d,pro = %d", WEXITSTATUS(status),pc);
        sleep(20);

    }
}
           
/*編寫一個孤兒程序,這個孤兒程序可以同時建立100個僵死程序。
 * fork.c
 *
 *  Created on: 2015年12月6日
 *      Author: Administrator
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

int main(int argc, char* argv[]) {
	pid_t pid = fork();
	if (pid == -1) {
		printf("fork failed %s\n", strerror(errno));
		return -1;
	}
	int i = 0;
	if (pid == 0) {
		for (i = 0; i < 100; i++) {
			pid_t tmp_pid = fork();
			if (tmp_pid == 0) {
				exit(0);
			}
		}
		sleep(10);//10秒後父程序退出,又父程序建立的已經僵屍的程序也會被系統回收
		//exit(0);
	} else if (pid > 0) {
		exit(0);
	}

	return 0;
}
           

繼續閱讀