CSAPP基本版第八章学习日志:关于fork函数(下)

上接CSAPP基本版第八章学习日志:关于fork函数(上)
上部分解释了关于fork函数例程中的fork0到fork11,接下来介绍fork12到fork17关于信号的部分。
信号是进程之间的通信方式。一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件。Linux系统支持30种不同类型的信号,每种信号类型都对应于某种系统事件。
每个信号类型都有一个预定义的默认行为,是下面中的一种:

  • 进程终止。
  • 进程终止并转储内存。
  • 进程停止(挂起)直到被SIGCONT信号重启。
  • 进程忽略该信号。

例如:

  • 如果当进程在前台运行时,你键入Ctrl+C,那么内核就会发送一个SIGINT信号(号码2)给这个前台进程组中的每个进程,其默认行为为终止相应事件为来自键盘的中断
  • 一个进程可以通过向另一个进程发送一个SIGKILL信号(号码9)强制终止它。其默认行为为终止相应事件为杀死进程。我们可以用kill -9 相应进程PID来实现该操作,其中kill功能为发送信号9号才代表终止进程
  • 还有很多信号,例如SIGALRM信号(号码14),其默认行为为终止相应事件为来自alarm函数的定时器信号
  • 当一个子进程终止或者停止时,内核会发送一个SIGCHLD信号(号码17)给父进程,它是wait函数在等待的信号。其默认行为为忽略相应事件为一个子进程停止或者终止

信号还有很多种,这里就不详细介绍了。
信号
发送信号有多种方式,比如用/bin/kill程序发送信号、从键盘发送信号、用kill函数发送信号、用alarm函数向它自己发送SIGALRM信号、用raise函数给自己发送信号等。
接收的信号可以操作其默认行为,也可由进程使用signal函数修改和信号相关联的默认行为。这将在下面实例介绍。
forks.c代码内容:

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

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

在shell命令行输入./forks 12运行结果如下:

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$ 

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

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid,int sig);

  • 如果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的输出。

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

在shell命令行输入./forks 13运行结果如下:

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$ 

该实例与上一个相似,但是它调用了signal函数,将SIGINT信号的原有行为终止改为去实现用户自己定义的int_handler函数。在前面介绍过,signal函数可以改变和信号相关联的默认行为。

#include <signal.h>
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum,sighandler_t handler);

signal函数可以通过下列三种方法之一来改变和信号signum相关联的默认行为:

  • 如果handler是SIG_IGN,那么忽略类型为signum的信号。
  • 如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为。
  • 否则,handler就是用户定义的函数的地址,这个函数被称为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序。

当处理程序执行它的return语句时,控制(通常)传递回控制流中进程被信号接收中断位置处的指令。信号处理程序可以被其他信号处理程序中断。

  1. 该实例定义变量后调用signal函数表示遇见SIGINT信号就转去执行用户自己定义的函数int_handler
  2. 接下来与上一个实例相同,创建N个不会正常退出而会死循环的子进程。
  3. 然后仍是给子进程发SIGINT信号,但这次子进程不会去执行SIGINT信号的默认行为终止,而转去运行int_handler函数输出语句打印子进程PID及信号号码(SIGINT信号的序号为2)并且正常退出,注意这里子进程最后根据exit(0)正常退出!
  4. 由于子进程正常退出,WIFEXITED返回真,因此执行if语句里的输出,输出子进程PID和退出状态(exit(0)里的0决定了退出状态为0)

注意由于本电脑父进程优先执行,因此父进程先输出

Killing process 2391
Killing process 2392
Killing process 2393
Killing process 2394
Killing process 2395

接下来父进程循环,每次解决一个子进程。遇到wait便等待子进程运行,这时子进程根据信号调用int_handler函数输出语句并退出,父进程再执行if的输出。

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

在shell命令行输入./forks 14运行结果如下:

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

