UNIX环境高级编程学习笔记八_信号

一. 信号概念

信号是软件中断,用于比较重要的程序处理。

产生信号的条件:

  • 用户终端产生信号,如Ctrl+C
  • 硬件检测出的软件错误,如无法运行的除0
  • 调用kill(1)和kill(2)所产生的信号
  • 检测到某种软件的条件已经发生

信号处理函数:

#include<signal.h>
void (*signal(int signo, void (*func)(int)))(int);  //触发signo规则时产生信号

UNIX系统常见信号如下:
UNIX系统信号

二. 中断的系统调用

在执行低速系统的系统调用时,期间因捕捉到信号而导致中断,但其返回条件无法实现,导致出错。如:

请求读某些文件,但文件中数据不存在了,导致请求无法实现。

处理的办法是设置原系统调用的自启动,以使系统不耗费在这个无用的等待信号下。常见的就是读写数据调用被打断时的自行启动。

三. 可重入函数

在函数运行时,如果被别的信号打断,将会导致原函数运行出现问题。例如:当函数运行的malloc分配内存时出现信号,此时可能会破坏malloc分配得列表,导致程序返回时出错,这类函数即称为不可重入函数。
可重入函数表如下:
可重入函数

四. kill和raise

函数原型:

#include<signal.h>
int kill(pid_t pid, int signo);  //向某个进程发送信号
int raise(int signo);            //向自己发送信号

kill参数有四种情况:

  • pid > 0 发送信号给pid进程
  • pid==0 发送信号给同组所有进程
  • pid<0 发送信号给等于其绝对值的进程组
  • pid==-1 发送给所有能发的进程

五. alarm和pause

函数原型:

#include<unistd.h>

usigned int alarm(unsigned int seconds);  //定时产生信号,默认操作终止当前进程
int pause(void);                          //挂起进程,等待信号

alarm函数的两个常用场景:

  1. 用于sleep函数的实现
    实现时注意点:
    (1)一个新的alarm可能导致已有的alarm失效,必须保存前面设置闹钟的参数,并在执行完此次闹钟后再重设前一次状态
    (2)如何防止程序中pause和alarm的竞争冲突而导致的调用者被永久挂起的问题。即alarm已经超时了pause才挂起,此时没有解除挂起的触发条件了。

以下为sleep函数的实现:

#include "apue.h"

static void sig_alrm(int signo)
{
	//不做任何处理,默认终止
}

unsigned int sleep(unsigned int seconds)
{
	struct sigaction newact, oldact;
	sigset_t         newmask, oldmask, suspmask;
	unsigned int     unslept;
	
	newact.sa_handler = sig_alrm;
	sigemptyset(&newact.sa_mask);     //将sig_alrm加入屏蔽信号
	newact.sa_flags = 0;
	sigaction(SIGALRM, &newmask, &oldmask); //设置新信号屏蔽集动作
 
 	sigemptyset(&newmask);
 	sigaddset(&newact, SIG_ALRM);
 	sigprocmask(SIG_BLOCK, &newmask, &oldmask);  //屏蔽newmask信号集

	alarm(seconds);                   //设置闹钟信号
 	suspmask = oldmask;               //保存旧信号集

	sigdelset(&suspmask, SIGALRM);    //删除旧信号集中SIGALRM信号
	sigsuspend(&suspmask);            //挂起旧信号集
	unslept = alarm(0);               //解除闹钟信号
	sigaction(SIGALRM, &oldact, NULL);  //恢复对SIGALRM的动作
	sigprocmask(SIG_SETMASK, &oldmask, NULL); //解除信号屏蔽
	return(unslept);                  //返回闹钟信号剩余时间
}
  1. 用于常见阻塞操作的时间上限的唤醒
    例如慢速I/O操作导致的阻塞,此时需要设置一个超时时间,防止系统永久阻塞。实现代码如下:
#include "apue.h"
#include <setjmp.h>

static void		sig_alrm(int);
static jmp_buf	env_alrm;

