2.3.4 信号
文章目录
信号概述
信号
是Linux
进程间通信最古老的方式之一,是事件发生时的通知机制,有时也称为软件中断
,一种异步通信模式。相比于管道
的主动获取,信号
由接收进程被动获取,可以中断进程用于完成一个突发事件。信号
具有简单
、高优先级
、无法携带大量信息
的特点。信号
具有三种状态:产生
、未决
、递达
。信号未决
时被阻塞在内核区,等待信号阻塞解除;信号递达
,被进程处理或忽略。信号
分为主要两种:实时信号
、系统信号
。实时信号
不具有特定意义,可以由用户指定;系统信号
通常具有特定含义。- 信号
递达
时,进程会中断当前的程序,转而处理与信号
相关联的程序,当信号处理完后继续执行当前进程。 - 信号阻塞后释放,多个信号将同时从
未决
状态进入递达
状态。该过程进程处理不同信号拥有不同优先级。默认动作高于自定义动作是首要优先级;在实时信号
中,高值信号优先级高于低值信号;在系统信号
中,以及实时信号
与系统信号
间,则具有复杂的优先级关系。 - 多个相同信号同时
递达
,实时信号
会被多次处理,系统信号
只处理一次。
(注:接下来在常用信号
中将介绍实时信号
、系统信号
以及默认动作。在信号集
章节中,将涉及信号阻塞;在捕捉信号
章节中,将涉及自定义动作。)
常用信号
- 查看信号列表
$ kill -l
,1~31
为常规信号,其余为实时信号。 - 查看信号说明
$ man 7 signal
。
信号宏 | 信号值 | 事件说明 | 默认动作 |
---|---|---|---|
S I G I N T SIGINT SIGINT | 2 2 2 | <
C
t
r
l
Ctrl
Ctrl+
C
C
C> 终端向运行中子进程 发出此信号 | T e r m Term Term |
S I G Q U I T SIGQUIT SIGQUIT | 3 3 3 | <
C
t
r
l
Ctrl
Ctrl+
\
\backslash
\> 终端向运行中子进程 发出此信号 | T e r m Term Term |
S I G K I L L SIGKILL SIGKILL | 9 9 9 | 强制终止进程 | T e r m Term Term |
S I G S E G V SIGSEGV SIGSEGV | 11 11 11 | 访问无效内存(Segment Fault ,段错误 ) | C o r e Core Core |
S I G P I P E SIGPIPE SIGPIPE | 13 13 13 | 向无读端管道写数据(Broken Pipe ,管道破裂 ) | T e r m Term Term |
S I G A L R M SIGALRM SIGALRM | 14 14 14 | 计时器结束向自己发出此信号 | T e r m Term Term |
S I G C H L D SIGCHLD SIGCHLD | 17 17 17 | 子进程 结束,父进程 收到此信号 | I g n Ign Ign |
S I G C O N T SIGCONT SIGCONT | 18 18 18 | 如果进程暂停,继续进程 | C o n t Cont Cont |
S I G S T O P SIGSTOP SIGSTOP | 19 19 19 | 暂停进程 | S t o p Stop Stop |
→ S I G M A X S I G M I N ^{~~~~SIGMIN}_{\to SIGMAX} →SIGMAX SIGMIN | 34 → 64 34 \to 64 34→64 | Linux 实时信号,用户自定义意义 | T e r m Term Term |
-
默认动作:
Term
:终止进程。Ign
:忽略。Core
:终止进程并记录。Stop
:中断进程。Cont
:继续进程。
-
SIGKILL
与SIGSTOP
信号不能被产生、阻塞与忽略。
信号集
信号集概述
信号
具有三种状态产生
、未决
、递达
。信号是否未决
并阻塞在进程内核区是由阻塞信号集
决定的。信号集
是一个64bit
整数,标志1~64
信号。信号集
分为未决信号集
、阻塞信号集
、自定义信号集
。阻塞信号集
:位于内核区,其中为1
的标志位表示阻塞该信号。用户可以通过Linux
函数操作阻塞信号集
。未决信号集
:位于内核区,其中为1
的标志位表示该信号未决
。在阻塞后试图递达
的信号将未决
,并在未决信号集
标记;在阻塞解除后该标记位立即置0
,信号递达
。自定义信号集
:位于用户区。事实上任何64bit
整数都可以是自定义信号集
,自定义信号集
仅是拥有信号集
意义的整数,在操作阻塞信号集
时作为参数传递。
信号集编码
信号集
操作通过位运算进行,对于x
号信号,其对应的标志位为x-1
。例如:一个包含2
、7
、9
信号的信号集的值为0b 0001 0100 0010
。标准C库
提供了一系列信号集
运算函数。但事实上我们不必记忆这些函数,理解原理后使用位运算即可解决。
#include <signal.h>
// remove all the signums from the set
int sigemptyset(sigset_t *set); // set = 0; return 0;
// add all the signums into the set
int sigfillset(sigset_t *set); // set = -1ull; return 0;
// add the signum into set
int sigaddset(sigset_t *set, int signum); // set = set | 1 << signum - 1; return 0;
// remove the signum from the set
int sigdelset(sigset_t *set, int signum); // set = set & ~(1 << signum - 1); return 0;
// examine if the signum is in the set
int sigismember(const sigset_t *set, int signum); // return set >> signum - 1 & 1;
操作阻塞信号集
sigprocmask
,用户无法直接操作阻塞信号集
,必须使用内核提供的函数完成操作。- 注意:
SIGKILL
与SIGSTOP
信号在操作中会被过滤。
#include <signal.h>
// examine and change the block signal set
// how:
// SIG_BLOCK:
// add the signals with 1 flag in the given set into the block signal set
// SIG_UNBLOCK:
// remove the signals with 1 flag in the given set from the block signal set
// SIG_SETMASK:
// set the block sognal set to the given set
// set:
// Operating signal set whose actual meaning is depending on "how".
// oldset:
// to receive the former block signal set, and can be NULL
// return value:
// return 0 for success, -1 for error
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
// About more:
// $ man 2 sigprocmask
- 只获取
oldset
如下:
const unsigned long long zero = 0;
unsigned long long oldset;
sigprocmask(SIG_BLOCK, &zero, &oldset);
发送信号
-
kill
用于向任意进程或任意进程组发射信号。 -
pid
:分为下述情况:< -1
:将信号发送到所有组ID
为-pid
(pid
的绝对值)的进程。-1
:将信号发送到所有允许发送到的进程。0
:将信号发送到所有组ID
与调用进程组ID
相同的进程。> 0
:将信号发送到PID
为pid
的进程。
-
返回值
:成功返回0
,失败返回-1
。
#include <sys/types.h>
#include <signal.h>
// send a signal to a process
int kill(pid_t pid, int sig);
// $ man 2 kill
raise
向当前进程发送信号,以下是等价的。(但是kill
的返回值是0/1
,raise
的返回值是0/nonzero
)
#include <signal.h>
// send a signal to the caller
int raise(int sig);
// $ man 3 raise
kill(getpid(), sig);
abort
向当前进程发送SIGABRT
信号,以下是等价的。(但是kill
的返回值是0/1
,abort
无返回值)
#include <stdlib.h>
// send SIGABRT to the caller
void abort(void);
// $ man 3 abort
kill(getpid(), SIGABRT);
计时器信号
alarm 计时器
alarm
计时器,执行一次,非阻塞,执行结束触发SIGABRT
信号。每执行alarm
函数将刷新计时器,计时器时间设置为seconds
。seconds
:计时器时间。需要注意的是,此操作会直接重置计时器,而之前的未触发的信号也不会触发;如果设置为0
,则计时器置为不可用,不会触发信号。返回值
:返回当前计时器时间(seconds
设置前),初始为0
。
#include <unistd.h>
// set an alarm clock for delivery of a signal
unsigned int alarm(unsigned int seconds);
// $ man 2 alarm
itimer 计时器
itimer
计时器,循环执行,非阻塞,每轮结束触发响应信号(由which
指定)。每执行setitimer
函数将刷新计时器,计时器实时参数重置为new_value
,并获取当前计时器实时参数(new_value
设置前),初始为{{0, 0}, {0, 0}}
。执行getitimer
函数可获取当前计时器实时参数到curr_value
而不重置计时器。which
:(下列选择一个)ITIMER_REAL
:真实时间,触发SIGALRM
信号。ITIMER_VIRTUAL
:用户时间,触发SIGVTALRM
信号。ITIMER_PROF
:用户与内核时间,触发SIGPROF
信号。- 真实事件 = 用户时间 + 切换时间 + 内核时间 真实事件 = 用户时间 + 切换时间 + 内核时间 真实事件=用户时间+切换时间+内核时间
- 无特殊需求通常选用
ITIMER_REAL
。 - 在
getitimer
中需与setitimer
使用相同的which
参数。
new_value
、old_value
、curr_value
原型如下:
// struct itimerval, in sys/time.h
struct itimerval {
struct timeval it_interval; // 时间间隔, 如为 {0, 0} 计时器不循环
struct timeval it_value; // 距离下次触发时间, 初始状态可以大于 it_interval; 如设置为 {0, 0} 计时器不可用
};
struct timeval {
time_t tv_sec; // 毫秒
suseconds_t tv_usec; // 微秒
};
new_value
:设置新参数。old_value
:接收旧参数,可为NULL
。curr_value
:接收当前参数。返回值
:成功返回0
,失败返回-1
。
#include <sys/time.h>
// get value of an interval timer
int getitimer(int which, struct itimerval *curr_value);
// $ man 2 getitimer
// set value of an interval timer
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
// $ man 2 setitimer
- 以下两者是等价的:
setitimer(ITIMER_REAL, new_value, old_value);
getitimer(ITIMER_REAL, old_value);
setitimer(ITIMER_REAL, NULL);
捕捉信号
signal 捕捉信号
signal
函数系统兼容性较差,不推荐使用。但相较于sigaction
函数,signal
函数更为简单。
#include <signal.h>
typedef void (*sighandler_t)(int);
// ANSI C signal handling
// signum:
// signal number
// handler:
// one of the following:
// SIG_DFL for using the default action
// SIG_IGN for ignore the signal
// callback function pointer
// return value:
// Return the last handler, or SIG_ERR for error. The inital handler is SIG_DFL
sighandler_t signal(int signum, sighandler_t handler);
// About more
// $ man 2 signal
示例:
#include <signal.h>
#include <stdio.h>
void func(int sig) {
printf("Get a signal %d\n", sig);
}
int main() {
signal(SIGINT, func);
raise(SIGINT); // 向自己发射 SIGINT 信号
while (1); // 发现尝试使用 Ctrl+C 退出进程将失败
return 0;
}
sigaction 捕捉信号
sigaction
函数具有更高的系统兼容性,并且支持更丰富的功能,尽管它们很少被用到。
#include <signal.h>
struct sigaction {
void (*sa_handler)(int); // handler 1, same to "signal"
void (*sa_sigaction)(int, siginfo_t *, void *); // handler 2, more options, see "About more"
sigset_t sa_mask; // temporary block signal mask which will rollback after calling
int sa_flags;
// usual 0 (handler 1), SA_SIGINFO for choosing handler 2, more flags see "About more"
void (*sa_restorer)(void); // deprecated
};
// change signal action
// signum:
// signal number
// act:
// the structure describe callback function, see "struct sigaction"
// oldact:
// to receive the last action
// return value:
// return 0 for success, -1 for error
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
// About more
// $ man 2 sigaction
示例:(与上个示例等价)
#include <signal.h>
#include <stdio.h>
void func(int sig) {
printf("Get a signal %d\n", sig);
}
int main() {
struct sigaction action = {func};
sigaction(SIGINT, &action, NULL);
raise(SIGINT); // 向自己发射 SIGINT 信号
while (1); // 发现尝试使用 Ctrl+C 退出进程将失败
return 0;
}