Linux--信号概念及相关函数用法

信号是软件中断,提供异步处理事件的方法,当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。

一、信号列表

在Linux系统中可以通过kill -l指令查看。在ubuntu16下执行,得到如下信号:

1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2

Linux将信号定义在<bits/signum.h>中,ubuntu16中的位置在/usr/include/x86_64-linux-gnu/bits

#define SIGHUP          1       /* 如果终端接口检测到一个连接断开,则将此信号发送给该终端相关的控制进程 (POSIX).  */
#define SIGINT          2       /* 按下中断键(Delete/Ctrl+C)产生此信号,发送至前台进程组中的每一个进程 (ANSI).  */
#define SIGQUIT         3       /* 用户在终端按退出键,一般Ctrl+\ (POSIX).  */
#define SIGILL          4       /* 表示进程已执行一条非法硬件指令 (ANSI).  */
#define SIGTRAP         5       /* 指示一个实现定义的硬件故障 (POSIX).  */
#define SIGABRT         6       /* abort函数产生,进程异常终止. (ANSI) */
#define SIGIOT          6       /* 指示一个实现定义的硬件故障 (4.2 BSD).  */
#define SIGBUS          7       /* 指示一个实现定义的硬件故障,如某些内存故障 (4.2 BSD).  */
#define SIGFPE          8       /* 表示一个算数运算异常 (ANSI). */
#define SIGKILL         9       /* 两个不能被捕捉或忽略信号中的一个 (POSIX).  */
#define SIGUSR1         10      /* 用户自定义信号 (POSIX). */
#define SIGSEGV         11      /* 指示进程进行了一次无效的内存引用 (ANSI).  */
#define SIGUSR2         12      /* 用户自定义信号 (POSIX). */
#define SIGPIPE         13      /* 在管道读进程已终止时写管道产生此信号 (POSIX).  */
#define SIGALRM         14      /* alarm设置的定时器超时产生信号. (POSIX).  */
#define SIGTERM         15      /* 由kill(1)命令发送的系统默认终止信号 (ANSI).  */
#define SIGSTKFLT       16      /* 用于数学协处理器的栈故障. */
#define SIGCLD          SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD         17      /* 当进程终止或停止时,该信号发送给其父进程 (POSIX). */
#define SIGCONT         18      /* 发送给需要继续运行但当前处于停止状态的进程 (POSIX). */
#define SIGSTOP         19      /* 停止一个进程,不能被捕捉或忽略 (POSIX). */
#define SIGTSTP         20      /* 交互停止信号,当用户在中断上按挂起键时,终端驱动程序产生此信号 (POSIX). */
#define SIGTTIN         21      /* 当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号 (POSIX). */
#define SIGTTOU         22      /* 当一个后台进程组进程试图写其控制终端时,终端驱动程序产生此信号 (POSIX). */
#define SIGURG          23      /* 此信号通知进程已经发生了一个紧急情况 (4.2 BSD). */
#define SIGXCPU         24      /* 进程超过了软CPU时间限制产生此信号 (4.2 BSD). */
#define SIGXFSZ         25      /* 进程超过了其软文件长度限制产生此信号 (4.2 BSD). */
#define SIGVTALRM       26      /* 当一个由setitimer(2)函数设置的虚拟间隔时间已经超时时,产生此信号 (4.2 BSD). */
#define SIGPROF         27      /* setitimer()函数设置的Profiling interval timer超时产生该信号 (4.2 BSD). */
#define SIGWINCH        28      /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL         SIGIO   /* 在一个可轮询设备上发生一个特定事件时产生此信号. */
#define SIGIO           29      /* 指示一个异步IO事件 (4.2 BSD). */
#define SIGPWR          30      /* 依赖于系统的信号,用于具有不间断电源(UPS)的系统 (System V). */
#define SIGSYS          31      /* 该信号指示一个无效的系统调用. */
#define SIGUNUSED       31
#define _NSIG           65      /* Biggest signal number + 1 (including real-time signals). */
#define SIGRTMIN        (__libc_current_sigrtmin ())
#define SIGRTMAX        (__libc_current_sigrtmax ())

