Linux -- signal信号

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rikeyone/article/details/88888059

简介

信号是操作系统提供了一种处理异步事件的方法,每个用户进程都会接受到信号,并且可以注册信号处理函数,当信号到来时会执行注册的处理函数。
当信号到来是可以告诉内核使用如下三种方式处理对应的信号:

  • 忽略该信号。大多数信号都可以做此操作,除了SIGKILL和SIGSTOP这两个信号,代码中信号处理函数注册为SIG_IGN可以忽略信号。
  • 捕获信号。注册信号处理函数为自己想要执行的用户函数,同样的,SIGKILL和SIGSTOP信号不能捕获。
  • 执行默认动作。不同的信号具有不同默认动作,不过大部分都是结束该进程,代码中信号处理函数注册为SIG_DFL可以恢复默认动作。

本文只介绍一些关键的常用信号,其他信号请参考其他文档:

 SIGABRT: 异常终止信号,默认动作:终止+core dump
 SIGALRM: 定时器超时信号,默认动作:终止
 SIGCHLD: 子进程终止或者状态改变,默认动作:忽略
 SIGHUP: 终端连接断开,只会发送给拥有控制终端的控制进程,一般是会话主进程,默认动作:终止
 SIGINT: 终端中断符(ctrl-c),发送给整个前台进程组,默认动作:终止
 SIGQUIT: 终端退出符(ctrl-\),发送给整个前台进程组,默认动作:终止+core dump
 SIGTSTOP: 终端停止(挂起)信号(ctrl-z),发送给整个前台进程组,默认动作:停止进程
 SIGIO: 异步I/O信号,默认动作:系统差异,可能终止也可能是忽略
 SIGKILL: 杀死信号,默认动作:终止
 SIGPIPE: 写入无读进程的管道,默认动作:终止
 SIGSEGV: 无效内存访问(段错误),默认动作:终止+core dump
 SIGSTOP: 停止信号,默认动作:停止进程
 SIGTERM: 终止信号,默认动作:终止
 SIGURG: 进程紧急情况信号,用于socket接收带外数据,默认动作:忽略
 SIGUSR1: 用户定义信号,默认动作:终止
 SIGUSR2: 用户定义信号,默认动作:终止

关于信号的继承特性:

  1. 当调用fork创建一个新进程时,子进程会继承父进程的信号处理方式。
  2. 当子进程使用exec替换一个新程序时,原先设置为要捕获的信号都更改为默认动作,其他信号保持不变。

signal函数

 #include <signal.h>
 void ( *signal(int signum, void (*handler)(int)) ) (int);

这个函数声明很难以理解,实际上它等于如下的定义方式:

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

这是最简单的处理信号机制的接口,根据Linux、Unix的不同存在很多个实现版本,在一些版本上是按照不可靠信号语义实现,因此它可能是不可靠的并且存在移植性的问题,所以应该尽量避免使用它,而改用sigaction函数。
这个接口在一些平台上可能是不可靠的,所谓不可靠,那就是说明有一些信号可能会被丢失,原因是该信号处理函数每次设置只会生效一次,一般的做法是在信号处理函数中重新再设置一遍,但是两个设置之间并不是原子操作,所以会产生丢失现象。
它接收两个参数,第一个参数是信号number,第二个是信号处理回调函数。返回一个函数指针,该指针指向在此之前的上一个信号处理函数。如果出错返回SIG_ERR。

sigaction函数

#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

返回值:成功返回0,错误返回-1

和signal功能一样,设置信号处理函数,但是它属于可靠信号的实现,建议使用它来设置信号处理函数。

sigprocmask函数

#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

返回值:成功返回0,错误返回-1

该函数可以设置、清除、查看信号的屏蔽字。how可选三个值:

SIG_BLOCK: 进程屏蔽字是set和当前屏蔽字的并集。
SIG_UNBLOCK: 解除对set中信号的屏蔽。
SIG_SETMASK: 更新进程信号屏蔽字为set。

set为要设置的屏蔽字信号集合,oset为设置前的进程屏蔽字集合。

sigpending函数

