信号(SIG)
1. 基本概念
Linux系统中的信号是操作系统内核用来通知进程状态改变的一种机制。信号可以用来通知进程接收到了某个事件,例如:一个子进程已经终止、按下了终端的中断键(CTRL+C)等等。Linux系统中包含了多种不同类型的信号,每种信号都有它自己的作用和用途。
进程可以通过设置信号处理器(signal handler)来捕获特定的信号,并对信号进行响应。例如,当进程接收到SIGINT(中断信号)时,可以设置信号处理器来终止进程。
总的来说,Linux系统中的信号是一种非常强大且重要的机制,可以用来在进 程间传递信息,控制进程的行为,以及响应系统事件。
2. 信号详细介绍
信号 | 值 | 缺省动作 | 备注 |
---|---|---|---|
SIGHUP | 1 | 终止 | 控制终端被关闭时产生 |
SIGINT | 2 | 终止 | 从键盘按键产生的中断信号(比如Ctrl+C) |
SIGQUIT | 3 | 终止并产生转储文件 | 从键盘按键产生的退出信号(比如Ctrl+\) |
SIGILL | 4 | 终止并产生转储文件 | 执行非法指令时产生 |
SIGTRAP | 5 | 终止并产生转储文件 | 遇到进程断点时产生 |
SIGABRT | 6 | 终止并产生转储文件 | 调用系统函数 abort()是产生 |
SIGBUS | 7 | 终止并产生转储文件 | 总线错误时产生 |
SIGFPE | 8 | 终止并产生转储文件 | 处理器出现浮点运算错误时产生 |
SIGKILL | 9 | 终止 | 系统杀戮信号 |
SIGUSR1 | 10 | 终止 | 用户自定义信号 |
SIGSEGV | 11 | 终止并产生转储文件 | 访问非法内存时产生 |
SIGUSR2 | 12 | 终止 | 用户自定义信号 |
SIGPIPE | 13 | 终止 | 向无读者的管道输入数据时产生 |
SIGALRM | 14 | 终止 | 定时器到点儿时产生 |
SIGTERM | 15 | 终止 | 系统终止信号 |
SIGSTKFLT | 16 | 终止 | 已废弃 |
SIGCHLD | 17 | 忽略 | 子进程暂停或终止时产生 |
SIGCONT | 18 | 恢复运行 | 系统恢复运行信号 |
SIGSTOP | 19 | 暂停 | 系统暂停信号 |
SIGTSTP | 20 | 暂停 | 由控制终端发起的暂停信号 |
SIGTTIN | 21 | 暂停 | 后台进程发起输入请求时控制终端产生该信号 |
SIGTTOU | 22 | 暂停 | 后台进程发起输入请求时控制终端产生该信号 |
SIGURG | 23 | 忽略 | 套接字上出现紧急数据是产生 |
SIGXCPU | 24 | 终止并产生转储文件 | 处理器占用时间超出限制值时产生 |
SIGXFSZ | 25 | 终止并产生转储文件 | 文件尺寸超出限制值时产生 |
SIGVTALRM | 26 | 终止 | 由虚拟定时器产生 |
SIGPROF | 27 | 终止 | profiling 定时器到点儿时产生 |
SIGWINCH | 28 | 忽略 | 窗口大小变更时产生 |
SIGIO | 29 | 终止 | I/O变得可用时产生 |
SIGPWR | 30 | 终止 | 启动失败时产生 |
SIGUNUSED | 31 | 终止并产生转储文件 | 同SIGSYs |
对以上信号,需要着重注意的是:
- 上表中罗列出来的信号的“值”,在x86、PowerPC和 ARM平台下是有效的,但是别的平台的信号值也许跟这个表的不一致。
- “备注”中注明的事件发生时会产生相应的信号,但并不是说该信号的产生就一定发生了这个事件。事实上,任何进程都可以使用函数 kill()来产生任何信号。
- 信 号SIGKILL 和SIGSTOP是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按缺省动作来响应。换句话说,除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:
- A) 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。否则进入B。
- B) 如果该信号被捕捉,那么进一步判断捕捉的类型:
- B1)如果设置了响应函数,那么执行该响应函数。
- B2)如果设置为忽略,那么直接丢弃该信号。
- 否则进入C。
- C) 执行该信号的缺省动作
3. 相关API
3.1 发送信号
kill()
是 Linux 系统中的一个函数,用于向指定的进程或进程组发送信号。该函数的原型如下:
int kill(pid_t pid, int sig);
该函数接受两个参数:
pid
:要发送信号的进程的 ID。如果pid
为负数,则信号将发送给进程组-pid
的所有成员。sig
:要发送的信号的编号。如果函数执行成功,则返回 0。如果指定的进程不存在,则返回 -1。
该函数可用于向指定的进程发送信号,以终止该进程或发送其他信号来控制进程的行为。例如,可以使用 SIGTERM
信号来发送终止命令,或者使用 SIGSTOP
信号来暂停进程。
3.2 捕捉信号
signal()
是 Linux 系统中的一个函数,用于设置信号处理器。该函数的原型如下:
void (*signal(int sig, void (*func)(int)))(int);
该函数接受两个参数:
sig
:要设置处理器的信号的编号。func
:要设置的处理器函数。该函数返回当前设置的信号处理器。
该函数可用于设置指定信号的处理器函数。当进程收到该信号时,将调用该处理器函数。这样,就可以在进程收到信号时采取某些特定的行动,比如终止进程、暂停进程等。例如,可以使用 signal()
函数来设置 SIGINT
信号的处理器,以便在进程收到该信号时终止进程。
这个函数一般是跟 kill()
配套使用的,目标进程必须先使用 signal()
来为某个信号设置一个响应函数,或者设置忽略某个信号,才能改变信号的缺省行为,这个过程称为“信号的捕捉”。注意,对一个信号的“捕捉”可以重复进行,signal()
函数将会返回前一次设置的信号响应函数指针。对于所谓的信号响应函数的接口,规定必须是: void(*)(int)
。
3.3 给自己发送信号
raise()
是 Linux 系统中的一个函数,用于向当前进程发送信号。该函数的原型如下:
int raise(int sig);
该函数接受一个参数:
sig
:要发送的信号的编号。如果函数执行成功,则返回 0。否则,返回一个非 0 值。
该函数用于向当前进程发送指定的信号。这样,就可以通过程序代码自行发送信号,控制进程的行为。例如,可以使用 raise()
函数发送 SIGSTOP
信号,以暂停当前进程。
3.4 挂起进程
pause()
是 Linux 系统中的一个函数,用于暂停当前进程的执行。该函数的原型如下:
int pause(void);
该函数没有任何参数。
当调用 pause()
函数时,当前进程将暂停执行,直到收到一个信号为止。如果收到的信号有一个处理器被安装,则该处理器函数将被调用。否则,如果该信号有默认的处理行为,则该处理行为将被执行。例如,如果收到 SIGINT
信号,则该信号的默认处理行为是终止进程,因此当前进程将被终止。
如果在调用 pause()
函数期间没有收到信号,则该函数将永远阻塞,不会返回。如果收到了信号,则该函数将立即返回。
3.5 信号集操作函数簇
- sigemptyset():将信号集清空。
- sigfillset( ):将所有信号添加到信号集中。
- sigaddset():将指定的一个信号添加到信号集中。
- sigdelset( ):将指定的一个信号从信号集中剔除。
- sigismember():判断一个指定的信号是否被信号集包含。
函数原型为:
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);
这些函数参数为:
set
: 信号集
signum
: 要添加,或者剔除,或者判断的信号这些函数成功:
sigismember()
返回1,其余函数返回0。失败sigismember()
返回0,其余函数返回-1
3.6 阻塞或解除阻塞信号
sigprocmask()
是 Linux 系统中的一个函数,用于操作进程的信号屏蔽字。该函数的原型如下:
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
该函数接受三个参数:
how
:指定如何改变屏蔽字的值。可能的值包括:
SIG_BLOCK
:将set
中指定的信号加入到当前进程的信号屏蔽字中。SIG_UNBLOCK
:从当前进程的信号屏蔽字中删除set
中指定的信号。SIG_SETMASK
:用set
中指定的信号替换当前进程的信号屏蔽字。set
:指向信号集的指针,包含要改变屏蔽字的信号。oldset
:指向信号集的指针,用于存储原始的信号屏蔽字。如果函数执行成功,则返回 0。否则,返回一个非 0 值。
该函数用于操作进程的信号屏蔽字,以控制哪些信号会被阻塞。例如,可以使用 sigprocmask()
函数将 SIGINT
信号加入屏蔽字,以防止该信号中断当前进程的执行。
3.7 发送信号并携带一些数据
int sigqueue()
是 Linux 系统中的一个函数,用于向指定的进程发送信号,并带有一个数据值。该函数的原型如下:
int sigqueue(pid_t pid,int sig, const union sigval value);
该函数接受三个参数:
pid
:要发送信号的进程的 ID。sig
:要发送的信号的编号。value
:要发送的数据值。如果函数执行成功,则返回 0。否则,返回一个非 0 值。
该函数用于向指定的进程发送信号,并带有一个数据值。当收到信号的进程的信号处理器被调用时,还可以访问该数据值。这样,就可以通过信号传递数据值,控制进程的行为。例如,可以使用 sigqueue()
函数向某个进程发送 SIGUSR1
信号,并带有一个整型数据值,用于指示某个特定的操作。
- 信号所携带数据联合体为:
union sigval{
int sigval_int;
void *sigval_prt;
}
3.8 捕捉信号并获取数据
`sigaction() 是 Linux 系统中的一个函数,用于控制信号处理器。该函数的原型如下:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
该函数接受三个参数:
signum
:要设置处理器的信号的编号。act
:指向一个struct sigaction
结构的指针,用于指定新的信号处理器。oldact
:指向一个struct sigaction
结构的指针,用于存储原来的信号处理器。如果函数执行成功,则返回 0。否则,返回一个非 0 值。
sigaction()
函数用于控制信号处理器。它可以设置或更改信号处理器,也可以查询当前的信号处理器。与 signal()
函数不同,sigaction()
函数提供了更多的选项,可以更精细地控制信号处理器的行为。例如,可以使用 sigaction()
函数设置 SIGINT
信号的处理器,以便在进程收到该信号时执行相应的操作。
4. 信号使用的示例代码
4.1 发送信号
//给指定进程发信号
// ./p1 xxxpid 1
int main(int argc, char *argv[])
{
kill(atoi(argv[1]), atoi(argv[2]));
return 0;
}
4.2 信号集使用示例
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
//标准信号响应函数
void f(int sig)
{
printf("sig = %d\n", sig);
}
int main(int argc, char *argv[])
{
printf("我的进程号是: %d\n", getpid());
//1. 缺省
//什么都不做
// signal(SIGINT, SIG_DFL);
// 2. 忽略
// signal(SIGINT, SIG_IGN);
// signal(SIGQUIT, SIG_IGN);
// signal(SIGKILL, SIG_IGN); //9号不可以被忽略
// signal(SIGSTOP, SIG_IGN); //19号不可以被忽略
// 3. 响应
// signal(SIGINT, f);
// signal(SIGQUIT, f);
// 4. 阻塞
sigset_t set; //定义一个信号集
sigemptyset(&set); //将信号集清空
sigaddset(&set, 2); //将2号信号添加到信号集
sigaddset(&set, 9); //将2号信号添加到信号集
//阻塞信号集中的信号,阻塞只是暂时不响应,当解除阻塞之后就可以响应了
sigprocmask(SIG_BLOCK, &set, NULL);
// sigemptyset(&set); //将信号集清空
// sigaddset(&set, 3); //将2号信号添加到信号集
// SIG_BLOCK:在原有信号阻塞的基础上,再把集合中的信号添加阻塞
//阻塞信号集中的信号,阻塞只是暂时不响应,当解除阻塞之后就可以响应了
// sigprocmask(SIG_BLOCK, &set, NULL);
// SIG_SETMASK:用set集中的信号替换原来被阻塞的信号
// sigprocmask(SIG_SETMASK, &set, NULL);
// int t = 5;
// while(t)
// {
// printf("%d秒\n", t--);
// sleep(1);
// }
//把信号集中所有信号解除阻塞,当阻塞接触之后信号可以被响应
// sigprocmask(SIG_UNBLOCK, &set, NULL);
// 自己给 自己发送一个信号
// raise(3);
//等待任意一个信号
pause();
// while(1)
// {}
return 0;
}
4.3 信号响应示例
//标准的信号响应函数
void f(int sig)
{
printf("信号[%d]已响应\n", sig);
}
//拓展的信号响应函数
void act_f(int sig, siginfo_t *info, void *ptr)
{
// printf("信号[%d]已响应, 所携带的数据是: %d\n", sig, info->si_int);
printf("信号[%d]已响应, 所携带的数据是: %s\n", sig, (char *)info->si_ptr);
}
int main(int argc, char *argv[])
{
printf("我的进程号是:[%d]\n", getpid());
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
struct sigaction act;
act.sa_handler = f; //标准响应函数
act.sa_sigaction = act_f; //拓展响应函数
act.sa_mask = set; //临时阻塞的信号
act.sa_flags |= SA_SIGINFO; //设置使用信号拓展响应函数
// signal(SIGINT, f);
sigaction(SIGINT, &act, NULL);
while(1);
return 0;
}
4.4 发送信号并携带数据示例
//argv[1]: 表示要接收信号的进程号
//argv[2]: 表示要发送的信号
int main(int argc, char *argv[])
{
// union sigval value;
// value.sival_int = 100;
value.sival_ptr = calloc(1, 1024);
strcpy(value.sival_ptr, "hello");
sigqueue(atoi(argv[1]), atoi(argv[2]), value);
printf("信号已发送\n");
return 0;
}