二、可靠与不可靠信号

信号值小于SIGRTMIN的信号都是不可靠信号(非实时信号),主要问题是信号可能丢失。后来加了一些新的信号定义为可靠信号(实时信号),这些信号支持排队不会丢失。信号的可靠与不可靠只与信号值有关,与信号的发送及安装函数无关。Linux中的signal()是通过sigation()函数实现的,因此,即使通过signal()安装的信号,在信号处理函数的结尾也不必再调用一次信号安装函数。同时,由signal()安装的实时信号支持排队,同样不会丢失。不可靠信号属于UNIX支持的信号,除了SIGUSR1和SIGUSR2都默认规定了特殊用途。
对于目前Linux的两个信号安装函数:signal()及sigaction()来说,它们都不能把SIGRTMIN以前的信号变成可靠信号(都不支持排队,仍有可能丢失,仍然是不可靠信号),而且对SIGRTMIN以后的信号都支持排队。这两个函数的最大区别在于,经过sigaction安装的信号都能传递信息给信号处理函数,而经过signal安装的信号不能向信号处理函数传递信息。对于信号发送函数来说也是一样的。

三、函数signal

用于改变进程接收到特定信号后的行为。

3.1 定义

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

3.2 参数

signum参数是信号名,handler的值可以是常量SIG_IGN、常量SIG_DFL或当接收到此信号后要调用的函数地址。SIG_IGN向内核表示忽略此信号(SIGKILL和SIGSTOP两个信号不能忽略);SIG_DFL表示接收到信号后执行默认动作;指定函数地址时,则在信号发生时调用该函数,称此函数为信号处理程序或信号捕捉函数。

3.3 返回值

返回信号处理程序的先前值,或者出错时返回SIG_ERR。 如果发生错误,errno设置为指示原因。

#define SIG_ERR ((__sighandler_t) -1)           /* Error return.  */
#define SIG_DFL ((__sighandler_t) 0)            /* Default action.  */
#define SIG_IGN ((__sighandler_t) 1)            /* Ignore signal.  */

在这里插入图片描述

3.4 信号来源

  • 硬件来源:比如按下了键盘或者其它硬件故障;
  • 软件来源:调用函数发送任意信号给另外一个进程或进程组,信号发送函数有kill、raise、alarm、abort等,软件来源还包括一些非法运算等操作。

3.5 相关函数

  • int kill(pid_t pid, int sig);
    将信号发送给进程或进程组(系统调用)
  • int raise(int sig);
    向进程自身发送信号(库函数)
  • unsigned int alarm(unsigned int seconds);
    设置闹钟,超时发送SIGALRM信号(系统调用)
  • void abort(void);
    首先解除进程对SIGABRT信号的阻止,然后向调用进程发送该信号(库函数)
  • int pause(void);
    将进程挂起直到捕捉到一个信号(系统调用)

3.6 测试代码

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

void sig_usr(int signo)
{
	if (signo == SIGUSR1)
		printf("received SIGUSR1.\n");
	else
		printf("received signal %d.\n", signo);
}

int main()
{
	if (signal(SIGUSR1, sig_usr) == SIG_ERR)
		printf("can't catch SIGUSR1.\n");
	for ( ; ; )
		pause();
}

在这里插入图片描述

3.7 函数缺陷

不改变信号的处理方式就不能确定信号的当前处理方式,很多捕捉信号的交互程序具有下列形式的代码:

void sig_int(int);

if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	signal(SIGINT, sig_int);

这样处理后仅当SIGINT未被忽略时进程才会捕捉它们。

四、函数sigaction

函数signal加强版。

4.1 定义

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

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

