linux程序设计——进程和信号(第十一章)

58 篇文章 0 订阅
57 篇文章 9 订阅

11.4.1    发送信号

进程可以通过调用kill函数向包括它本身在内的其他进程发送一个信号.如果程序没有发送该信号的权限,对kill函数的调用就将失败,失败的常见原因是目标进程由另一个用户所拥有.这个函数和同名的shell命令完成相同的功能,它的定义如下所示:
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
kill函数把参数sig给定的信号发送给参数pid给出的进程号所指定的进程,成功它返回0.想要发送一个信号,发送进程必须拥有相应的权限,这通常意味着连个进程必须拥有相同的用户ID.
kill调用会在失败时返回-1并设置errno变量.失败的原因可能是:给定的信号无效;发送进程权限不够;目标进程不存在.
信号提供了一个有用的闹钟功能.进程可以通过alarm函数在经过预定时间后发送一个SIGALRM信号.
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
alarm函数用来在seconds秒之后安排发送一个SIGALRM信号,但由于处理的延时和时间调度的不确定性,实际闹钟时间将比预先安排的要稍微拖后一点.把参数seconds设置为0将取消所有已设置的闹钟请求.如果在接收到SIGALRM信号之前再次调用alarm函数,则闹钟重新开始计时.每个进程只能有一个闹钟.alarm函数的返回值是以前设置的闹钟时间的余留秒书,如果调用失败则返回-1.
为了说明alarm函数的工作情况,通过使用fork,sleep和signal来模拟它的效果.程序可以启动一个新的进程.它专门用于在未来的某一时刻发送一个信号.
编写程序alarm.c
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

static int alarm_fired = 0;

void ding(int sig){
	alarm_fired = 1;
}

