Skip to main content

信号(SIG)

1. 基本概念

Linux系统中的信号是操作系统内核用来通知进程状态改变的一种机制。信号可以用来通知进程接收到了某个事件,例如:一个子进程已经终止、按下了终端的中断键(CTRL+C)等等。Linux系统中包含了多种不同类型的信号,每种信号都有它自己的作用和用途。

进程可以通过设置信号处理器(signal handler)来捕获特定的信号,并对信号进行响应。例如,当进程接收到SIGINT(中断信号)时,可以设置信号处理器来终止进程。

总的来说,Linux系统中的信号是一种非常强大且重要的机制,可以用来在进程间传递信息,控制进程的行为,以及响应系统事件。

2. 信号详细介绍

信号缺省动作备注
SIGHUP1终止控制终端被关闭时产生
SIGINT2终止从键盘按键产生的中断信号(比如Ctrl+C)
SIGQUIT3终止并产生转储文件从键盘按键产生的退出信号(比如Ctrl+\)
SIGILL4终止并产生转储文件执行非法指令时产生
SIGTRAP5终止并产生转储文件遇到进程断点时产生
SIGABRT6终止并产生转储文件调用系统函数 abort()是产生
SIGBUS7终止并产生转储文件总线错误时产生
SIGFPE8终止并产生转储文件处理器出现浮点运算错误时产生
SIGKILL9终止系统杀戮信号
SIGUSR110终止用户自定义信号
SIGSEGV11终止并产生转储文件访问非法内存时产生
SIGUSR212终止用户自定义信号
SIGPIPE13终止向无读者的管道输入数据时产生
SIGALRM14终止定时器到点儿时产生
SIGTERM15终止系统终止信号
SIGSTKFLT16终止已废弃
SIGCHLD17忽略子进程暂停或终止时产生
SIGCONT18恢复运行系统恢复运行信号
SIGSTOP19暂停系统暂停信号
SIGTSTP20暂停由控制终端发起的暂停信号
SIGTTIN21暂停后台进程发起输入请求时控制终端产生该信号
SIGTTOU22暂停后台进程发起输入请求时控制终端产生该信号
SIGURG23忽略套接字上出现紧急数据是产生
SIGXCPU24终止并产生转储文件处理器占用时间超出限制值时产生
SIGXFSZ25终止并产生转储文件文件尺寸超出限制值时产生
SIGVTALRM26终止由虚拟定时器产生
SIGPROF27终止profiling 定时器到点儿时产生
SIGWINCH28忽略窗口大小变更时产生
SIGIO29终止I/O变得可用时产生
SIGPWR30终止启动失败时产生
SIGUNUSED31终止并产生转储文件同SIGSYs

对以上信号,需要着重注意的是:

  1. 上表中罗列出来的信号的“值”,在x86、PowerPC和 ARM平台下是有效的,但是别的平台的信号值也许跟这个表的不一致。
  2. “备注”中注明的事件发生时会产生相应的信号,但并不是说该信号的产生就一定发生了这个事件。事实上,任何进程都可以使用函数 kill()来产生任何信号。
  3. 信号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;
}