siginfo_t {
	int      si_signo;     /* Signal number */
	int      si_errno;     /* An errno value */
	int      si_code;      /* 信号产生的原因,对所有信号有意义 */
	int      si_trapno;    /* Trap number that caused hardware-generated signal (unused on most architectures) */
	pid_t    si_pid;       /* Sending process ID */
	uid_t    si_uid;       /* Real user ID of sending process */
	int      si_status;    /* Exit value or signal */
	clock_t  si_utime;     /* User time consumed */
	clock_t  si_stime;     /* System time consumed */
	sigval_t si_value;     /* Signal value */
	int      si_int;       /* POSIX.1b signal */
	void    *si_ptr;       /* POSIX.1b signal */
	int      si_overrun;   /* Timer overrun count; POSIX.1b timers */
	int      si_timerid;   /* Timer ID; POSIX.1b timers */
	void    *si_addr;      /* Memory location which caused fault */
	long     si_band;      /* Band event (was int in glibc 2.3.2 and earlier) */
	int      si_fd;        /* File descriptor */
	short    si_addr_lsb;  /* Least significant bit of address (since Linux 2.6.32) */
	void    *si_call_addr; /* Address of system call instruction(since Linux 3.5) */
	int      si_syscall;   /* Number of attempted system call(since Linux 3.5) */
	unsigned int si_arch;  /* Architecture of attempted system call(since Linux 3.5) */
}

4.2 参数

参数signum是要检测或修改具体动作的信号编号。act为输入型参数,若act指针非空,则修改其动作。oldact为输出型参数,如果oldact指针非空,则系统经由oldact指针返回该信号的上一个动作。

4.3 返回值

成功返回 0,失败返回 -1,errno指示错误。

4.4 结构体参数

  • sa_flags:指定一组修改信号行为的标志。
选项 说明
SA_NOCLDSTOP 如果signum是SIGCHLD,当子进程停止时(即当它们收到SIGSTOP,SIGTSTP,SIGTTIN或SIGTTOU之一时)或恢复(即它们接收SIGCONT)时,不接收通知。只有在为SIGCHLD建立处理程序时,此标志才有意义。
SA_NOCLDWAIT 如果signum是SIGCHLD,则当调用进程的子进程终止时,不创建僵尸进程。只有在为SIGCHLD建立处理程序或将该信号的处置设置为SIG_DFL时,此标志才有意义。
SA_NODEFER 当捕捉到这个信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号(除非sa_mask包括了此信号)。
SA_ONSTACK 在 sigaltstack(2) 提供的备用信号堆栈上调用信号处理程序。 如果备用堆栈不可用,则将使用默认堆栈。 只有在建立信号处理程序时,此标志才有意义。
SA_RESETHAND 进入信号处理程序后,将信号动作重置为SIG_DFL,并清除SA_SIGINFO标志,但是不能重置SIGILL和SIGTRAP信号的配置。 只有在建立信号处理程序时,此标志才有意义。
SA_RESTART 通过使某些系统调用可以跨信号重新启动,提供与BSD信号语义兼容的行为。 只有在建立信号处理程序时,此标志才有意义。
SA_RESTORER 不适合应用程序使用。 C库使用此标志来指示sa_restorer字段包含“signal trampoline”的地址。 有关详细信息,请参阅sigreturn(2)。
SA_SIGINFO 此选项对信号处理程序提供了附加信息:一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针。
  • sa_handler:指定信号捕捉后的信号处理程序。
  • sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。
  • sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。
  • sa_mask:是一个信号集,在调用该信号捕捉函数之前,将需要block的信号加入这个sa_mask,仅当信号捕捉函数正在执行时,才阻塞sa_mask中的信号,当从信号捕捉函数返回时进程的信号屏蔽字复位为原先值,原文地址

Q1:这个复位动作是sigaction函数内部处理,还是由调用者自己处理呢?
  由sigaction函数自动复位,不用我自己再去处理。