#include <signal.h>
int sigpending(sigset_t *set);

返回值:成功返回0,错误返回-1

该函数返回信号阻塞期间产生的信号集,也叫挂起状态的信号集。比如一个信号被设置为阻塞,在阻塞期间,内核依然产生了该信号,那么它就属于被挂起的信号,当解除阻塞后该挂起信号是会重新送达给进程的。

sigsuspend函数

#include <signal.h>
int sigsuspend(const sigset_t *sigmask);

返回值:成功返回0,错误返回-1

该函数阻塞当前进程等待信号的到来,和pause不同,它会执行如下三步:

  1. 修改当前信号屏蔽字,按照sigmask阻塞信号,只等待其他信号
  2. 阻塞等待其他信号的到来
  3. 恢复当前信号屏蔽字为旧值

之所以增加此API,是因为这三步可以认为是原子操作,不用担心中间存在不安全的时间窗口,到时信号丢失或者其他异常。

发送信号函数

#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

#include <stdlib.h>
void abort(void);

kill可以向指定进程发送一个信号,raise只能向本进程发送一个信号。
alarm函数会设定定时器,当时间到期后向本进程发送一个SIGALRM信号。
abort异常终止函数,向本进程发送SIGABRT异常终止信号。

pause函数

#include <unistd.h>
int pause(void);

阻塞进程等待一个信号的到来,只有执行了相应的信号处理程序后才会从pause返回。返回值是-1, errno设置为EINTR。

被信号中断的系统调用

一个低速系统调用可能会阻塞,如果再阻塞期间,进程捕获到了一个信号,则该系统调用会被中断不再继续执行,系统调用返回错误,errno被设置为EINTR。针对这种中断,需要软件进行处理,以重启系统调用恢复执行。
比如一个典型的read操作:

ssize_t
readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;
    ssize_t nread;
    char    *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;/* and call read() again */
            else
                return(-1);
        } else if (nread == 0)
            break;      /* EOF */

        nleft -= nread;
        ptr   += nread;
    }
    return(n - nleft);          /* return >= 0 */
}

针对这种特定场景,有些系统支持自动重启系统调用,并且可以针对每个信号配置是否允许重启系统调用:

int set_signal(int signo, SIG_FUNC func)
{
    struct sigaction act, oact;
    act.sa_handler = func;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if (signo == SIGALRM) {
#ifdef  SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;       /* SunOS 4.x */
#endif
    } else {        //Only SIGALRM can interrupt the system call, otherwise we restart system call 
#ifdef  SA_RESTART
        act.sa_flags |= SA_RESTART;     /* SVR4, 44BSD */
#endif
    }
    if (sigaction(signo, &act, &oact) < 0)
        return(-1);
    return 0;
}

为了软件的可移植性,需要使用#ifdef来判断平台是否支持对应的属性。

示例

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <string.h>


int set_signal(int signo, void func(int signo))
{
	struct sigaction act, oact;

	act.sa_handler = func;
	sigemptyset(&act.sa_mask);
	act.sa_flags = 0;
	if (signo == SIGALRM) {
#ifdef SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT;
#endif
	} else {
#ifdef SA_RESTART
		act.sa_flags |= SA_RESTART;
#endif
	}
	if (sigaction(signo, &act, &oact) < 0) {
		printf("sigaction error,%s\n",strerror(errno));
		return 1;
	}
	return 0;
}

void signal_handler(int signo)
{
	switch (signo) {
	case SIGALRM:
		printf("SIGALRM triggered\n");
		break;
	case SIGINT:
		printf("SIGINT triggered\n");
		break;
	case SIGQUIT:
		printf("SIGQUIT triggered\n");
		break;
	case SIGTERM:
		printf("process terminated!!!\n");
		break;
	default:
		printf("Do nothing for signal:%d\n", signo);
		break;
	}
}

