csapp第八章学习笔记:17个fork()函数

学习内容:

在CSAPP第八章——异常控制流中接触到了进程的概念。而其中的fork()函数更是让我头疼不已。
先放本尊:

> 
#include<unistd.h>
#include<sys/types.h>
pid_t fork( void);
(pid_t 是一个宏定义,其实质是int 被定义在#includesys/types.h>中)
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
  • fork()函数会创建一个子进程,他会完全复制父进程的虚拟地址空间,代码,数据段,堆,共享库,以及用户栈。但是两个进程是相互独立的,其对变量等操作不会影响到另一个进程的内存。
  • 因为子进程继承了父进程的堆栈,压栈了就会有返回,所以造就了fork()函数的“调用一次,返回两次”的特性。
  • 父子是并发的,所以其输出前后顺序是不确定的,依照拓扑排序的。

学习笔记:

随堂练习17fork():
进程图自己画的,很潦草,看得懂就行哈哈哈

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 0
Hello from parent
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Hello from child

因为fork调用一次返回一次的特性,做到了同时运行了if和else中的语句,实现了“我全都要”的情况hhh
同时因为父子是并发的,两句输出的顺序是随机的。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 1
Parent has x = 0
Bye from process 2456 with x = 0
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Child has x = 2
Bye from process 2457 with x = 2

在这里插入图片描述
因为子进程会复制父进程的数据,并且两者相互独立运行,所以他们是对自己的“x”进行操作。
而子进程的pid一般情况下是父进程pid+1。
又双因为并发,输出顺序只要符合该拓扑排序,都是可能的,比如2,0,bye2,bye0。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 2
L0
L1
Bye
username@username-virtual-machine:/mnt/hgfs/chap8_code$ L1
Bye
Bye
Bye

在这里插入图片描述

由进程图可知,每次调用fork函数,都会“分叉”一次,并且子进程也会创建自己的子进程,(也就是孙子?)

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 3
L0
L1
L2
Bye
username@username-virtual-machine:/mnt/hgfs/chap8_code$ L2
Bye
Bye
L1
L2
Bye
Bye
Bye
L2
Bye
Bye

fork3和fork2同理,只是“子又生孙孙又生子”,即会输出1个L0,2个L1,4个L2,8个Bye。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 4
L0
L1
L2
Bye
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Bye
Bye

在这里插入图片描述
fork()函数调用成功会返回两个值,父进程返回PID,子进程返回0,所以这个代码中的子进程都不满足if条件,故不会输出L1,L2。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 5
L0
Bye
username@username-virtual-machine:/mnt/hgfs/chap8_code$ L1
Bye
L2
Bye

在这里插入图片描述
fork5和fork4的不同之处只在if条件,只有fork()产生的子进程,才能执行if语句。而要注意的是,第二次调用fork的是第一次产生的子进程,此时它已经长大成了“父进程”,所以也进不了第二个if语句。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 6
Cleaning up
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Cleaning up

在这里插入图片描述
atexit()函数

C 库函数 int atexit(void (*func)(void)) 当程序正常终止时,调用指定的函数 func(在程序终止时被调用的函数)。您可以在任何地方注册你的终止函数,但它会在程序终止的时候被调用。

所以,在fork6中,当父子进程exit后,就会调用cleanup函数。

  1. fork7
  2. fork8
/* 
 * 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);
    }
}

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 7
Running Parent, PID = 2365
Terminating Child, PID = 2366
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 8
Terminating Parent, PID = 2228
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Running Child, PID = 2227

在这里插入图片描述
fork7和fork8是两个悲剧故事,
fork7中父进程进入了死循环,而子进程早早结束,但系统不会回收它,它的尸体还等着父进程来回收,但你也知道,你永远等不来不可能来的人,于是子进程就一直在那里,最后变成了“僵死进程”。
fork8中子进程死循环一直运行,而父进程早早结束,在临终的时候,子进程还在外忙碌,无暇回家探望,于是父进程无法回收子进程,自己孤独的去世,子进程就变成了没有家的“孤儿进程”

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 9
HP: hello from parent
HC: hello from child
CT: child has terminated
Bye

在这里插入图片描述
在这里父进程执行到wait(&child_status); 会暂时挂起,直到wait到子进程结束,“回家”,才会继续执行后面的语句。所以CT: child has terminated和Bye一定会在HC后执行。

  1. fork10
  2. fork11
#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);
    }
}

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 10
Child 2860 terminated with exit status 104
Child 2859 terminated with exit status 103
Child 2858 terminated with exit status 102
Child 2857 terminated with exit status 101
Child 2856 terminated with exit status 100
注:这5句不一定会按顺序
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 11
Child 2922 terminated with exit status 104
Child 2921 terminated with exit status 103
Child 2920 terminated with exit status 102
Child 2919 terminated with exit status 101
Child 2918 terminated with exit status 100
username@username-virtual-machine:/mnt/hgfs/chap8_code$
注:一定按顺序

在这里插入图片描述

fork10和fork11都是先父进程调用5次fork,生成5个子进程,然后返回100+i.而两者唯一的区别在于:

fork10:
pid_t wpid = wait(&child_status);
fork11:
pid_t wpid = waitpid(pid[i], &child_status, 0);

fork10中 任意一个子进程结束,都会执行一次for循环,回收该子进程并打印其pid和返回值。
而fork11中杀死了这种随机性,为waitpid的第一个参数设置了pid[i],即for循环i=1的时候,只会等到pid[1]存的进程号对应的子进程结束才会执行。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 12
Killing process 2164
Killing process 2165
Killing process 2166
Killing process 2167
Killing process 2168
Child 2166 terminated abnormally
Child 2167 terminated abnormally
Child 2165 terminated abnormally
Child 2168 terminated abnormally
Child 2164 terminated abnormally
username@username-virtual-machine:/mnt/hgfs/chap8_code$

在这里插入图片描述
在这里本来5个子进程都会进入死循环,但在第二个for循环中,

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

父进程残忍的杀害了它的5个子进程,并且还大声说出来我杀了几号子进程0.0,然后向外发送SIGINT的信号。
到了第三个for循环,

 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的,不是正常退出的,所以不满足if条件语句if (WIFEXITED(child_status)),转而执行else语句,然后因为没有像fork11一样用pid_t wpid = waitpid(pid[i], &child_status, 0);指定目标,所以最后打印的死亡名单可能是乱序的。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 13
Killing process 2391
Killing process 2392
Killing process 2393
Killing process 2394
Killing process 2395
Process 2395 received signal 2
Child 2395 terminated with exit status 0
Process 2394 received signal 2
Child 2394 terminated with exit status 0
Process 2393 received signal 2
Child 2393 terminated with exit status 0
Process 2392 received signal 2
Child 2392 terminated with exit status 0
Process 2391 received signal 2
Child 2391 terminated with exit status 0
username@username-virtual-machine:/mnt/hgfs/chap8_code$

在这里插入图片描述
fork13与12的不同之处在于增加了自己定义的

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

当kill杀死子进程的时候,会发出SIGINT的信号,然后会被signal()信号处理程序捕获,转而运行int_handler(),所以这里的子进程在被kill后,进入int_handler,打印,然后exit(0),所以是正常退出,在第三个for循环中满足if条件语句。
其次,SIGINT在信号表中值为2,所以打印出received 2.

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 14
Received SIGCHLD signal 17 for process 2879
Received SIGCHLD signal 17 for process 2881

注:可能会输出1~5行。

在这里插入图片描述
fork14前面与fork12,13一样,然后子进程会sleep休眠一段时间,而父进程因为N>0,会进入死循环。每个子进程休眠完毕,正常结束的时候,会发出编号为17的SIGCHLD信号,然后就会跳转到自定义的chld_handler函数中,打印发送该信号的进程的pid。
然而,为什么输出却没有5行,有的子进程没有回收呢?
因为一个类型的信号至多只会有一个待处理信号,如果进程已经有了SIGCHLD的待处理信号,那么接下来该类型的信号都会被丢弃,并不会排队。所以在fork14中,父进程在处理SIGCHLD的时候,不会接收其他子进程发出的SIGCHLD,也就不会回收本应正常结束的子进程,这些子进程就成了僵死进程。
通俗一点讲就是大哥回来看望父母(发出SIGCHLD),会带父母出去逛街(处理信号),在逛街的时候,二弟也回来了,但是父母不在家,于是二弟就走了(没有被回收)。

  1. 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();
    }
}

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 15
Received signal 17 from process 3324
Received signal 17 from process 3323
Received signal 17 from process 3322
Received signal 17 from process 3321
Received signal 17 from process 3320
username@username-virtual-machine:/mnt/hgfs/chap8_code$

在这里插入图片描述
fork15与fork14的不同之处在于:

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

 while ((pid = wait(&child_status)) > 0) {
	ccount--;
	printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
	fflush(stdout); /* Unsafe */
    }