键入Ctrl+Z挂起并ps查看进程状态后,找到父进程PID可用kill -9来杀死,再查看状态:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 14
Received SIGCHLD signal 17 for process 2879
Received SIGCHLD signal 17 for process 2880
^Z
[1]+  已停止               ./forks 14
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2744 pts/0    00:00:00 bash
  2871 pts/0    00:03:32 forks
  2881 pts/0    00:00:00 forks <defunct>
  2882 pts/0    00:00:00 forks <defunct>
  2883 pts/0    00:00:00 forks <defunct>
  2938 pts/0    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ kill -9 2871
[1]+  已杀死               ./forks 14
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2744 pts/0    00:00:00 bash
  3074 pts/0    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ 

该实例综合使用很多方面知识,sleep函数,上面提到的signal函数,而且运行时还可以通过kill -9杀死父进程。
首先解释下新出现的函数:sleep函数。sleep函数将一个进程挂起一段指定的时间。

#include <unistd.h>

unsigned int sleep(unsigned int secs);

参数即挂起的时间,如果请求的时间量已经到了,sleep返回0,否则返回还剩下的要休眠的秒数。
程序中的清空缓冲区fflush(stdout); 刷新流 stream 的输出缓冲区。更多可参考:C 库函数 - fflush()

  1. 父进程首先遇到让全局变量ccount = N;然后遇到signal(SIGCHLD, child_handler);表示接收到SIGCHLD信号(一个子进程停止或者终止)则调用child_handler函数。由于还没有子进程,因此还没有SIGCHLD信号。
  2. 接下来创建N个子进程。子进程执行if,这里的sleep是为了让每个子进程的退出滞后一点,确保父进程先运行,也为展现后面父进程要花许多时间等待子进程。
  3. 接下来父进程由于ccount=N=5>0而死循环,而每个子进程正常退出,产生SIGCHLD信号,父进程即可调用child_handler函数。父进程在死循环的时候,一等到SIGCHLD信号,即调用child_handler函数。可以看到,第一个子进程2879在父进程执行时死循环时传递了SIGCHLD信号,接下来父进程调用child_handler函数,由于只进行了一次wait,其中等待集合由父进程所有的子进程组成的,其中第一个子进程2879先结束先进行下一步,而其它子进程要自己再给父进程发信号让父进程等它们,ccount-1=4,并将等来的这个子进程相关信息输出,并清空缓冲区,删除这个子进程的存在痕迹。第二个子进程2880也在父进程死循环时传递SIGCHLD信号,接下来父进程调用child_handler函数,ccount-1=3,并将等来的这个子进程相关信息输出,并清空缓冲区,删除这个子进程的存在痕迹。
  4. 但接下来的子进程因为sleep被拖延了时间,父进程在等待子进程退出前的时间里一直在死循环。接下来的子进程未被回收,成为僵死进程。(在等待子进程发信号时间内父进程持续死循环),个人观点:如果等待时间足够长,剩下子进程也能输出语句并最后让父进程ccount=0,从而结束死循环而子进程均被回收。
  5. 我们将程序挂起并查看运行进程可以看到一个仍在运行的父进程和3个已结束但仍占空间的三个子进程。
  6. 使用kill -9 2871杀死父进程再查看运行进程时可以发现,僵死子进程也随之被回收了。

注意,由于每次运行程序的速度不同,因此出现只有一个子进程或三个及以上的子进程在屏幕上输出都是有可能的。
该实例需要每个子进程退出时各返回一个SIGCHLD信号给父进程,然后父进程在child_handler函数里等待到一个子进程输出完ccount-1若ccount仍大于0就继续回去死循环了。而下一个实例只需等待一次信号,让父进程会等所有子进程退出。个人观点:该实例让父进程在死循环时遇到信号并调用函数,但在等信号期间父进程仍在执行死循环,不会停下来等信号,因此等待所有子进程回收可能需要运行很长时间,更何况子进程被sleep拖延。这个实例不愧叫挂起的信号不排队

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

在shell命令行输入./forks 15运行结果如下:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 15
Received signal 17 from process 3214
Received signal 17 from process 3213
Received signal 17 from process 3212
Received signal 17 from process 3211
Received signal 17 from process 3210
username@username-virtual-machine:/mnt/hgfs/chap8_code$ 

