2. 信号
Unix/Linux 各种不同信号及其用途
基本概念
信号是事件发生时对进程的通知机制,有时也称为软件中断。信号更多用于进程间的一种同步技术。信号定义在头文件 <signal.h>
中,不同的信号以 SIGXXXX
的形式命名。 信号一般在进程正在执行,且内核态到用户态的下一次切换时发送,如果该信号由进程自己产生则会马上传递信号。当信号到达后,进程可采取如下措施:
忽略信号
终止进程
产生核心转储文件,该文件可用于后续的调试
停止进程
恢复运行
信号类型与默认行为
常用信号如下(默认行为,term 信号终止,core 产生核心转储文件并退出,ignore 忽略信号,stop 停止进程,cont 恢复运行):
SIGABRT
终止进程
core
SIGALRM
实时定时器过期
term
SIGBUS
内存访问错误
core
SIGCHLD
子进程终止或停止
ignore
SIGCONT
继续运行进程
cont
SIGEMT
硬件错误
term
SIGHUP
挂起
term
SIGINT
终端中断 Ctrl-C
term
SIGILL
非法指令
core
SIGKILL
杀死进程(必杀)
term,无法阻塞、忽略、捕获
SIGPIPE
管道断开
term
SIGQUIT
终端退出 Ctrl-\
core
SIGSTOP
确保停止
stop,无法阻塞、忽略、捕获
SIGTERM
进程终止
term
发送信号
kill
可以使用 kill(pid, 0)
检查目标进程是否存在,sig 为 0 表示这是一个空信号。
raise
在单线程程序中,相当于调用 kill(getpid(), sig)
,多线程环境下为 pthread_kill(pthread_self(), sig)
。调用此函数向进程自身发送信号时,信号立即传递,且在 raise 函数返回之前。此函数唯一可能发生的错误为 EINVAL,即 sig 无效。
killpg
打印信号
信号集
许多信号相关的系统调用需要表示一组信号,比如阻塞一组信号,返回一组目前在等待的信号。一组信号可以通过信号集这个数据结构表示,即 sigset_t
。
信号掩码与信号等待
内核为每个进程维护一个信号掩码,用于阻塞信号对该进程的传递。在掩码内的信号将会延后传递给进程,直到该信号解除阻塞,解除阻塞后会立即传递给进程,如果该信号在阻塞期间内到达多次,在解除阻塞后也只传递一次。
如果进程接受一个被阻塞的信号,它会被加入到等待信号集中:
等待信号集只能说明信号是否发生,不能表面其发生次数,这些论述只针对标准信号,对于实时信号则会进行排队处理。
改变信号处置
signal
signal 系统调用是设置信号处置的原始 API,它的行为在不同 Unix 上存在差异,所以一般使用 sigaction 函数建立信号处理器函数。
它无法在不改变信号处置的同时获取当前的信号处置。handler 可以采用以下定义值:
SIG_DFL:将信号处置重置为默认值
SIG_IGN:忽略该信号(不处置)
sigaction
sigaction 对信号的处置更具灵活性,也可以实现更精准的控制。
等待信号
信号处理器函数
信号处理器函数一般设计的越简单越好,以避免引发竞争条件的风险。
可重入函数和异步信号安全函数
如果一个进程的多条线程可以同时安全地调用此函数,则该函数为可重入的。更新全局变量或静态数据结构的函数可能是不可重入的(只用到本地变量的函数肯定是可重入的)。 如果某一函数是可重入的,又或者信号处理器函数无法将其中断时,就称该函数是异步信号安全的。 在编写信号处理器时:
确保信号处理器函数代码本身是可重入的,且只调用异步信号安全函数
当主程序执行不安全的函数或是去操作信号处理函数也会更新的全局数据结构时,阻塞该信号的传递
信号处理器可能会更新 errno
,所以需要在函数入口保存一份拷贝,并在函数返回时重新赋值给 errno
全局变量与 sig_atomic_t 数据类型
可以使用 sig_atomic_t
数据类型以保证程序读写操作的原子性,以确保全局变量的共享是安全的。
终止信号处理器函数的其他方法
除了直接返回外,还有以下终止方法:
_exit()
函数终止进程,不能调用exit()
因为其会对缓冲区做额外处理kill
发送信号来杀掉进程从信号处理器函数执行非本地跳转,通过
sigsetjmp``siglongimp
使用
abort
函数终止进程,并产生核心存储
SA_SIGINFO 标志
此标志会给信号处理器函数传递一些附加信息。sigaction
中信号处理地址的完整申明如下:
系统调用的中断和重启
可以利用下面的代码手动重启系统调用(也可以使用 SA_RESTART 标志,但并不是对所有函数都有效):
高级特性
核心转储文件
某些信号会创建核心转储文件以方便调试。但有时候并不会产生,原因如下:
进程对核心转储文件没有写权限。
存在同名、可写的普通文件
创建核心转储文件的路径不对
文件大小超过了系统对核心转储文件的限制,可通过 ulimit 命令设置
进程可创建文件大小的限制
文件系统以满,工作目录文件系统仅可读
Set-user-ID 程序由非其属主(属组)执行时,不创建以避免用户恶意窥探程序数据
一些特殊情况
SIGKILL,SIGSTOP:其默认行为无法改变,除非进程处于 TASK_UNINTERRUPTIBLE 睡眠状态,此状态的进程,系统不会把信号传递给它。相反,TASK_INTERRUPTIBLE 睡眠状态则会被信号唤醒。
SIGCONT:若进程在停止状态,此信号总能唤醒进程,即使被阻塞或忽略。因为只能通过此信号恢复程序运行。若重新定义此信号的处理函数,当程序恢复运行后并取消阻塞后才会去调用。SIGSTOP 和 SIGCONT 分别会将其到来前的 SIGCONT 和 SIGSTOP 丢弃。
由终端产生的信号若被忽略,则不应该改变其处置。
信号一般是异步的,除非此信号为进程自己产生并发送给自己。此时会立即传递信号,除非其被阻塞。
实时信号
实时信号相比标准信号有以下特点:
信号范围更大
采取队列化管理,实时信号发送多次给进程将会多次传递,标准信号只会传送一次
可指定伴随数据
传递顺序有保障,会优先传递最小编号的信号
实时信号范围由 SIGRTMIN
和 SIGRTMAX
定义。
在使用 sigaction 处理信号时,siginfo_t 会设置以下字段:
si_signo: 信号编号
si_code: 信号来源,实时信号为 SIG_QUEUE
si_value: 即 sigval
si_pid,si_uid: 发送进程的进程 ID 和实际用户 ID
用掩码等待信号
用同步方式等待信号
最后更新于
这有帮助吗?