fork函式的學習--深入瞭解計算機系統

熒惑守心ZL發表於2020-12-27

1、介紹

       父程式可以通過呼叫fork函式建立一一個新的執行的子程式

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

pid_t fork(void);
//返回:子程式返回0,父程式返回子程式的PID,如果出錯,則為-1。

       新建立的子程式幾乎但不完全與父程式相同。子程式得到與父程式使用者級虛擬地址空間相同的(但是獨立的)一份副本,包括程式碼和資料段、堆、共享庫以及使用者棧。子程式還獲得與父程式任何開啟檔案描述符相同的副本,這就意味著當父程式呼叫fork 時,子程式可以讀寫父,程式中開啟的任何檔案。父程式和新建立的子程式之間最大的區別在於它們有不同的PID。
       fork函式是有趣的(也常常令人迷惑),因為它只被呼叫一次,卻會返回兩次:一次是在呼叫程式(父程式)中,一次是在新建立的子程式中。在父程式中,fork 返回子程式的PID。在子程式中,fork返回0。 因為子程式的PID總是為非零,返回值就提供一個明確的方法來分辨程式是在父程式還是在子程式中執行。
       程式碼框1 展示了一個使用fork 建立子程式的父程式的示例。當fork呼叫在第6行返回時,在父程式和子程式中x的值都為1。子程式在第8行加一-並輸出它的x的副本。相似地,父程式在第13行減- -並輸出它的x的副本。

int main() 
{
	pid_t pid;
	int x=1;
	pid = Fork() ;
	if(pid == 0){ /*Child*/
		printf("child : x=%d\n", ++x) ;
		exit(0) ;
	}
	/* Parent */
	printf ("parent: x=%d\n"--x);
	exit(0) ;
}

                            程式碼框1

       當在Unix系統上執行這個程式時,我們得到程式碼框2中的結果:

linux> . /fork
parent: x=0
child : x=2

                            程式碼框2

這個簡單的例子有一些微妙的方面。
       ●呼叫一次,返回兩次。fork 函式被父程式呼叫一次,但是卻返回兩次——一次是返回到父程式,一次是返回到新建立的子程式。對於只建立一個子程式的程式來說,這還是相當簡單直接的。但是具有多個fork 例項的程式可能就會令人迷惑,需要仔細地推敲了。
       ●併發執行。父程式和子程式是併發執行的獨立程式。核心能夠以任意方式交替執行它們的邏輯控制流中的指令。在我們的系統上執行這個程式時,父程式先完成它的printf語句,然後是子程式。然而,在另一個系統上可能正好相反。一般而言,作為程式設計師,我們決不能對不同程式中指令的交替執行做任何假設。
       ●相同但是獨立的地址空間。如果能夠在fork函式在父程式和子程式中返回後立即暫停這兩個程式,我們會看到兩個程式的地址空間都是相同的。每個程式有相同的使用者棧、相同的本地變數值、相同的堆、相同的全域性變數值,以及相同的程式碼。因此,在我們的示例程式中,當fork函式在第6行返回時,本地變數x在父程式和子程式中都為1。然而,因為父程式和子程式是獨立的程式,它們都有自己的私有地址空間。後面,父程式和子程式對x所做的任何改變都是獨立的,不會反映在另一個程式的記憶體中。這就是為什麼當父程式和子程式呼叫它們各自的printf語句時,它們中的變數x會有不同的值。
       ●共享檔案。當執行這個示例程式時,我們注意到父程式和子程式都把它們的輸出顯示在螢幕.上。原因是子程式繼承了父程式所有的開啟檔案。當父程式呼叫fork時,stdout檔案是開啟的,並指向螢幕。子程式繼承了這個檔案,因此它的輸出也是指向螢幕的。

       如果你是第一次學習fork函式,畫程式圖通常會有所幫助,程式圖是刻畫程式語句的偏序的一種簡單的前趨圖。每個頂點a對應於一條程式語句的執行。有向邊a-→b表示語句a發生在語句b之前。邊上可以標記出一些資訊,例如一個變數的當前值。對應於printf語句的頂點可以標記上printf的輸出。每張圖從一個頂點開始,對應於呼叫main的父程式。這個頂點沒有人邊,並且只有一個出邊。每個程式的頂點序列結束於一個對應於exit呼叫的頂點。這個頂點只有一條人邊,沒有出邊。
       例如,圖1展示了程式碼框1中示例程式的程式圖。初始時,父程式將變數x設定為1。父程式呼叫fork, 建立一個子程式,它在自己的私有地址空間中與父程式併發執行。對於執行在單處理器上的程式,對應程式圖中所有頂點的拓撲排序(topological sort)表 示程式中語句的一個可行的全序排列。下面是一個理解拓撲排序概念的簡單方法:給定程式圖中頂點的一個排列,把頂點序列從左到右寫成-行,然後畫出每條有向邊。排列是一個拓撲排序,當且僅當畫出的每條邊的方向都是從左往右的。因此,在程式碼框1的示例程式中,父程式和子程式的printf語句可以以任意先後順序執行,因為每種順序都對應於圖頂點的某種拓撲排序。
