Linux应用 信号
基本概念
信号是事件发生时对进程的通知,可以理解为软件中断,其提供了一种处理异步事件的方法,因为我们无法知晓信号的到来时间,所以说他是异步通知的。
信号由谁发出
信号的目的是用来通信,其通常会被发出的情况有这些
1、硬件检测到异常,首先会将错误条件发送给内核, 内核发送对应的信号给进程,进程做出对应的处理
2、终端,在终端中输入能够产生信号的特殊字符,例如CTRL+C 产生SIGINT CTRL+Z产生SIGCONT
3、进程,进程调用kill函数可以发送任意信号到一个进程或者是进程组
4、用户也可以通过KILL命令发送信号给指定PID的进程
5、软件事件,例如计算中出现除0或者是访问非法内存,都会产生信号
信号做的就是将情况告知给相应的进程,进而实现同步通信的目的。
信号由谁处理
信号由接受到信号的进程处理
信号怎么处理
对于大部分信号,内核会有一个默认的处理方式,系统默认的处理方式一般都是终止该进程。当然,我们也可以通过一些函数来更改当前进程对于信号的处理方式。还可以规定当前进程忽略某些信号的处理。
信号的本质
信号的本质是int类型的数字编号,内核给我们进行了定义
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
信号的分类
可靠信号和不可靠信号
他们两个的区分方式便是信号会不会丢失,Linux系统中,信号编号高于34的都不可靠信号。后边将这个64以上的信号规定为不可靠信号。
可靠信号支持排队,所以就不会丢失。
实时信号和非实时信号、
它们两个的是从时间关系上进行的分类,非实时信号不支持排队,所以它也是不可靠信号,实时信号支持排队,所以它是可靠信号。非实时信号也叫标准信号。
进程对信号的处理
前面说过我们可以更改进程对于一些信号的处理方式,主要是通过两个函数来进行更改
子进程会继承父进程的信号处理方式0
在没有调用signal()更改处理方式之前,系统对于信号的处理都是默认操作
信号掩码
如果进程接收到了信号掩码中的这些信号,那么这个信号将会被阻塞暂时不能得到处理,直到这些信号从进程的信号掩码中移除
信号处理函数类似中断处理函数,越简单越好
signal函数
#include <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);
//signum:要更改的信号标号
//handler:要更改的处理函数句柄 可以设置为SIG_IGN忽略 SIG_DFL默认
//成功以后返回之前的信号处理函数,失败返回 SIG_ERR(-1),并会设置 errnostatic void sig_handler(int sig)
{printf("Received signal: %d\n", sig);
}
sigaction函数
sigaction()允许单独获取信号的处理函数而不是设置,并且还可以设置各种属性对调用信号处理函数时的行为施以更加精准的控制,其函数原型如下所示
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
//signum:信号标号
//act:新的处理函数
//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; /* Signal code */int si_trapno; /* Trap number that caused hardware-generated signal(unused on mostarchitectures) */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) */
}
向进程发送信号
kill函数
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
//pid:指定进程的pid
//sig:要发送的信号
raise函数
向自身发送信号
#include <signal.h>
int raise(int sig);
//sig:向自身发送的信号
//等价于kill(getpid(), sig);
//getpid()获取自身进程的pid
有关时间的信号
alarm函数
在指定时间以后给进程发送一个SIGALRM信号
每个进程只能设置一个alarm闹钟
每个alarm闹钟只能触发一次信号,想要循环的话,需要在处理函数中重新设置闹钟
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
//设置当前发送SIGALRM信号的时间为seconds以后
pause函数
将进程设置为休眠状态,直至有信号唤醒
#include <unistd.h>
int pause(void);
信号集
信号集是一个sigset_t结构体,有一些指定的api函数来操作它
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} sigset_t;
相关api函数
#include <signal.h>
int sigemptyset(sigset_t *set); //初始化空的信号集
int sigfillset(sigset_t *set); //初始化包含所有信号的信号集
int sigaddset(sigset_t *set, int signum); //添加信号到信号集
int sigdelset(sigset_t *set, int signum); //从信号集中删除信号
int sigismember(const sigset_t *set, int signum); //查看信号是否在信号集中
获取信号的描述信息
sys_siglist数组
在 Linux 下,每个信号都有一串与之相对应的字符串描述信息,用于对该信号进行相应的描述。这些字符串位于 sys_siglist 数组中,sys_siglist 数组是一个 char 类型的数组,数组中的每一个元素存放的是一个字符串指针,指向一个信号描述信息。譬如,可以使用 sys_siglist[SIGINT]来获取对 SIGINT 信号的描述。需要引用头文件*<signal.h>**
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{printf("SIGINT 描述信息: %s\n", sys_siglist[SIGINT]);printf("SIGQUIT 描述信息: %s\n", sys_siglist[SIGQUIT]);printf("SIGBUS 描述信息: %s\n", sys_siglist[SIGBUS]);exit(0);
}
strsignal函数
#include <string.h>
char *strsignal(int sig);
//使用结果同上所述
psignal函数
#include <signal.h>
void psignal(int sig, const char *s);
//指定查看信号信息,同时加上s附加说明
信号掩码
信号掩码是对于进程来说的
其本质是一个信号集
信号掩码中的信号将会被阻塞、无法传递给进程进行处理,那么内核会将其阻塞,直到该信号从信号掩码中移除,内核才会把该信号传递给进程从而得到处理。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
//set:新信号掩码集
//oldset:旧信号掩码集
//how:一些信号掩码操作标识,
//SIG_BLOCK:将参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中。换言之,将信号掩码设置为当前值与 set 的并集。
//SIG_UNBLOCK:将参数 set 指向的信号集内的所有信号从进程信号掩码中移除。
//SIG_SETMASK:进程信号掩码直接设置为参数 set 指向的信号集。
阻塞等待信号
恢复信号掩码和 pause()挂起进程这两个动作封装成一个原子操作,避免信号提前发送导致进程休眠。
#include <signal.h>
int sigsuspend(const sigset_t *mask);
//将参数 mask 所指向的信号集来替换进程的信号掩码,也就是将进程的信号掩码设置为参数 mask 所指向的信号集,然后挂起进程,直到捕获到信号被唤醒(如果捕获的信号是 mask 信号集中的成员,将不会唤醒、继续挂起)、并从信号处理函数返回,一旦从信号处理函数返回,sigsuspend()会将进程的信号掩码恢复成调用前的值
//其执行过程等价于
sigprocmask(SIG_SETMASK, &mask, &old_mask);
pause();
sigprocmask(SIG_SETMASK, &old_mask, NULL);
//主要用于等待指定的信号到来
实时信号
获取进程中等待的信号
#include <signal.h>
int sigpending(sigset_t *set);
//set:存放当前进程中等待的信号的信号集
发送实时信号
应用程序当中使用实时信号,需要有以下的两点要求:
1、发送进程使用 sigqueue()系统调用向另一个进程发送实时信号以及伴随数据。
2、接收实时信号的进程要为该信号建立一个信号处理函数,使用sigaction函数为信号建立处理函数,并加入 SA_SIGINFO,这样信号处理函数才能够接收到实时信号以及伴随数据,也就是要使用sa_sigaction 指针指向的处理函数,而不是 sa_handler,当然允许应用程序使用 sa_handler,但这样就不能获取到实时信号的伴随数据了。
使用sigqueque发送实时信号
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
//value是信号的伴随数据,一个整形或者是一个指针
typedef union sigval
{int sival_int;void *sival_ptr;
} sigval_t;//接受实时信号的处理函数如下
static void sig_handler(int sig, siginfo_t *info, void *context)
{sigval_t sig_val = info->si_value;printf("接收到实时信号: %d\n", sig);printf("伴随数据为: %d\n", sig_val.sival_int);
}
abort异常退出
进程中可以使用abort()库函数发送SIGABRT信号终止当前进程的运行
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_handler(int sig)
{printf("接收到信号: %d\n", sig);
}
int main(int argc, char *argv[])
{struct sigaction sig = {0};sig.sa_handler = sig_handler;sig.sa_flags = 0;if (-1 == sigaction(SIGABRT, &sig, NULL)) {perror("sigaction error");exit(-1);}sleep(2);abort(); // 调用 abortfor ( ; ; )sleep(1);exit(0);
}
无论阻塞或忽略 SIGABRT 信号,abort()调用均不收到影响,总会成功终止进程。