父进程在死循环的时候,不会一直执行循环,而是因为pause函数暂停,直到接收到信号,然后进入child_handler函数中,在while循环中,依次输出结束的子进程。
因为fork15的父进程全程只要接收一个信号,并且child_handler会通过while循环打印所有结束的子进程pid,所以这个程序的运行结果会是N行,在这里N=5.
又通俗一点说,有一个孩子在家庭微信群里说,会回家看爸妈,然后回家后,爸妈没有急着和这个孩子出去,因为他知道其他的孩字也会回家,然后等大家都回来了,一个一个出门逛街。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 16
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Child1: pid=2299 pgrp=2298
Child2: pid=2299 pgrp=2298

在这里插入图片描述
getpgrp()函数是返回该进程所在进程组的编号,而子进程所在的进程组号通常是等于其父进程的pid的。
所以会产生上述输出。
在这里每个进程都会进入死循环,我们使用kill -9可以杀死相应进程。

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

运行结果:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 17
Parent: pid=2581 pgrp=2581
Child: pid=2582 pgrp=2581

在这里插入图片描述
fork17是想让我们知道ctrl+c 和+z的区别。
Ctrl+c会直接终止程序,通过ps可以发现死循环的僵死程序被清除了,而
Ctrl + z会挂起程序,通过ps会发现僵死程序还在,还需要通过kill -9来杀死它们。

以上就是17个fork()实例的解析,多亏有老师的教导和网友的经验分享。在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值