17个fork函数分析
基础内容
-
fork()函数会创建一个子进程,它会完全复制父进程的虚拟地址空间,代码,数据段,以及用户栈等等内容。但是两个进程是相互独立的,其对变量等操作不会影响到另一个进程的该变量的值。
-
因为子进程继承了父进程的堆栈,压栈了就会有返回,所以造就了fork()函数的“调用一次,返回两次”的特性。
-
父子是并发的,所以其输出前后顺序是不确定的,依照拓扑排序的。
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函数的自己的理解与看法,请多指教。