Fork函数的解析(二)---Signal

1.Sending signals with the kill() function 

#define N 5
int main() 
{
	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);    //将终止信号发送给对应PID为pid[i]的进程
    }

    for (i = 0; i < N; i++) {
	pid_t wpid = wait(&child_status);    //等待所有的进程
	if (WIFEXITED(child_status))   //如果子进程是正常退出则输出子进程的PID及其退出状态
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminated abnormally\n", wpid);//否则子进程是非正常退出的
    }
	return 0;
}

进程可通过调用kill函数发送给信号给其他进程,形式为int kill(pid_t pid,int sig); 如果pid大于0,那么kill函数发送信号号码sig给进程pid。如果pid等于0,那么kill函数发送信号sig给调用进程组的每个进程,包括调用进程自己。如果pid小于0,kill发送信号sig给进程组|pid|(pid的绝对值)中的每个进程。程序中的SIGINT信号代表的默认行为是终止。

程序中用kill函数发送终止信号给对应的5个子进程,因为是接收SIGINT信号才退出的,所以是非正常退出。

运行结果如下:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2.Simple signal handler example

#define N 5
void int_handler(int sig)  //信号处理程序,显示接收该信号的进程ID及接收信号的号码
{
    printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
    exit(0);
}
int main() 
{
	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);
    }
	return 0;
}

这个程序可以帮助我们理解发送信号和接收信号。首先要先了解每个信号都会有一个对应的唯一的号码,几种常见的信号如下:SIGINT--2、SIGKILL--9、SIGALRM--14、SIGCHLD--17。当进程在前台运行的时候,你键入ctrl+c,那么内核会发送一个SIGINT信号给这个前台的每一个进程;一个进程可以通过向另一个进程发送一个SIGKILL信号强行终止它;当一个进程终止或停止时,内核会发送一个SIGCHLD信号给父进程;当alarm函数定时器计时结束后,内核会发送SIGALRM终止对应的进程。

当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。

sighandler_t signal(int signum, sighandler_t handler);第一个参数代表信号的名称,接收信号后要执行的程序。

程序中子进程一旦接收到了信号就去执行handler函数,执行handler函数后是以状态0正常退出。

运行结果如下:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

将上面的信号处理程序换为下列代码:

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

运行结果为:

在fork函数的解析(一)中提到过,当wait函数返回值为-1的时候表示程序出错,这个程序一直不会出现命令行,即使按下Ctrl+c键,这里ctrl+c键其实还是SIGINT信号。按Ctrl+z键将其挂起,因为wait函数都等不到任何一个子进程的退出,所以会出错返回-1。

3.SIGCHLD handler that reaps one terminated child

#define N 5
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 */
}
int main() 
{
	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)
	;
}

此处先解释一下sleep函数--unsigned int sleep(unsigned int secs)  参数为要休眠的秒数,它的作用是将一个进程挂起一段指定的时间。如果请求的时间量到了,sleep会返回0,若它被一个信号中断而过早地返回,它会返回还剩下要休眠的秒数

SIGCHLD信号相应的事件是一个子进程终止或者停止,故当子进程正常退出时,转而就会去执行child_handler。

运行结果如下:

这里输出的结果断断续续地,原因如下:

sleep函数能被信号中断。当执行完中断程序后就直接执行下一条语句,导致sleep的休眠时间被打乱,这也是用sleep函数经常出现的问题,解决方法可看下段代码。(水平有限,解释的可能有些模糊)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4.Using a handler that reaps multiple children 

#define N 5
int ccount = 0;
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 */
    }
}
int main() 
{
	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();
    }
}

 

运行结果如下:

这个程序与上个程序的区别在于加上了一个pause函数,该函数地作用是让调用函数休眠,直到该进程遇到一个信号。

用了pause函数,解决了上面的问题,所以程序运行后能完整地输出五条语句。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

5.Demonstration of using /bin/kill program 

int main() 
{
    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);   //父进程和子进程最终都会进入死循环
	}
}

getpid这个函数在上一篇文章中介绍过,这里不再重复。这边有个新的函数getpgrp()它的作用是返回当前进程的进程组ID,每个进程只属于一个进程组,进程组是由一个正整数的进程组ID来标识的。默认地,一个子进程和它的父进程同属一个进程组。一个进程可以通过set-pgid函数改变自己或其他进程的进程组:int setpgid(pid_t pid, pid_t pgid); 将pid的进程组改为pgid。若pid是0,就使用当前进程的PID,若pgid是0,那么就用pid指定的进程的PID作为进程组ID。

运行结果如下:

这里还有一个知识点 kill -9 -3104是指发送号码为9的信号即SIGKILL给进程组号为3104的所有进程,也就是让进程组号为3104的所有进程“死掉”。执行之后查看状态可知child1和child2都终止了。 kill -9 -3104要区别于 kill -9 3104,后者是“杀死”一个进程号为3104的进程。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

6.Demonstration of using ctrl-c and ctrl-z

int main() 
{
    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 w(采用宽阔的格式来查看程序状态):

用ctrl+z后只是将程序挂起在后台运行,我们还是能查看到它的状态。

用ctrl+c后用ps w查看它的状态:

ctrl+z是把进程杀死,很明显用ps w已经看不到相应进程的状态了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值