与上一个相似,这次程序用到了循环使用wait函数和pause函数解决了fork14出现的问题

pause函数让调用函数休眠,直到该进程收到一个信号。

由于加入了这个函数,即使父进程在死循环也要等待信号到来执行相应调用函数,这样所有的子进程都执行了

#include <unistd.h>

int pause(void);

因为pause函数会等待信号,因此一进入循环就等待SIGCHLD信号,由于第一个子进程结束后能很快给父进程返回信号,然后父进程调用child_handler2函数,遇到循环由于直接调用wait相当于waitpid第一个参数为-1,即会等待所有的子进程结束才退出循环,并且waitpid第三个参数为0,默认行为是挂起调用进程,直到有子进程终止就返回该子进程PID。
上一个实例要等待每个子进程结束发送信号,而这次只需等待一次信号,因为wait循环,pause只需运行一次,父进程停在wait函数等待所有子进程退出并依次输出,同时ccount最后也减到了0。

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

在shell命令行输入./forks 16运行结果如下:

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 16
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Child1: pid=2328 pgrp=2327
Child2: pid=2329 pgrp=2327

可以看到该实例由于几个子进程陷入死循环而成为僵死进程,根据前面所学的知识我们可以用ps看到运行状态找到对应子进程并用kill -9来杀死相应进程。再yongps查看运行状态即可发现僵死进程没有了。

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 16
username@username-virtual-machine:/mnt/hgfs/chap8_code$ Child1: pid=2328 pgrp=2327
Child2: pid=2329 pgrp=2327
ps
   PID TTY          TIME CMD
  2310 pts/1    00:00:00 bash
  2328 pts/1    00:00:04 forks
  2329 pts/1    00:00:04 forks
  2348 pts/1    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ kill -9 2328
username@username-virtual-machine:/mnt/hgfs/chap8_code$ kill -9 2329
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2310 pts/1    00:00:00 bash
  2349 pts/1    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ 

该实例是对kill指令和ps查看运行状态的一个应用实例,我们可以找到对应僵死进程并杀死它。
这里再介绍一下getpid函数和getpgrp函数。

  • getpid函数返回调用进程的PID。
  • getpgrp函数返回它的父进程的PID(创建调用进程的进程)。
#include <sys/types.h>
#include <unistd.h>

pid_t getpid(void);
pid_t getppid(void);

该实例进程图可以清楚显示其关系:
8-16

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

在shell命令行输入./forks 17运行结果如下:

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

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 17
Parent: pid=2111 pgrp=2111
Child: pid=2112 pgrp=2111
^Z
[1]+  已停止               ./forks 17
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2090 pts/0    00:00:00 bash
  2111 pts/0    00:00:00 forks
  2112 pts/0    00:00:00 forks
  2120 pts/0    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ kill -9 2111
[1]+  已杀死               ./forks 17
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2090 pts/0    00:00:00 bash
  2146 pts/0    00:00:00 ps
username@username-virtual-machine:/mnt/hgfs/chap8_code$ 

username@username-virtual-machine:/mnt/hgfs/chap8_code$ ./forks 17
Parent: pid=2607 pgrp=2607
Child: pid=2608 pgrp=2607
^C
username@username-virtual-machine:/mnt/hgfs/chap8_code$ ps
   PID TTY          TIME CMD
  2599 pts/1    00:00:00 bash
  2609 pts/1    00:00:00 ps

该实例用来显示Ctrl+Z与Ctrl+C的不同。首先这个实例父进程与子进程在输出语句后都陷入死循环,无法进行下一步操作。

  1. 我们可以用Ctrl+Z挂起程序,这样就可以去运行别的程序。如果通过ps查看运行状态时发现死循环的进程仍未清除。我们可以通过kill -9来杀死父进程,并且它相应的子进程就会被回收。
  2. 我们也可以用Ctrl+C直接终止程序,再通过ps查看运行状态,可以发现死循环的进程均被清除。

该实例进程图可以清楚显示其关系:
8-17

总结:以上就是关于fork函数0到17实例的全部分析,博客中大部分知识点来自《深入理解计算机系统》

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值