fork-----------一个让人迷惑的函数

fork函数简介

通过调用fork函数进程可以创造一个与之一模一样的进程。
我们称被创造的进程为子进程,进程本身为父进程。
何为一模一样?
意思就是新创建的子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。
同时,子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用Fork函数时,子进程可以读写父进程中打开的任何文件。
我们通过他们之间的不同标志(PID)来区分他们。父进程的PID为1,子进程的PID为0。
既然我们创造的子进程可以调用与父进程一样的函数,那么他们在函数结束的时候都会返回一个值,所以我们调用一次fork,则返回两次。

处理fork函数的方法:画进程图

一般当我们遇到fork函数时,我们的应对方法是画进程图:
进程图是刻画程序语句的偏序的一种简单的前趋图。每个顶点a对应于一条程序语句的执行。有向边a->b表示语句a发生在语句b之前。边上可以标注一些信息,例如一个变量的当前值。对应于printf语句的顶点可以标记上printf的输出(这里输出的都是hello便没有标记)。每张图从一个顶点开始,对应于调用main的父进程。这个顶点没有入边并且只有一个出边。每个进程顶点序列结束于一个对应exit调用的顶点。这个顶点只有一条入边没有出边。

fork的列子

/*
 * forks.c - Examples of Unix process control
 */
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h> 
#include <signal.h>

/*
 * 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");
    }
}

/* 
 * 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);
}

/*
 * 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");
}


/*
 * 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");
}

/* 
 * 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");
}

/*
 * 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");
}

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

/*
 * fork6 - Exit system call terminates process
 * call once, return never
 */
void fork6()
{
    atexit(cleanup);
    fork();
    exit(0);
}

/* 
 * 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 */
    }
}

/* 
 * 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);
    }
}

/*
 * 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");
}

#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);
    }
}

/* 
 * 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);
    }
}


/********* 
 * 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);
    }
}

/*
 * 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);
    }
}


/*
 * 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)
	;
}


/*
 * 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] = fork()) == 0) {
	    sleep(1);
	    exit(0); /* Child: Exit */

	}
    while (ccount > 0) {
	pause();
    }
}

/* 
 * 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);
    }
} 

/* 
 * 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);
} 


int main(int argc, char *argv[])
{
    int option = 0;
    if (argc > 1)
	option = atoi(argv[1]);
    switch(option) {
    case 0: fork0();
	break;
    case 1: fork1();
	break;
    case 2: fork2();
	break;
    case 3: fork3();
	break;
    case 4: fork4();
	break;
    case 5: fork5();
	break;
    case 6: fork6();
	break;
    case 7: fork7();
	break;
    case 8: fork8();
	break;
    case 9: fork9();
	break;
    case 10: fork10();
	break;
    case 11: fork11();
	break;
    case 12: fork12();
	break;
    case 13: fork13();
	break;
    case 14: fork14();
	break;
    case 15: fork15();
	break;
    case 16: fork16();
	break;
    case 17: fork17();
	break;
    default:
	printf("Unknown option %d\n", option);
	break;
    }
    return 0;
}

本代码展示了17个关于fork函数调用的列子,我们在此只选其中几个进行讲解,我们在Ubuntu环境下对其运行,通过指令gcc -o forks forks.c来进行编译可以获得目标文件forks。

fork0

我们先来运行一个简单的fork列子fork0:
在得到目标文件后我们输入指令./forks 0来运行该列子,得到的结果为:
在这里插入图片描述
通过这个列子我们看到虽然代码段写的是if-else函数,在我们的印象中该算法只输出一个部分,但是因为调用了fork函数的原因,if和else部分的printf都被打印了出来,这就是fork函数的作用,调用一个fork函数有了两次返回。

void fork0() 
{
    if (fork() == 0) {
	printf("Hello from child\n");
    }
    else {
	printf("Hello from parent\n");
    }
}

通过代码我们可以看出在fork()==0时会返回一次,此时fork()就是指的PID,当PID=0时,就是我们的子进程运行时,它会返回Hello from child,当PID!=0时,此时就是指的父进程,父进程就会返回Hello from parent。所以该进程在运行时会返回两次。
我们可以通过进程图来观察该进程运行的过程:
在这里插入图片描述
更据进程图我们可以看出子进程和父进程各输出一次,所以总共输出两次。

fork2

通过上面的fork0我们大概的认识了fork函数的作用,接着我们来讲解fork2。

void fork2()
{
    printf("L0\n");
    fork();
    printf("L1\n");    
    fork();
    printf("Bye\n");
}

该函数在fork0的基础上进行了升级, 它运用了两个fork函数,所以加上父进程一共有4个进程。下面我们来展示它的进程图:
在这里插入图片描述
更据进程图我们可以看到L0之后的fork函数总共产生两个进程,而两个进程中的L1之后又有一个fork函数,所以每个进程又分出两个进程,所以我们可以看到总共有4个进程,分别可以输出L0L1Bye;Bye;L1Bye;Bye。所以我们在Linux上运行可以看出
在这里插入图片描述
它依次输出了4个进程的返回值。

fork9

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");
}

在画进程图之前我们先来解释一下wait函数:
语句wait(&status); 等价于语句waitpid(-1,&status,0);
默认情况下(当options=0时),waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。
等待集合的成员是由参数pid来确定的:

  • 如果pid>0,那么等待集合就是一个单独的子进程,它的进程ID等于pid。
  • 如果pid=-1,那么等待集合就是由父进程所有的子进程组成的。
    如果statusp参数时非空的,那么waitpid就会在status中放上关于导致返回子进程的状态信息,status是statusp指向的值。
    因此wait(&child_status);或者waitpid(-1,&status,0); 代表挂起调用进程的执行,直到等待集合(父进程的所有子进程)中的一个子进程终止,就将状态信息放在变量child_status里。
    因此我们可以看到该机器上父进程先执行printf(“HP: hello from parent\n”);语句
    后遇到wait(&child_status);则等待子进程运行printf(“HC: hello from child\n”);并退出
    父进程再接下来执行printf(“CT: child has terminated\n”);
    最后执行printf(“Bye\n”);
    进程图为:
    在这里插入图片描述
    执行的结果为:
    在这里插入图片描述

fork12

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

该实例体现了kill函数发送SIGINT信号,将本会死循环的子进程通过信号终止,相当于用户自己键入Ctrl+C。进程可通过调用kill函数发送信号给其他进程(包括它们自己)。

  • 如果pid大于0,那么kill函数发送信号号码sig给进程pid。
  • 如果pid等于0,那么kill发送信号sig给调用进程所在进程组中的每个进程,包括调用进程自己。
  • 如果pid小于0,kill发送信号sig给进程组|pid|(pid的绝对值)中的每个进程。
    1.该实例首先创建N个子进程(N在前面是5的宏定义),子进程的PID保存在pid[i]数组里,并且每个子进程都是死循环。父进程调用wait函数等待子进程,如果没有kill函数,则子进程迟迟不终止,父进程就要一直等待子进程而停滞。
    2.创建完子进程后先父进程输出语句,后通过kill函数根据pid数组里的子进程PID对每个子进程发送SIGINT信号(相当于键入Ctrl+C终止进程),这样每个子进程就终止了。
    3.接下来对剩下的父进程调用wait函数,回收到子进程的PID给wpid变量,但是由于子进程非正常终止,WIFEXITED返回假,最终执行else的输出。
    执行结果为:
    在这里插入图片描述
    以上则是对fork函数的一小部分分析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值