从易到难fork18题

本文旨在给大家分享一些稍有难度的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;
}

fork3

fork3没什么好讲的,按部就班画流程图就结束了,当作热个身吧。注意实际顺序在满足拓扑排列任意情况都有可能,什么叫不满足拓扑排序呢?例如第一个bye就不可能在L2后面,因为第一个bye前面必会存在一个L2。fork4只是在fork3的基础上增加条件判断,也不多说
在这里插入图片描述


fork6

这道题主要了解一下atexit函数,C 库函数 int atexit(void (*func)(void)) 。记忆很简单,at exit当exit时调用。当程序调用exit函数终止时,调用指定的函数 func。您可以在任何地方注册你的终止函数,但它会在程序终止的时候被调用。知道这个后结果就很清楚了


fork7

fork7涉及新概念所以讲一下,先贴运行结果

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 7
Running Parent, PID = 4038
Terminating Child, PID = 4039
^Z
[1]+  Stopped                 ./a.out 7
zzz@ubuntu:/mnt/hgfs/shared$ ps
   PID TTY          TIME CMD
  2699 pts/0    00:00:00 bash
  4038 pts/0    00:00:06 a.out
  4039 pts/0    00:00:00 a.out <defunct>
  4040 pts/0    00:00:00 ps

首先我们看到父进程是4038,子进程是4039.看程序可知这个程序的父进程是不会终止的,而子进程调用exit()终止了。
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁, 而是留下一个称为僵死进程(Zombie)的数据结构(系统调用exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵死进程,并不能将其完全销毁)
所以这个子进程就是僵死进程,我们使用ps查看也可以看到的字眼,这个字眼就是意指僵死进程
最后让我们结束他们。用kill-9命令杀死父进程。父进程被杀死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程.它产生的所有僵尸进程也跟着消失。
消除僵死进程的方法还有:改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这个函数在后面的例子会有涉及。
这里还要区分一下僵死进程和孤儿进程的概念贴一个别人的博文https://www.cnblogs.com/GyForever1004/p/8410804.html


fork8

先看结果,父进程结束,子进程还在运行,变成孤儿进程,进程终止时被init回收。在这里,子进程进入死循环,必须用kill指令强制杀死。

zzz@ubuntu:/mnt/hgfs/shared$ ps
   PID TTY          TIME CMD
  2699 pts/0    00:00:00 bash
 18794 pts/0    00:05:04 a.out
 18800 pts/0    00:00:00 ps

shell:就是zzz@ubuntu:/mnt/hgfs/shared$
父进程是shell main 中创建的 父进程结束 自然回shell了 。父进程死循环自然回不到shell.


fork9

前面提到过僵死进程,这里就介绍处理僵死进程的第二种处理方法,使用wait等函数让父进程等待子进程。
了解wait前先了解waitpid,pid_t waitpid(pid_t pid, int *statusp, int options);
参数
pid>0 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
pid=-1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
pid=0时 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
pid<-1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
statusp:
参数statusp如果不是一个空指针,则终止进程的终止状态就存放在status所指向的单元。
参数statusp如果是一个空指针,则表示父进程不关心子进程的终止状态
options
WNOHANG 若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0
WUNTRACED 返回终止子进程信息和因信号停止的子进程信息
WCONTINUED 返回收到SIGCONT信号而恢复执行的已停止子进程状态信息
返回值:
成功 成功结束运行的子进程的进程号
失败 返回-1
WNOHANG 没有子进程退出返回0

wait等价于waitpid(-1,&statusp,0)。

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 9
HP: hello from parent
HC: hello from child
CT: child has terminated
Bye
zzz@ubuntu:/mnt/hgfs/shared$ ps
   PID TTY          TIME CMD
  2699 pts/0    00:00:00 bash
 19035 pts/0    00:00:00 ps
zzz@ubuntu:/mnt/hgfs/shared$ 

所以最后一切正常,没有僵死进程的产生


fork10

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 10
Child 19039 terminated with exit status 100
Child 19041 terminated with exit status 102
Child 19040 terminated with exit status 101
Child 19042 terminated with exit status 103
Child 19043 terminated with exit status 104