/* 在main函数中,告诉子进程在等待5秒后发送一个SIGALRM信号给它的父进程*/
int main(){
	pid_t pid;
	printf("alarm application starting\n");
	pid = fork();
	switch(pid){
	case -1:		/* Failure */
		perror("fork failed");
		exit(1);
	case 0:			/* child */
		sleep(5);
		kill(getppid(), SIGALRM);
		exit(0);
	}
	/* 父进程通过一个signal调用安排好捕获SIGALRM信号的工作,然后等待它的到来*/
	printf("waiting for alarm to go off\n");
	(void) signal(SIGALRM, ding);
	
	pause();
	if (alarm_fired)
		printf("Ding!\n");

	printf("done\n");
	exit(0);
}
运行这个程序时,它会暂停5秒,等待模拟闹钟的闹响.
这个程序用到了一个新的函数pause(),它的作用很简单,就是把程序的执行挂起直到有一个信号出现为止.当程序接收到一个信号时,预设好的信号处理函数将开始运行,程序也将恢复正常的执行.pause函数的定义如下所示:
#include <unistd.h>
int pause(void)
当它被一个信号中断时,将返回-1(如果下一个接收到的信号没有导致程序终止的话)并把errno设置为EINTR.当需要等待信号时,一个更常见的做法是使用稍后将介绍的sigsuspend函数.
闹钟模拟程序通过fork调用启动新的进程,这个子进程休眠5秒后向其父进程发送一个SIGALRM信号.父进程在安排好捕获SIGALRM信号后暂停运行,直到接收到一个信号为止.并未在信号处理函数中直接使用printf,而是通过在该函数中设置标志,然后在main函数中检查该标志来完成消息的输出.
使用信号并挂起程序的执行是linux程序设计中的一个重要部分.这意味着程序不需要总是在执行着.程序不必在一个循环中无休止地检查某个事件是否已发生,相反,它可以等待事件的发生.这在只有一个CPU的多用户环境中尤其重要,进程共享着一个处理器,繁忙的等待将会对系统的性能造成极大的影响.程序中信号的使用将带来一个特殊的问题:"如果信号出现在系统调用的执行过程中会发生什么情况?"答案是相当让人不满意的"视情况而定".一般来说,只需要考虑慢系统调用,例如从终端读数据,如果在这个系统调用等待数据时出现一个信号,它就返回一个错误.如果开始在程序中使用信号,就需要注意一些系统调用会因为接收到了一个信号而失败,而这种错误情况可能是在添加信号处理函数之前没有考虑到的.
在编写程序中处理信号部分的代码时必须非常小心,因为在使用信号的程序中出现各种各样的"竞态条件".例如,如果想调用pause等待一个信号,可信号却出现在调用pause之前,就会使程序无限期等待一个不会发生的事件.这些竞态条件都是一些对时间要求苛刻的问题,所以在检查和信号相关的代码时总是要非常小心.
一个健壮的信号接口
已经对signal和其相关函数来生成和捕获信号做了比较深入的介绍,因为它们在传统的UNIX编程中很常见.但X/Open和UNIX规范推荐了一个更新,更健壮的变成接口:sigaction.它的定义如下:
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
sigaction结构定义在文件signal.h中,它的作用是定义在接收到参数sig指定的信号后应该采取的行动.该结构至少应该包括一下几个成员:
void (*) (int) sa_handler        /* function, SIG_DEL or SIG_IGN
sigset_t sa_mask                /* signals to block in sa_handler
int sa_flags                    /* signal action modifiers
sigaction函数设置与信号sig相关联的动作.如果oact不是空指针,sigaction将把原先对该信号的动作写到它指向的位置.如果act是空指针,则sigaction函数就不需要再做其他设置了,否则将在该参数中设置对指定信号的动作.
与signal函数一样,sigaction函数会在成功时返回0,失败时返回-1.如果给出的信号无效或者试图对一个不允许被捕获或忽略的信号进程捕获或忽略,错误变量errno将被设置为EINVAL.
参数act指向的sigaction结构中,sa_handler是一个函数指针,它指向接收到信号sig时将被调用的信号处理函数.它相当于前面见到的传递给函数signal的参数func.可以就爱那个sa_handler字段设置为特殊值SIG_IGN和SIG_DFL,它们分别表示信号将被忽略或把该信号的处理方式恢复为默认动作.
sa_mask成员指定了一个信号集,在调用sa_handler所指向的信号处理函数之前,该信号集将被加入到进程的信号屏蔽字中.这是一组将被阻塞且不会传递给该进程的信号.设置信号屏蔽字可以防止前面看到的信号在它的处理函数还未运行结束时就被接收到的情况.使用sa_mask字段可以消除这一竞态条件.
设置信号屏蔽字可以防止前面看到的信号在它的处理函数还未运行结束时就被接收到的情况.使用sa_mask字段可以消除这一竞态条件.
但是,由sigaction函数设置的信号处理函数在默认情况下是不被重置的,如果希望获得类似前面用第二次signal调用对信号处理进程重置的效果,就必须在sa_flags成员中包含值SA_RESETHAND.在深入了解sigaction函数之前,先用sigaction替换signal来重写程序ctrlc.c
使用sigaction来截获SIGINT信号,编写程序ctrlc2.c
/*************************************************************************
 > File Name: ctrlc2.c
 > Description: 使用sigaction截取SIGINT信号,按下Ctrl+C,可以看到一条消息,因为sigaction函数连续处理到来的SIGINT信号,Ctrl+\终止,产生SIGQUIT信号
 > Author: liubingbing
 > Created Time: 2015年07月
 > History:
************************************************************************/

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

void ouch(int sig){
	printf("OUCH! - I got signal %d\n", sig);
}

int main(){
	struct sigaction act;

	act.sa_handler = ouch;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;

	sigaction(SIGINT, &act, 0);

	while(1){
		printf("hello world!\n");
		sleep(1);
	}
}
运行这个程序,只需要按下Ctrl+C组合键,就可以看到一条消息.因为sigaction函数连续处理到来的SIGINT信号.要想终止这个程序,只能按下Ctrl+\组合键,它在(默认情况下产生SIGQUIT信号.
这个程序用sigaction代替signal来设置Ctrl+c组合键(SIGINT信号)的信号处理函数为ouch.它首先必须设置一个sigaction结构,在该结构中包含信号处理函数,信号屏蔽字和标志.在本例中不需要设置任何标志,并通过调用新的函数sigemptyset来创建空的信号屏蔽字.
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值