int
main(void)
{
	int		n;
	char	line[MAXLINE];

	if (signal(SIGALRM, sig_alrm) == SIG_ERR)   //信号处理函数
		err_sys("signal(SIGALRM) error");
	if (setjmp(env_alrm) != 0)                  //设置跳跃点
		err_quit("read timeout");

	alarm(10);                                  //10s后发送信号
	if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0)
		err_sys("read error");
	alarm(0);                                   //取消前一次注册(时间未到I/O操作已经完成)

	write(STDOUT_FILENO, line, n);
	exit(0);
}

static void
sig_alrm(int signo)
{
	longjmp(env_alrm, 1);
}

六. 信号操作函数

#include<signal.h>

int sigemptyset(sigset_t *set);         //初始化set所指信号集,清除所有信号
int sigfillset(sigset_t *set);          //使所指信号包含set指向的所有信号
int sigaddset(sigset_t *set, int signo);//添加一个信号至信号集
int sigdelset(sigset_t *set, int signo);//删除信号集中某个信号
int sigprocmark(int how, const sigset_t *restrict set, sigset_t *restrict oset);
/* 
   oset: 返回当前信号集屏蔽字
   set: 非空指针,通过how说明对当前信号集进行的操作
   how: 判断对当前信号集进行什么操作 
*/

信号操作函数使用示例:

#include "apue.h"

static void	sig_quit(int);

int main(void)
{
	sigset_t	newmask, oldmask, pendmask;

	if (signal(SIGQUIT, sig_quit) == SIG_ERR)   //SIGQUIT信号处理函数
		err_sys("can't catch SIGQUIT");
		
	/*
	 * Block SIGQUIT and save current signal mask.
	 */
	sigemptyset(&newmask);                      //初始换新信号集
	sigaddset(&newmask, SIGQUIT);               //将SIGQUIT加入新信号集
	if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)  //将SIGQUIT信号加入当前信号集阻塞队列,即开始阻塞SIGQUIT信号
		err_sys("SIG_BLOCK error");

	sleep(5);	/* SIGQUIT here will remain pending */

	if (sigpending(&pendmask) < 0)
		err_sys("sigpending error");
	if (sigismember(&pendmask, SIGQUIT))
		printf("\nSIGQUIT pending\n");

	/*
	 * Restore signal mask which unblocks SIGQUIT.
	 */
	if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)   //将SIGQUIT恢复至原理非阻塞状态
		err_sys("SIG_SETMASK error");
	printf("SIGQUIT unblocked\n");
	sleep(5);	/* SIGQUIT here will terminate with core file */
	exit(0);
}

static void sig_quit(int signo)
{
	printf("caught SIGQUIT\n");
	if (signal(SIGQUIT, SIG_DFL) == SIG_ERR)
		err_sys("can't reset SIGQUIT");
}

此程序可以验证退出信号在阻塞前后能否对进程产生影响。

七. 信号处理非局部函数

为解决longjmp在信号处理函数中该怎么处理当前信号屏蔽字的问题。重新定义了两个函数:

#include<setjmp.h>

int sifsetjmp(sigjmp_buf env, int savemask);   
/*区别就是savemask,非0时会保存信号屏蔽字,到siglongjmp将其返回*/
void siglongjmp(sijmp_buf env, int val);

八. 信号实现父、子进程间的同步

实现父子进程同步的为五个函数,分别为TELLWAIT, TELL_PARENT, TELL_CHILD, WAIT_PARENT, WAIT_CHILD。它们用信号实现代码如下:

#include "apue.h"

static volatile sig_atomic_t sigflag;           //信号执行标志
static sigset_t newmask, oldmask, zeromask;

static void sig_usr(int signo)
{
	sigflag = 1;
}

void TELL_WAIT(void)
{
	if(signal(SIGUSR1, sig_usr) == SIG_ERR)
		err_sys("signal(SIGUSR1) error");
	if(signal(SIGUSR2, sig_sur) == SIG_ERR)
		err_sys("signal(SIGUSR2) error");
	sigemptyset(&zeromask);                 //清除信号集
	sigemptyset(&newmask);                  
	sigaddset(&newmask, SIGUSR1);           //将SIGUSR1加入newmask信号集
	sigaddset(&newmask, SIGUWR2);
	
	if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
		err_sys("SIG_BLOCK error");
}