fork函式的學習--深入瞭解計算機系統
                            圖1 程式碼框1中示例程式的程式圖

       程式圖特別有助於理解帶有巢狀fork呼叫的程式。例如,程式碼框3中的程式原始碼中兩次呼叫了fork。 對應的程式圖(圖2)可幫助我們看清這個程式執行了四個程式,每個都呼叫了一次printf,這些printf可以以任意順序執行。

int main()
{
	Fork();
	Fork();
	printf ("hel1o\n");
	exit(0);
}

                            程式碼框3
fork函式的學習--深入瞭解計算機系統
                            圖2 程式碼框2中巢狀fork的程式圖

2、例項

1、fork0

/*
 * fork0 - The simplest fork example
 * Call once, return twice
 * Creates child that is identical to parent
 * Returns 0 to child process
 * Returns child PID to parent process
 */
void fork0() 
{
    if (fork() == 0) {
	printf("Hello from child\n");
    }
    else {
	printf("Hello from parent\n");
    }
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

2、fork1

/* 
 * fork1 - Simple fork example 
 * Parent and child both run same code
 * Child starts with identical private state
 */
void fork1()
{
    int x = 1;
    pid_t pid = fork();

    if (pid == 0) {
	printf("Child has x = %d\n", ++x);
    } 
    else {
	printf("Parent has x = %d\n", --x);
    }
    printf("Bye from process %d with x = %d\n", getpid(), x);
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

3、fork2

/*
 * fork2 - Two consecutive forks
 * Both parent and child can continue forking
 * Ordering undetermined
 */
void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

4、fork3

/*
 * fork3 - Three consective forks
 * Parent and child can continue forking
 */
void fork3()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("L2\n");    
    fork();
    printf("Bye\n");
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

5、fork4

/* 
 * fork4 - Nested forks in parents
 */
void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
	printf("L1\n");    
	if (fork() != 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

6、fork5

/*
 * fork5 - Nested forks in children
 */
void fork5()
{
    printf("L0\n");
    if (fork() == 0) {
	printf("L1\n");    
	if (fork() == 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

7、fork6

void cleanup(void) {
    printf("Cleaning up\n");
}

/*
 * fork6 - Exit system call terminates process
 * call once, return never
 */
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

8、fork7

/* 
 * fork7 - Demonstration of zombies.
 * Run in background and then perform ps 
 */
void fork7()
{
    if (fork() == 0) {
	/* Child */
	printf("Terminating Child, PID = %d\n", getpid());
	exit(0);
    } else {
	printf("Running Parent, PID = %d\n", getpid());
	while (1)
	    ; /* Infinite loop */
    }
}
fork函式的學習--深入瞭解計算機系統        子程式結束後,如果父程式一直不結束,會變為僵死程式,需要殺死它的父程式才能殺死僵死程式

9、fork8

/* 
 * fork8 - Demonstration of nonterminating child.  
 * Child still running even though parent terminated
 * Must kill explicitly
 */
void fork8()
{
    if (fork() == 0) {
	/* Child */
	printf("Running Child, PID = %d\n",
	       getpid());
	while (1)
	    ; /* Infinite loop */
    } else {
	printf("Terminating Parent, PID = %d\n",
	       getpid());
	exit(0);
    }
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

       子程式沒有結束

10、fork9

/*
 * fork9 - synchronizing with and reaping children (wait)
 */
void fork9()
{
    int child_status;

    if (fork() == 0) {
	printf("HC: hello from child\n");
        exit(0);
    } else {
	printf("HP: hello from parent\n");
	wait(&child_status);
	printf("CT: child has terminated\n");
    }
    printf("Bye\n");
}
fork函式的學習--深入瞭解計算機系統 fork函式的學習--深入瞭解計算機系統

11、fork10

#define N 5
/* 
 * fork10 - Synchronizing with multiple children (wait)
 * Reaps children in arbitrary order
 * WIFEXITED and WEXITSTATUS to get info about terminated children
 */
void fork10()
{
    pid_t pid[N];
    int i, child_status;

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    exit(100+i); /* Child */
	}
    for (i = 0; i < N; i++) { /* Parent */
	pid_t wpid = wait(&child_status);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminate abnormally\n", wpid);
    }
}
fork函式的學習--深入瞭解計算機系統        這個函式是建立了N個子程式,然後父程式也執行五次等待,每次等待N個子程式中任意一個結束後列印一次,然後繼續迴圈

12、fork11

/* 
 * fork11 - Using waitpid to reap specific children
 * Reaps children in reverse order
 */
void fork11()
{
    pid_t pid[N];
    int i;
    int child_status;

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0)
	    exit(100+i); /* Child */
    for (i = N-1; i >= 0; i--) {
	pid_t wpid = waitpid(pid[i], &child_status, 0);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminate abnormally\n", wpid);
    }
}
fork函式的學習--深入瞭解計算機系統        這裡跟fork10基本一致,只是wait換成了waitpid,且waitpid指定了具體的pid號,即指定了具體的等待的子程式

13、fork12

/********* 
 * Signals
 *********/

/*
 * fork12 - Sending signals with the kill() function
 */
void fork12()
{
    pid_t pid[N];
    int i;
    int child_status;

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    /* Child: Infinite Loop */
	    while(1)
		;
	}
    for (i = 0; i < N; i++) {
	printf("Killing process %d\n", pid[i]);
	kill(pid[i], SIGINT);
    }

    for (i = 0; i < N; i++) {
	pid_t wpid = wait(&child_status);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminated abnormally\n", wpid);
    }
}
fork函式的學習--深入瞭解計算機系統        這裡跟fork10差不多,只是父程式會利用kill函式向指定一個子程式號傳送終止訊號,被kill的子程式是異常退出的

14、fork13

/*
 * int_handler - SIGINT handler
 */
void int_handler(int sig)
{
    printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
    exit(0);
}

/*
 * fork13 - Simple signal handler example
 */
void fork13()
{
    pid_t pid[N];
    int i;
    int child_status;

    signal(SIGINT, int_handler);
    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    /* Child: Infinite Loop */
	    while(1)
		;
	}

    for (i = 0; i < N; i++) {
	printf("Killing process %d\n", pid[i]);
	kill(pid[i], SIGINT);
    }

    for (i = 0; i < N; i++) {
	pid_t wpid = wait(&child_status);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminated abnormally\n", wpid);
    }
}
fork函式的學習--深入瞭解計算機系統        這裡仍然是利用kill函式向子程式發訊號,子程式被kill時,本來是異常終止,但是子程式的signal函式接收到了SIGINT訊號,呼叫handler函式處理,並正確的退出,因此子程式並不算是異常退出

15、fork14

/*
 * child_handler - SIGCHLD handler that reaps one terminated child
 */
int ccount = 0;
void child_handler(int sig)
{
    int child_status;
    pid_t pid = wait(&child_status);
    ccount--;
    printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
    fflush(stdout); /* Unsafe */
}

/*
 * fork14 - Signal funkiness: Pending signals are not queued
 */
void fork14()
{
    pid_t pid[N];
    int i;
    ccount = N;
    signal(SIGCHLD, child_handler);

    for (i = 0; i < N; i++) {
	if ((pid[i] = fork()) == 0) {
	    sleep(1);
	    exit(0);  /* Child: Exit */
	}
    }
    while (ccount > 0)
	;
}
fork函式的學習--深入瞭解計算機系統        子程式正常退出時,會向父程式傳送SIGCHLD訊號,signal函式接收到該訊號,呼叫handler函式處理,handler函式等待一個子程式結束,然後ccount會減一,父程式由於ccount = N,一開始會陷入死迴圈,直到子程式全部被回收為止,但是如果已有一個子程式先sleep結束向父程式傳送訊號,到父程式處理該訊號之前,所有的子程式傳送的SIGCHLD訊號都會被丟棄,因此這個函式的輸出是不確定的,並且如果子程式未全部結束,父程式會一直持續下去,此時掛起當前程式後使用ps命令檢視會看到已經結束的子程式會變成僵死程式

16、fork15

/*

  • child_handler2 - SIGCHLD handler that reaps all terminated children
    /
    void child_handler2(int sig)
    {
    int child_status;
    pid_t pid;
    while ((pid = wait(&child_status)) > 0) {
    ccount–;
    printf(“Received signal %d from process %d\n”, sig, pid); /
    Unsafe /
    fflush(stdout); /
    Unsafe */
    }
    }

/*

  • fork15 - Using a handler that reaps multiple children
    */
    void fork15()
    {
    pid_t pid[N];
    int i;
    ccount = N;

    signal(SIGCHLD, child_handler2);

    for (i = 0; i < N; i++)
    if ((pid[i] = for

k()) == 0) {
	    sleep(1);
	    exit(0); /* Child: Exit */

	}
    while (ccount > 0) {
	pause();
    }
}
fork函式的學習--深入瞭解計算機系統        這裡跟14略有不同,父程式在迴圈時會pause,pause是掛起該程式等待一個訊號出現才繼續,同時,handler函式做了迴圈處理,當父程式接收到訊號後,雖然pause函式結束了,但是handler接管了執行權,handler的迴圈會等待五個子程式全部退出,因此能確保子程式全部被回收,且父程式也能正常退出

17、fork16

/* 
 * fork16 - Demonstration of using /bin/kill program 
 */
void fork16() 
{
    if (fork() == 0) {
	printf("Child1: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
	if (fork() == 0)
	    printf("Child2: pid=%d pgrp=%d\n",
		   getpid(), getpgrp());
	while(1);
    }
} 
fork函式的學習--深入瞭解計算機系統        子程式由哪個程式建立,就屬於哪個程式組,因此這三個程式都是一個組的

18、fork17

/* 
 * Demonstration of using ctrl-c and ctrl-z 
 */
void fork17() 
{
    if (fork() == 0) {
	printf("Child: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    else {
	printf("Parent: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    while(1);
} 
fork函式的學習--深入瞭解計算機系統        這個和fork16一模一樣,目的在於明白ctrl + z和ctrl + c的區別

相關文章