int sigprocmask_test(void)
{
	sigset_t set, oset, pendset;

	if (set_signal(SIGINT, signal_handler) < 0) {
		printf("set signal error, exit\n");
		exit(1);
	}
	if (set_signal(SIGQUIT, signal_handler) < 0) {
		printf("set signal error, exit\n");
		exit(1);
	}

	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) {
		printf("sigprocmask error, exit\n");
		exit(1);
	}
	printf("blocked signal SIGINT, please try ctrl-c!\n");
	printf("use ctrl-\\ to wake up process using SIGQUIT\n");
	printf("wait for a signal by pause...\n");
	pause();

	if (sigpending(&pendset) < 0) {
		printf("sigpending error\n");
		exit(1);
	}

	if (sigismember(&pendset, SIGINT)) {
		printf("SIGINT is pending\n");
	}

	if (sigprocmask(SIG_SETMASK, &oset, NULL) < 0) {
		printf("sigprocmask error, exit\n");
		exit(1);
	}
	printf("reset signal mask to default, please try ctrl-c again!\n");

	/* 
	 * during reset and pause, it has a time window here which is unsafe 
	 * this time window may make the pause wait for another signal to wake up
	 * but not the pending one
	 */

	pause();
}

int sigsuspend_test(void)
{
	sigset_t set, oset, pendset, waitset;

	if (set_signal(SIGINT, signal_handler) < 0) {
		printf("set signal error, exit\n");
		exit(1);
	}
	if (set_signal(SIGQUIT, signal_handler) < 0) {
		printf("set signal error, exit\n");
		exit(1);
	}

	sigemptyset(&set);
	sigaddset(&set, SIGINT);
	if (sigprocmask(SIG_BLOCK, &set, &oset) < 0) {
		printf("sigprocmask error, exit\n");
		exit(1);
	}
	printf("blocked signal SIGINT, please try ctrl-c!\n");
	printf("use ctrl-\\ to wake up process using SIGQUIT\n");
	printf("wait for a signal by pause...\n");
	pause();

	if (sigpending(&pendset) < 0) {
		printf("sigpending error\n");
		exit(1);
	}

	if (sigismember(&pendset, SIGINT)) {
		printf("SIGINT is pending\n");
	}

	printf("sigsuspend wait for a signal without SIGQUIT\n");
	sigemptyset(&waitset);
	sigaddset(&waitset, SIGQUIT);

	//suspend return -1 as success return
	if (sigsuspend(&waitset) != -1) {
		printf("sigsuspend error, exit\n");
		exit(1);
	}
	printf("sigsuspend get an signal and returned\n");
	printf("sigsuspend should reset signal mask to the status before sigsuspend, please try ctrl-c again!\n");
	pause();
}

/* alarm test */
int alarm_test(void)
{
	char buf[20];
	int n;

	if (set_signal(SIGALRM, signal_handler) < 0) {
		printf("set signan error, exit\n");
		exit(1);
	}
	printf("alarm timer will expire 10 seconds later\n");
	alarm(10);

	printf("read blockly from stdin for 20 characters, timeout 10 seconds\n");
	n = read(STDIN_FILENO, buf, 20);
	if (n < 0) {
		if (errno == EINTR)
			printf("read interrupted by signal, exit\n");
		exit(1);
	}

	printf("read returned successfully, cancel the alarm timer\n");
	alarm(0);
	write(STDOUT_FILENO, buf, n);
	exit(0);

}

void usage(int argc, char *argv[])
{
	printf("Usage:\n");
	printf("\t %s [-a] \"alarm test\"\n", argv[0]);
	printf("\t %s [-m] \"sigprocmask test\"\n", argv[0]);
	printf("\t %s [-s] \"sigsuspend test\"\n", argv[0]);
}

int main(int argc, char *argv[])
{
	int opt, ret, i;
	pid_t pid;

	if (argc == 1) {
		usage(argc, argv);
		exit(1);
	}

	while((opt = getopt(argc, argv, "sam")) != -1) {
		switch(opt) {
		case 'a':
			alarm_test();
			break;
		case 'm':
			sigprocmask_test();
			break;
		case 's':
			sigsuspend_test();
			break;
		default:
			printf("unknown option:%c\n", optopt);
			usage(argc, argv);
			break;
		}
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/rikeyone/article/details/88888059