system函数与信号

使用在写在2019年来临前的倒数0.5小时的system_test()函数来调用如下loop_echo程序:

#include <stdio.h>
#include <string.h>
#include <signal.h>

#define BUFSZ 1024

static void handler(int sig)
{
	printf("<.q> to exit\n");
}

int main(void)
{
	struct sigaction sa;

	printf("###################################\n");
	char buf[BUFSZ] = {0};

	/* 捕捉SIGQUIT和SIGINT信号 */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = handler;

	if (sigaction(SIGINT, &sa, NULL) < 0) {
		perror("sigaction");
		return -1;
	}

	if (sigaction(SIGQUIT, &sa, NULL) < 0) {
		perror("sigaction");
		return -1;
	}

	while (1) {
		/* 获取终端输入被回显,直至用户键入.q才退出 */
		fgets(buf, BUFSZ, stdin);
		
		if(strstr(buf, ".q")) {
			puts("Bye\n");
			break;
		}
		
		puts(buf);
		bzero(buf, BUFSZ);
	}
	return 0;
}

程序主流程main.c是这样的:

#include <stdio.h>
#include <signal.h>
#include "system_test.h"

static void handler(int sig)
{
	printf("Caught SIGINT\n");
}

static void handler2(int sig)
{
	printf("Caught SIGCHLD\n");
}

int main(void)
{
	struct sigaction sa, sa2;

	/* 捕捉SIGINT和SIGCHLD信号 */
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = handler;

	sigemptyset(&sa2.sa_mask);
	sa2.sa_flags = 0;
	sa2.sa_handler = handler2;

	if (sigaction(SIGINT, &sa, NULL) < 0) {
		perror("sigaction");
		return -1;
	}

	if (sigaction(SIGCHLD, &sa2, NULL) < 0) {
		perror("sigaction");
		return -1;
	}
	
	/* 做创建子进程操作 */
	//....

	system_test("./loop_echo");
	
	return 0;
}

编译运行:在这里插入图片描述
由运行结果可见:
(1)当我们发送SIGINT信号给loop_echo程序时,main进程仍能捕捉到该信号;
(2)当loop_echo进程退出时,main进程可以收到子进程退出时内核发出的SIGCHLD信号;

我们知道,运行main程序后会有3个进程在进程:①main、②bash、③loop_echo,它们在干什么?
① main:阻塞在waidpid()中,即system_test()的父进程(即本main进程)的代码片段:

	default:
	while (waitpid(cpid, &status, 0) == -1) {
		if (errno == EINTR) {		/* 被信号中断时继续回去等待,重要 */
			continue;
		}

		perror("waitpid");
		return -1;
	}
	return status;

② bash:调用loop_echo并占用终端
③ loop_echo:正在和用户进行交互

我们知道在终端中发出一个信号时发给所在终端的进程组,那么main和loop_echo自然会都收到该信号。然而,让main进程对这个信号都做出响应是没有意义的,这会让使用system_test()的程序员会对此行为困惑不已,因为程序员让主进程去调用system_test()进而执行某个新进程,实际上就是放弃对该新进程的控制权,而只阻塞在waitpid()中。

另外一个很严峻的信号时SIGCHLD。由上运行可见,当由system_test()锁创建的子进程loop_echo退出时产生了SIGCHLD信号,该信号可能被main进程所捕获(在system_test()有机会调用waitpid()之前主进程的信号处理函数可能会率先得以执行去收集子进程状态,这将是竞争条件,所以需要保证system_test()要先回收自己创建的子进程);假设main()程序还直接创建了其它子进程并在SIGCHLD的信号处理函数调用wait()去回收子进程,那么main进程会误以为wait回收的是自己直接创建的该子进程,事实上却是system_test()子进程。

因此,POSIX.1要求system()忽略SIGINT和SIGQUIT,阻塞SIGCHLD信号。

忽略SIGINT和SIGQUIT是因为主进程已经放弃新进程的控制权,主进程不应该对新进程的SIGINT和SIGQUIT做出响应;阻塞SIGCHLD信号时确保system()所创建的子进程退出时主进程不会去回收该子进程。为什么是阻塞?因为system()退出时还要恢复主进程对SIGCHLD的响应,由于此时system()已经回收自己所创建的子进程了,那么恢复主进程对SIGCHLD的响应,收到system()所创建的子进程退出的SIGCHLD信号后,假设再去回收该子进程的已经回收失败,因为该子进程已经不存在了。需要注意,可能在阻塞期间main进程所直接创建的子进程会退出,由于SIGCHLD被阻塞所以主进程并不能去做出响应和回收,恢复之后,对这些进程仍然进行回收。这就是为什么使用阻塞的原因。

综上分析,system_test()对信号的正确处理后的实现为:

#include <stdio.h>
#include <signal.h>
#include "system_test.h"
#include <sys/wait.h>

static void handler(int sig)
{
	printf("Caught SIGINT\n");
}

static void handler2(int sig)
{
	printf("Caught SIGCHLD\n");
}

int main(void)
{
	struct sigaction sa, sa2;

	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = handler;

	sigemptyset(&sa2.sa_mask);
	sa2.sa_flags = 0;
	sa2.sa_handler = handler2;

	if (sigaction(SIGINT, &sa, NULL) < 0) {
		perror("sigaction");
		return -1;
	}

	if (sigaction(SIGCHLD, &sa2, NULL) < 0) {
		perror("sigaction");
		return -1;
	}

	system_test("./loop_echo");	//阻塞在system_test()中的waitpid(pid, &status, 0)中
	return 0;
}

编译运行:
在这里插入图片描述
由运行可知,main进程还是会收到system_test()创建的子进程退出时系统发出的SIGCHLD信号(因为该信号一直处于未决状态),此时若main进程的SIGCHLD再去回收system_test()创建的子进程的退出信息将无法获取。修改main.c的handler2函数,增加waitpid():

static void handler2(int sig)
{
	printf("Caught SIGCHLD\n");
	pid_t childPid;

	while ((childPid = waitpid(-1, NULL, WNOHANG)) > 0) {
		printf("childPid = %d\n", childPid);
	}
	if (childPid < 0) {
		perror("waitpid");
	}
}

运行:
在这里插入图片描述
这样一来,main.c的handler2()函数就只回收到main进程直接创建的子进程的退出信息了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值