深入理解计算机系统基础之17个fork函数分析

17个fork函数分析

基础内容

  1. fork()函数会创建一个子进程,它会完全复制父进程的虚拟地址空间,代码,数据段,以及用户栈等等内容。但是两个进程是相互独立的,其对变量等操作不会影响到另一个进程的该变量的值。

  2. 因为子进程继承了父进程的堆栈,压栈了就会有返回,所以造就了fork()函数的“调用一次,返回两次”的特性。

  3. 父子是并发的,所以其输出前后顺序是不确定的,依照拓扑排序的。

17个fork函数详解

fork0

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

运行结果:
在这里插入图片描述
结果分析:
如果是子进程,就输出“Hello from child”;如果是父进程,就输出“Hello from parent”

fork1

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

运行结果:
在这里插入图片描述
结果分析:
如果是子进程,就输出++x,即2,然后输出子进程的进程号;如果是父进程,就输出–x,即0(因为子进程创建后就有了父进程的一些特性。此时子进程不受父进程干扰,父进程同理,所以在父进程中,x的初始值依旧是1),然后输出父进程的进程号。
fork2

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

运行结果:
在这里插入图片描述
线程图:

在这里插入图片描述
fork3

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

运行结果:
在这里插入图片描述
线程图:
在这里插入图片描述

fork4

void fork4()
{
    printf("L0\n");
    if (fork() != 0) {
	printf("L1\n");    
	if (fork() != 0) {
	    printf("L2\n");
	}
    }
    printf("Bye\n");
}

运行结果:在这里插入图片描述
线程图:在这里插入图片描述
fork5

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

void fork6()
{
    atexit(cleanup);//当进程正常退出时,将调用使用atexit注册过的函数。
    fork();
    exit(0);
}

运行结果:
在这里插入图片描述
线程图:
在这里插入图片描述
结果分析:atexit()函数的功能是当进程正常退出时,将调用使用atexit注册过的函数。
fork7

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

运行结果:
在这里插入图片描述
进程图:
在这里插入图片描述
结果分析:
如果是子进程,输出子进程的进程号;如果是父进程,输出父进程进程号并进入死循环,此时需要ctrl+z挂起父进程,ps查看当前进程状态,可以看出来父进程进入了死循环(没有做完事情),但是子进程结束了,没有人来对他进行处理,所以他就变成了僵死进程。
fork8

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

运行结果:
在这里插入图片描述

进程图:
在这里插入图片描述
结果分析:
与fork7不同的是,此时子进程进入了死循环,而父进程正常退出,终端不会显示异常。但此时由于子进程没有做完他的事情,所以不是僵死进程,可以用kill来杀死它。kill -9 2634杀死子进程,可以用ps指令来检查是否删除成功。
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() 会暂时停止进程的执行,直到有信号或子进程结束,如果在调用wait()时子进程已经结束,则wait() 会立即返回子进程结束的状态值,子进程的结束的状态值由参数status 返回,儿子进程的PID也一块返回,所以绝对不可能出现“CT: child has terminated”比“HC: hello from child”提前输出的情况。
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);
    }
}

运行结果:
在这里插入图片描述
结果分析:
在此函数中,创建了N个子进程,然后父进程等待五次,每次都等子进程中的任一并且打印结束的状态码,然后继续循环。
WIFEXITED()函数当子进程正常退出,则返回大于零的值,否则为0.
fork11

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

运行结果:
在这里插入图片描述
结果分析;
与fork10类似,只是这次等待的是具体的子进程。用waitpid()函数来实现。
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(pid[i],SIGINT)函数来向进程pid[i]发送SIGINT(即终止)信号。因为子进程一直没有结束,但是kill()向其发送了终止信号,所以子进程都是不正常退出的。
fork13

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


运行结果;
在这里插入图片描述
结果分析:
signal(SIGINT,int_handler);函数指定,当接受到SIGINT信号时用int_handler();函数进行处理。
这里父进程向每个孩子发送信号的时候不会发生堵塞。
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)
	;
}

运行结果;
在这里插入图片描述
结果分析:
子进程结束后向父进程发送SIGCHLD信号,signal函数接收到信号,调用handler函数进行处理,handler()等待一个子进程结束后会进行count–(父进程的),父进程一开始会进入死循环,直到子进程被全部回收,但是如果已经有一个子进程先sleep完向父进程发送信号,在父进程处理该信号之前,所有子进程发送的SIGCHLD信号都会被丢弃,所以输出的结果不确定,有的子进程结束了,父进程未处理它,它就会变成僵死进程。
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] = fork()) == 0) {
	    sleep(1);
	    exit(0); /* Child: Exit */

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

运行结果;
在这里插入图片描述
结果分析:
与fork14不同的是,此时父进程可以接受到每个子进程发来的信号,handler的循环会等待五个子进程全部退出,确保子进程全部被回收,父进程也能正常退出。
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);
    }
} 

运行结果;
在这里插入图片描述
结果分析:
终端会在父进程结束后显示出结果,此时父进程正常结束,且子进程仍然在工作,ps可查看。
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);
} 

运行结果;
在这里插入图片描述
结果分析:
此进程中,父进程和子进程都处于死循环,所以需要ctrl+z挂起ps查看可知都进入了死循环,ctrl+c结束所有进程。

其中:
Ctrl+c会直接终止程序,通过ps可以发现死循环的僵死程序被清除了,而
Ctrl + z会挂起程序,通过ps会发现僵死程序还在,可以通过kill -9来杀死它们。

以上就是17fork函数的自己的理解与看法,请多指教。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值