exit(100+i);子进程的退出码分别为100、101、102、104、105,通过wait(&child_status);的返回值获取子进程的pid,某一子进程结束后输出其PID和退出码。
如果我们statusp参数不为空,则该指针将指向导致返回的状态信息。
WIFEXITED and WEXITSTATUS 是两个解释状态信息的宏,他们的参数都是(status),(也就是我们自定义的child_status),至于意思顾名思义。第一个是wait if exited 即是否退出成功,成功返回true。第二是wait exit status 即exit的退出码,只有第一个为真时这个才会被定义。
fork11也类似


fork12

父进程fork五次后子进程一直处于循环之中,之后父进程杀死子进程。最后让父进程等待子进程的结束信息,可以看到子进程都是异常地结束,因为是被父进程kill杀死的。

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 12
Killing process 19110
Killing process 19111
Killing process 19112
Killing process 19113
Killing process 19114
Child 19110 terminated abnormally
Child 19111 terminated abnormally
Child 19112 terminated abnormally
Child 19113 terminated abnormally
Child 19114 terminated abnormally


fork13

承接fork12,kill子进程的时候产生了信号SIGINT,触发处理信号函数int_handler提前将子进程exit,所以结束信息是正常结束。

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 13
Killing process 19121
Killing process 19122
Killing process 19123
Killing process 19124
Killing process 19125
Process 19121 received signal 2
Process 19123 received signal 2
Process 19122 received signal 2
Child 19121 terminated with exit status 0
Child 19122 terminated with exit status 0
Child 19123 terminated with exit status 0
Process 19124 received signal 2
Process 19125 received signal 2
Child 19124 terminated with exit status 0
Child 19125 terminated with exit status 0


fork14 and 15

fork14很有意思,大家多跑几次会发现会有不一样的结果。
按道理应该会有五条语句(N=5),但实际一般会少于五条,然后开始一直while,while多久你都等不到更多的。
先不说原理看fork15,fork15则一定会输出五条语句,结果如下。

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 15
Received signal 17 from process 3274
Received signal 17 from process 3275
Received signal 17 from process 3276
Received signal 17 from process 3277
Received signal 17 from process 3278

对比后我们发现差别就在于pause函数和信号处理函数中的while:该函数让调用函数休眠直到该进程收到一个信号
类似的还有函数sleep() :sleep()会令目前的进程暂停参数seconds 所指定的时间, 或是暂停中途被信号所中断。所以我们现在知道fork14的问题了,
fork15当接收到子进程的终止信号SIGCHLD时会停止pause,继而执行调用child_handler(仅一次),然后一直wait输出所有子进程信息。
而fork14没有这个pause和while所以接受一个信号跳转执行一次,因为任何时刻一个进程一种类型的待处理信号都只能有一个,所以forks接受到了信号执行信号处理程序时(即handler函数)这段时间内接受到的其他待处理信号就会被简单丢弃。这种错误被称作未处理的信号不排队


fork16和17

16

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 16
Child1: pid=19129 pgrp=19128
zzz@ubuntu:/mnt/hgfs/shared$ Child2: pid=19130 pgrp=19128
ps
   PID TTY          TIME CMD
  2699 pts/0    00:00:00 bash
 19129 pts/0    00:00:09 a.out
 19130 pts/0    00:00:09 a.out
 19131 pts/0    00:00:00 ps
zzz@ubuntu:/mnt/hgfs/shared$ 

getpgrp获得parent的pid。子进程一直未终止,但是我们还是返回了shell,因为父进程终止了。两个子进程都陷入了while(1);而没有终止,因此父进程在终止的时候不会回收其子进程,于是两个子进程都变成了孤儿进程,由inti进程收养这时要用kill命令杀死进程:

17

zzz@ubuntu:/mnt/hgfs/shared$ ./a.out 17
Parent: pid=19148 pgrp=19148
Child: pid=19149 pgrp=19148

这段程序让父进程一直循环,所以我们无法返回shell,用于了解ctrl +z(挂起)和ctrl +c(中止)。我们ctrl+z就能再ps就能看到19148和19149,若直接ctrl +c 终止父进程,子进程也跟着回收

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值