参考引用
进程同样也可以向自身发送信号,但发送给进程的诸多信号中,大多数都是来自于内核
/* 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 信号机制基本上是从 UNIX 系统中继承过来的,早期 UNIX 系统中的信号机制存在问题
Linux 支持不可靠信号,但是对不可靠信号机制做了改进
新增加了一些信号 (SIGRTMIN(34)~ SIGRTMAX(64)),并把它们定义为可靠信号
$ kill -l
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
63) SIGRTMAX-1 64) SIGRTMAX
系统调用 signal() 函数可将信号的处理方式设置为捕获信号、忽略信号以及系统默认操作
#include <signal.h>
typedef void (*sig_t)(int);
sig_t signal(int signum, sig_t handler);
/* Fake signal functions */
#define SIG_ERR ((sig_t) -1) /* Error return. */
#define SIG_DFL ((sig_t) 0) /* Default action. */
#define SIG_IGN ((sig_t) 1) /* Ignore signal. */
signal() 函数使用示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void sig_handler(int sig) {
// 于测试程序中捕获了该信号,而对应的处理方式仅仅只是打印一条语句,而并不终止进程
printf("Received signal: %d\n", sig);
}
int main(int argc, char* argv[]) {
sig_t ret = NULL;
ret = signal(SIGINT, (sig_t)sig_handler);
if (SIG_ERR == ret) {
perror("signal error");
exit(-1);
}
for (;;) {}
exit(0);
}
$ gcc signal.c -o signal
$ ./signal
^CReceived signal: 2
^CReceived signal: 2
^CReceived signal: 2
# 需另开一个终端 kill -9 xxxx(pid)
Killed
# 2617 为 ./signal 对应的 pid 号
# SIGKILL(编号为 9)
$ kill -9 2617
除了 signal() 之外,sigaction() 系统调用是设置信号处理方式的另一选择,推荐使用 sigaction() 函数。虽然 signal() 函数简单好用,而 sigaction() 更复杂,但 sigaction() 更具灵活性以及移植性
#include <signal.h>
// signum:需要设置的信号,除了 SIGKILL 信号和 SIGSTOP 信号之外的任何信号
// act:一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构,该数据结构描述了信号的处理方式
// 如果参数 act 不为 NULL,则表示需要为信号设置新的处理方式
// 如果参数 act 为 NULL,则表示无需改变信号当前的处理方式
// oldact:一个 struct sigaction 类型指针,指向一个 struct sigaction 数据结构
// 如果参数 oldact 不为 NULL,则会将信号之前的处理方式等信息通过参数 oldact 返回出来
// 如果无意获取此类信息,那么可将该参数设置为 NULL
// 返回值:成功返回 0;失败将返回-1,并设置 errno
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction 结构体
struct sigaction {
void (*sa_handler)(int); // sa_handler:指定信号处理函数,与 signal() 函数的 handler 参数相同
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void); // sa_restorer:该成员已过时,不要再使用
};
示例:sigaction() 函数使用
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
static void sig_handler(int sig) {
printf("Received signal: %d\n", sig);
}
int main(int argc, char *argv[]) {
struct sigaction sig = {0};
int ret;
sig.sa_handler = sig_handler;
sig.sa_flags = 0;
ret = sigaction(SIGINT, &sig, NULL);
if (-1 == ret) {
perror("sigaction error");
exit(-1);
}
/* 死循环 */
for (;;) {}
exit(0);
}
kill() 系统调用可将信号发送给指定的进程或进程组中的每一个进程
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
示例:使用 kill() 函数向一个指定的进程发送信号
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc, char* argv[]) {
int pid;
/* 判断传参个数 */
if (argc < 2) {
exit(-1);
}
/* 将传入的字符串转为整形数字 */
pid = atoi(argv[1]);
printf("pid: %d\n", pid);
/* 向 pid 指定的进程发送信号 */
if (kill(pid, SIGINT) == -1) {
perror("kill error");
exit(-1);
}
exit(0);
}
# 将上一小节代码运行于后台
$ ./signal &
[1] 2869
$ Received signal: 2
# 另开一个终端
$ gcc kill.c -o kill
$ ./kill 2869
pid: 2869
#include <signal.h>
// sig:需要发送的信号。
// 返回值:成功返回 0;失败将返回非零值
int raise(int sig);
使用 alarm() 函数可以设置一个定时器(闹钟),当定时器时间到,内核会向进程发送 SIGALRM 信号
#include <unistd.h>
// seconds:设置定时时间,以秒为单位;如果为 0,则表示取消之前设置的 alarm 闹钟
// 返回值:如果在调用 alarm() 时,之前已经为该进程设置了 alarm 闹钟还没有超时
// 则该闹钟的剩余值作为本次 alarm() 函数调用的返回值,之前设置的闹钟则被新的替代;否则返回 0
unsigned int alarm(unsigned int seconds);
示例:alarm() 函数使用
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_handler(int sig) {
puts("Alarm timeout");
exit(0);
}
int main(int argc, char *argv[]) {
struct sigaction sig = {0};
int second;
/* 检验传参个数 */
if (argc < 2) {
exit(-1);
}
/* 为 SIGALRM 信号绑定处理函数 */
sig.sa_handler = sig_handler;
sig.sa_flags = 0;
if (sigaction(SIGALRM, &sig, NULL) == -1) {
perror("sigaction error");
exit(-1);
}
/* 启动 alarm 定时器 */
second = atoi(argv[1]);
printf("定时时长: %d 秒\n", second);
alarm(second);
for (;;) {
sleep(1);
}
exit(0);
}
$ gcc alarm.c -o alarm
$ ./alarm 3
定时时长: 3 秒
Alarm timeout
pause() 系统调用可以使得进程暂停运行、进入休眠状态,直到进程捕获到一个信号为止,只有执行了信号处理函数并从其返回时,pause() 才返回,在这种情况下,pause() 返回 -1,并且将 errno 设置为 EINTR
#include <unistd.h>
int pause(void);
示例:alarm() 和 pause() 模拟 sleep
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_handler(int sig) {
puts("Alarm timeout");
}
int main(int argc, char *argv[]) {
struct sigaction sig = {0};
int second;
/* 检验传参个数 */
if (argc < 2) {
exit(-1);
}
/* 为 SIGALRM 信号绑定处理函数 */
sig.sa_handler = sig_handler;
sig.sa_flags = 0;
if (sigaction(SIGALRM, &sig, NULL) == -1) {
perror("sigaction error");
exit(-1);
}
/* 启动 alarm 定时器 */
second = atoi(argv[1]);
printf("定时时长: %d 秒\n", second);
alarm(second);
/* 进入休眠状态 */
pause();
puts("休眠结束");
exit(0);
}
sigemptyset() 和 sigfillset() 用于初始化信号集
#include <signal.h>
// set:指向需要进行初始化的信号集变量
// 返回值:成功返回 0;失败将返回 -1,并设置 errno
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
使用示例
// 初始化为空信号集
sigset_t sig_set;
sigemptyset(&sig_set);
// 初始化信号集,使其包含所有信号
sigset_t sig_set;
sigfillset(&sig_set);
分别使用 sigaddset() 和 sigdelset() 函数向信号集中添加或移除一个信号
#include <signal.h>
// set:指向信号集
// signum:需要添加/删除的信号
// 返回值:成功返回 0;失败将返回 -1,并设置 errno
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
示例
// 向信号集中添加信号
sigset_t sig_set;
sigemptyset(&sig_set);
sigaddset(&sig_set, SIGINT);
// 从信号集中移除信号
sigset_t sig_set;
sigfillset(&sig_set);
sigdelset(&sig_set, SIGINT);
使用 sigismember() 函数可以测试某一个信号是否在指定的信号集中
#include <signal.h>
// set:指定信号集
// signum:需要进行测试的信号
// 返回值:若 signum 在信号集 set 中,则返回 1;如果不在则返回 0;失败则返回 -1,并设置 errno
int sigismember(const sigset_t *set, int signum);
示例:判断 SIGINT 信号是否在 sig_set 信号集中
sigset_t sig_set;
......
if (sigismember(&sig_set, SIGINT) == 1) {
puts("信号集中包含 SIGINT 信号");
} else if (!sigismember(&sig_set, SIGINT)) {
puts("信号集中不包含 SIGINT 信号");
}
在 Linux 下,每个信号都有一串与之相对应的字符串描述信息,用于对该信号进行相应的描述
示例:从 sys_siglist 数组获取信号描述信息
#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);
}
$ gcc test.c -o test
$ ./test
SIGINT 描述信息: Interrupt
SIGQUIT 描述信息: Quit
SIGBUS 描述信息: Bus error
除了直接使用 sys_siglist 数组获取描述信息之外,还可以使用 strsignal() 库函数(推荐)
#include <string.h>
char *strsignal(int sig);
示例
...
printf("SIGINT 描述信息: %s\n", strsignal(SIGINT));
printf("SIGQUIT 描述信息: %s\n", strsignal(SIGQUIT));
printf("SIGBUS 描述信息: %s\n", strsignal(SIGBUS));
printf("编号为 1000 的描述信息: %s\n", strsignal(1000));
...
psignal() 可以在标准错误(stderr)上输出信号描述信息
#include <signal.h>
void psignal(int sig, const char *s);
示例
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
psignal(SIGINT, "SIGINT 信号描述信息");
psignal(SIGQUIT, "SIGQUIT 信号描述信息");
psignal(SIGBUS, "SIGBUS 信号描述信息");
exit(0);
}
内核为每一个进程维护了一个信号掩码(其实就是一个信号集),即一组信号
向信号掩码中添加一个信号的方式
#include <signal.h>
// how:指定了调用函数时的一些行为
// SIG_BLOCK:将参数 set 所指向的信号集内的所有信号添加到进程的信号掩码中
// SIG_UNBLOCK:将参数 set 指向的信号集内的所有信号从进程信号掩码中移除
// SIG_SETMASK:进程信号掩码直接设置为参数 set 指向的信号集
// set:将参数 set 指向的信号集内的所有信号添加到信号掩码中或者从信号掩码中移除
// 如果参数 set 为 NULL,则表示无需对当前信号掩码作出改动
// oldset:如果不为 NULL,在向信号掩码中添加新的信号之前,获取到进程当前的信号掩码,存放在 oldset 所指定的信号集中
// 如果为 NULL 则表示不获取当前的信号掩码
// 返回值:成功返回 0;失败将返回 -1,并设置 errno
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
示例 1:将信号 SIGINT 添加到进程的信号掩码中
int ret;
/* 定义信号集 */
sigset_t sig_set;
/* 将信号集初始化为空 */
sigemptyset(&sig_set);
/* 向信号集中添加 SIGINT 信号 */
sigaddset(&sig_set, SIGINT);
/* 向进程的信号掩码中添加信号 */
ret = sigprocmask(SIG_BLOCK, &sig_set, NULL);
if (-1 == ret) {
perror("sigprocmask error");
exit(-1);
}
示例 2:从信号掩码中移除 SIGINT 信号
...
/* 从信号掩码中移除信号 */
ret = sigprocmask(SIG_UNBLOCK, &sig_set, NULL);
if (-1 == ret) {
perror("sigprocmask error");
exit(-1);
}
测试信号掩码的作用
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_handler(int sig) {
printf("执行信号处理函数...\n");
}
int main(void) {
struct sigaction sig = {0};
sigset_t sig_set;
/* 注册信号处理函数 */
sig.sa_handler = sig_handler;
sig.sa_flags = 0;
if (sigaction(SIGINT, &sig, NULL) == -1) {
exit(-1);
}
/* 信号集初始化 */
sigemptyset(&sig_set);
sigaddset(&sig_set, SIGINT); // 向信号集中添加 SIGINT 信号
/* 向信号掩码中添加 SIGINT 信号 */
if (sigprocmask(SIG_BLOCK, &sig_set, NULL) == -1) {
exit(-1);
}
/* 向自己发送 SIGINT 信号 */
// 如果信号掩码没有生效,应该会立马执行 sig_handler 函数
raise(SIGINT);
/* 休眠 2 秒 */
// 2 秒后执行信号处理函数
sleep(2);
printf("休眠结束\n");
/* 从信号掩码中移除添加的信号 */
// 在移除之前接收到该信号会将其阻塞
if (sigprocmask(SIG_UNBLOCK, &sig_set, NULL) == -1) {
exit(-1);
}
exit(0);
}
$ gcc mask.c -o mask
$ ./mask
休眠结束
执行信号处理函数...
更改进程的信号掩码可以阻塞所选择的信号,或解除对它们的阻塞,可以保护不希望由信号中断的关键代码段。如果希望对一个信号解除阻塞后,然后调用 pause() 以等待之前被阻塞的信号的传递,如何实现?
#include <signal.h>
// mask:参数 mask 指向一个信号集
// 返回值:始终返回 -1,并设置 errno 来指示错误(通常为 EINTR),表示被信号所中断,如果调用失败,将 errno 设置为 EFAULT
int sigsuspend(const sigset_t *mask);
sigsuspend() 函数使用示例
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
static void sig_handler(int sig) {
printf("执行信号处理函数...\n");
}
int main(void) {
struct sigaction sig = {0};
sigset_t new_mask, old_mask, wait_mask;
/* 信号集初始化 */
sigemptyset(&new_mask);
sigaddset(&new_mask, SIGINT);
sigemptyset(&wait_mask);
/* 注册 SIGINT 信号处理函数 */
sig.sa_handler = sig_handler;
sig.sa_flags = 0;
if (sigaction(SIGINT, &sig, NULL) == -1) {
exit(-1);
}
/* 向信号掩码中添加 SIGINT 信号 */
if (sigprocmask(SIG_BLOCK, &new_mask, &old_mask) == -1) {
exit(-1);
}
/* 执行保护代码段 */
puts("执行保护代码段");
/******************/
/* 挂起、等待信号唤醒 */
if (sigsuspend(&wait_mask) != -1) {
exit(-1);
}
/* 恢复信号掩码 */
if (sigprocmask(SIG_SETMASK, &old_mask, NULL) == -1) {
exit(-1);
}
exit(0);
}
$ gcc susp.c -o susp
$ ./susp
执行保护代码段
^C执行信号处理函数...
#include <signal.h>
// set:处于等待状态的信号会存放在参数 set 所指向的信号集中
// 返回值:成功返回 0;失败将返回-1,并设置 errno
int sigpending(sigset_t *set);
/* 定义信号集 */
sigset_t sig_set;
/* 将信号集初始化为空 */
sigemptyset(&sig_set);
/* 获取当前处于等待状态的信号 */
sigpending(&sig_set);
/* 判断 SIGINT 信号是否处于等待状态 */
if (sigismember(&sig_set, SIGINT) == 1) {
puts("SIGINT 信号处于等待状态");
} else if (!sigismember(&sig_set, SIGINT)) {
puts("SIGINT 信号未处于等待状态");
}
应用程序当中使用实时信号,需要有以下的两点要求
#include <signal.h>
// pid:指定接收信号的进程对应 pid,将信号发送给该进程
// sig:指定需要发送的信号。与 kill() 函数一样,也可将参数 sig 设置为 0,用于检查参数 pid 所指定的进程是否存在
// value:指定信号的伴随数据,union sigval 数据类型
// 返回值:成功将返回 0;失败将返回-1,并设置 errno
int sigqueue(pid_t pid, int sig, const union sigval value);
// 携带的伴随数据,既可以指定一个整形的数据,也可以指定一个指针
typedef union sigval {
int sival_int;
void *sival_ptr;
} sigval_t;
示例 1:使用 sigqueue() 函数发送信号
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int main(int argc, char *argv[]) {
union sigval sig_val;
int pid;
int sig;
/* 判断传参个数 */
if (argc < 3) {
exit(-1);
}
/* 获取用户传递的参数 */
pid = atoi(argv[1]);
sig = atoi(argv[2]);
printf("pid: %d\nsignal: %d\n", pid, sig);
/* 发送信号 */
sig_val.sival_int = 10; //伴随数据
if (sigqueue(pid, sig, sig_val) == -1) {
perror("sigqueue error");
exit(-1);
}
puts("信号发送成功!");
exit(0);
}
示例 2:使用 sigaction() 函数为实时信号绑定处理函数
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
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);
}
int main(int argc, char *argv[]) {
struct sigaction sig = {0};
int num;
/* 判断传参个数 */
if (argc < 2) {
exit(-1);
}
/* 获取用户传递的参数 */
num = atoi(argv[1]);
/* 为实时信号绑定处理函数 */
sig.sa_sigaction = sig_handler;
sig.sa_flags = SA_SIGINFO;
if (sigaction(num, &sig, NULL) == -1) {
perror("sigaction error");
exit(-1);
}
/* 死循环 */
for (;;) {
sleep(1);
}
exit(0);
}
使用 exit()、_exit() 或 _Exit() 这些函数来终止进程,通常用于正常退出应用程序,而对于异常退出程序,则一般使用 abort() 库函数,使用 abort() 终止进程运行,会生成核心转储文件,用于判断程序调用 abort() 时的程序状态
#include <stdlib.h>
void abort(void);
示例:abort() 终止进程
#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(); // 调用 abort
for (;;) {
sleep(1);
}
exit(0);
}
$ gcc abort.c -o abort
$ ./abort
接收到信号: 6
Aborted (core dumped)