void TELL_PARENT(pid_t pid)
{
	kill(pid, SIGUSR2);                   //通知父进程子进程结束
}
void WAIT_PARENT(void)
{
	while(sigflag == 0)
		sigsuspend(&zeromask);           //恢复信号屏蔽字并使进程进入休眠等待父进程
	sigflag = 0;
	
	if(sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)  //返回其初始屏蔽状态 
		err_sys("SIG_SETMASK error");
}

void TELL_CHILD(pid_t pid)
{
	kill(pid, SIGUSR2);
}

void WAIT_CHILD(void)
{
	while(sigflag == 0)
		sigsuspend(&zeromask);
	sigflag = 0;

	if(sigpromask(SIG_SETMASK, &oldmask, NULL) < 0)
		err_sys("SIG_SETMASK error");
}

九. 函数sigaction

函数原型:

#include<signal.h>

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

参数分析:
signo: 检测或操作的信号编号。
act: 对信号执行的动作
oact: 此信号的上一个动作

其中,使用到了一个数据结构sigaction,其结构如下:

struct sigaction{
	void (*sa_handler)(int);
	sigset_t sa_mask;
	int sa_falgs;
	void (*sa_sigaction)(int, siginfo_t *, void *);
};

我们分析一下此结构包含的内容:

  • 若sa_handler字段包含信号捕捉函数的地址,则sa_mask字段包含的信号集将在信号捕捉函数调用前被加入到进程的信号屏蔽字当中。当从信号捕捉函数返回时才恢复到原先的值。以在调用信号处理函数时阻塞某些其他信号。
  • 一旦调用sigaction对信号设置了一个动作后,只有再调用sigaction显式的改变它才会改变这一设置的动作。
  • sa_flag字段表示对信号进行处理的各个可选标志。其可选项如下图所示:
    sa_flag参数

十. 带信号处理的system函数的实现

函数实现代码如下:

对比无信号处理system函数

#include	<sys/wait.h>
#include	<errno.h>
#include	<signal.h>
#include	<unistd.h>

int system(const char *cmdstring)	/* with appropriate signal handling */
{
	pid_t				pid;
	int					status;
	struct sigaction	ignore, saveintr, savequit;
	sigset_t			chldmask, savemask;

	if (cmdstring == NULL)
		return(1);		/* always a command processor with UNIX */

	ignore.sa_handler = SIG_IGN;	/* ignore SIGINT and SIGQUIT */
	sigemptyset(&ignore.sa_mask);
	ignore.sa_flags = 0;
	if (sigaction(SIGINT, &ignore, &saveintr) < 0)
		return(-1);
	if (sigaction(SIGQUIT, &ignore, &savequit) < 0)
		return(-1);
	sigemptyset(&chldmask);			/* now block SIGCHLD */
	sigaddset(&chldmask, SIGCHLD);
	if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0)
		return(-1);

	if ((pid = fork()) < 0) {
		status = -1;	/* probably out of processes */
	} else if (pid == 0) {			/* child */
		/* restore previous signal actions & reset signal mask */
		sigaction(SIGINT, &saveintr, NULL);
		sigaction(SIGQUIT, &savequit, NULL);
		sigprocmask(SIG_SETMASK, &savemask, NULL);

		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
		_exit(127);		/* exec error */
	} else {						/* parent */
		while (waitpid(pid, &status, 0) < 0)
			if (errno != EINTR) {
				status = -1; /* error other than EINTR from waitpid() */
				break;
			}
	}

	/* restore previous signal actions & reset signal mask */
	if (sigaction(SIGINT, &saveintr, NULL) < 0)
		return(-1);
	if (sigaction(SIGQUIT, &savequit, NULL) < 0)
		return(-1);
	if (sigprocmask(SIG_SETMASK, &savemask, NULL) < 0)
		return(-1);

	return(status);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值