Q2:设置sa_mask的目的?
  在调用信号处理程序时就能阻塞某些信号。注意仅仅是在信号处理程序正在执行时才能阻塞某些信号,如果信号处理程序执行完了,那么依然能接收到这些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号,也就是说自己也被阻塞,除非设置了SA_NODEFER。因此保证了在处理一个给定的信号时,如果这种信号再次发生,通常并不将它们排队,所以如果在某种信号被阻塞时它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
Q3:对于不同信号,当信号A被捕捉到并信号A的handler正被调用时,信号B产生了,
  3.1如果信号B没有被设置阻塞,那么正常接收信号B并调用自己的信号处理程序。另外,如果信号A的信号处理程序中有sleep函数,那么当进程接收到信号B并处理完后,sleep函数立即返回(如果睡眠时间足够长的话)
  3.2如果信号B有被设置成阻塞,那么信号B被阻塞,直到信号A的信号处理程序结束,信号B才被接收并执行信号B的信号处理程序。
  如果在信号A的信号处理程序正在执行时,信号B连续发生了多次,那么当信号B的阻塞解除后,信号B的信号处理程序只执行一次。
  如果信号A的信号处理程序没有执行或已经执行完,信号B不会被阻塞,正常接收并执行信号B的信号处理程序。
Q4:对于相同信号,当一个信号A被捕捉到并信号A的handler正被调用时,
  4.1 又产生了一个信号A,第二次产生的信号被阻塞,直到第一次产生的信号A处理完后才被递送;
  4.2 如果连续产生了多次信号,当信号解除阻塞后,信号处理函数只执行一次。

五、函数sigqueue

发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数sigaction()配合使用。

5.1 定义

int sigqueue(pid_t pid, int sig, const union sigval value);

union sigval {
	int   sival_int;
	void *sival_ptr;
};

typedef union sigval {
	int sival_int;
	void *sival_ptr;
} sigval_t;

5.2 参数

pid参数指定接收信号的进程的id;sig参数指定要发送的信号;value参数指定信号传递的参数,该参数类型为四字节的联合体类型。

5.3 返回值

成功返回 0,失败返回 -1,errno指示错误。

5.4 用法

sigqueue函数只能把信号发送给单个进程,可以使用value参数向信号处理程序传递整数或者指向更多信息的缓冲区指针,除此之外,sigqueue函数和kill函数类似。

如果接收进程使用SA_SIGINFO标志sigaction安装了此信号的处理程序,则它可以通过第二个参数传递的siginfo_t结构的si_value字段获取sigval数据。此外,该结构的si_code字段将被设置为SI_QUEUE。

siginfo_t 结构体参数si_value获取了sigqueue函数参数sigval value的值,siginfo_t 结构体参数si_int对应了sigval value的sival_int值,siginfo_t 结构体参数si_ptr对应了sigval value的sival_ptr值,所以以下两种写法效果一样。
info->si_value.sival_int 等同于 info->si_int

5.5 测试代码

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

void sig_usr(int signal, siginfo_t *info, void *act);

int main()
{
	struct sigaction act;
	act.sa_sigaction = sig_usr;
	sigemptyset(&act.sa_mask);
	act.sa_flags = SA_SIGINFO;
	if (sigaction(SIGUSR1, &act, NULL) < 0)
	{
		printf("sigaction error.\n");
		exit(-1);
	}
	
	union sigval mysigval;
    mysigval.sival_int = 27;
	if (sigqueue(getpid(), SIGUSR1, mysigval) < 0)
	{
        printf("sigqueue error.\n");
        exit(-1);
    }
	
	sleep(2);	
	return 0;
}

void sig_usr(int signal, siginfo_t *info, void *act)
{
	printf("signal num %d.\n", signal);
	printf("si_value.sival_int = %d.\n", info->si_value.sival_int);
	printf("si_int = %d.\n", info->si_int);
}

在这里插入图片描述


参考资料

猜你喜欢

转载自blog.csdn.net/Meteor_s/article/details/84832142
今日推荐