Linux信号
信号
- 信号的基本概念
- 信号的分类、Linux 提供的各种不同的信号及其作用
- 进程对信号的处理
- 向进程发送信号
- 信号集
- 获取信号的描述信息
- 信号掩码(阻塞信号传递)
- 阻塞等待信号 sigsuspend()
- 确定进程中处于等待状态的是哪些信号
- 实时信号
信号的基本概念
信号可以由“谁”发出
- 硬件发生异常,即硬件检测到错误条件并通知内核,随即再由内核发送相应的信号给相关进程。硬件检测到异常的例子包括执行一条异常的机器语言指令,诸如,除数为 0、数组访问越界导致引用了无法访问的内存区域等,这些异常情况都会被硬件检测到,并通知内核、然后内核为该异常情况
发生时正在运行的进程发送适当的信号以通知进程。 - 用于在终端下输入了能够产生信号的特殊字符。譬如在终端上按下 CTRL + C 组合按键可以产生中断信号(SIGINT),通过这个方法可以终止在前台运行的进程;按下 CTRL + Z 组合按键可以产生暂停信号(SIGCONT),通过这个方法可以暂停当前前台运行的进程。
- 。。。
信号由谁处理、怎么处理
- 忽略信号。也就是说,当信号到达进程后,该进程并不会去理会它、直接忽略,就好像是没有出该信号,信号对该进程不会产生任何影响。
- 捕获信号。当信号到达进程后,执行预先绑定好的信号处理函数。为了做到这一点,要通知内核在某种信号发生时,执行用户自定义的处理函数,该处理函数中将会对该信号事件作出相应的处理,Linux 系统提供了 signal()系统调用可用于注册信号的处理函数,将会在后面向大家介绍。
- 执行系统默认操作。进程不对该信号事件作出处理,而是交由系统进行处理,每一种信号都会有其对应的系统默认的处理方式。
信号本质上是 int 类型数字编号
/* 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). */
信号的分类、Linux 提供的各种不同的信号及其作用
可靠信号与不可靠信号
不可靠信号
Linux 下的不可靠信号问题主要指的是信号可能丢失。在 Linux 系统下,信号值小于 SIGRTMIN(34)的信号都是不可靠信号.如果在短时间内多次发送同一信号,某些实现可能会丢失部分信号,尤其是对 SIGKILL 和 SIGSTOP 等特殊信号,这些信号无法被阻塞、捕捉或忽略。
可靠信号
支持排队,不会丢失。
实时信号与非实时信号
实时信号与非实时信号其实是从时间关系上进行的分类,与可靠信号与不可靠信号是相互对应的,非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。实时信号保证了发送的多个信号都能被接收,实时信号是 POSIX 标准的一部分,可用于应用进程。
进程对信号的处理
signal()和 sigaction()两个函数
向进程发送信号
kill()系统调用之外,Linux 系统还提供了系统调用 killpg()以及库函数 raise(),也可用于实现发送信号的功能
alarm()和 pause()
信号集
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} sigset_t;
- 初始化信号集
sigemptyset()和 sigfillset()用于初始化信号集。sigemptyset()初始化信号集,使其不包含任何信号;而sigfillset()函数初始化信号集,使其包含所有信号(包括所有实时信号) - 向信号集中添加/删除信号
分别使用 sigaddset()和 sigdelset()函数向信号集中添加或移除一个信号 - 测试信号是否在信号集中
获取信号的描述信息
可以使用 sys_siglist[SIGINT]来获取对 SIGINT 信号的描述。
strsignal()函数 psignal()函数
信号掩码(阻塞信号传递)
内核为每一个进程维护了一个信号掩码(其实就是一个信号集),即一组信号。当进程接收到一个信号掩码中定义的信号时,该信号将会被阻塞、无法传递给进程进行处理,那么内核会将其阻塞,直到该信号从信号掩码中移除,内核才会把该信号传递给进程从而得到处理。
向信号掩码中添加一个信号,通常有如下几种方式:
⚫ 当应用程序调用 signal()或 sigaction()函数为某一个信号设置处理方式时,进程会自动将该信号添加到信号掩码中,这样保证了在处理一个给定的信号时,如果此信号再次发生,那么它将会被阻塞;当然对于 sigaction()而言,是否会如此,需要根据 sigaction()函数是否设置了 SA_NODEFER 标志而定;当信号处理函数结束返回后,会自动将该信号从信号掩码中移除。
⚫ 使用 sigaction()函数为信号设置处理方式时,可以额外指定一组信号,当调用信号处理函数时将该组信号自动添加到信号掩码中,当信号处理函数结束返回后,再将这组信号从信号掩码中移除;通过 sa_mask 参数进行设置,参考 8.4.2 小节内容。
⚫ 除了以上两种方式之外,还可以使用 sigprocmask()系统调用,随时可以显式地向信号掩码中添加/移除信号。
阻塞等待信号 sigsuspend()
上一小节已经说明,更改进程的信号掩码可以阻塞所选择的信号,或解除对它们的阻塞。使用这种技术可以保护不希望由信号中断的关键代码段。如果希望对一个信号解除阻塞后,然后调用 pause()以等待之前被阻塞的信号的传递。
sigset_t new_set, old_set;/* 信号集初始化 */sigemptyset(&new_set);sigaddset(&new_set, SIGINT);/* 向信号掩码中添加信号 */if (-1 == sigprocmask(SIG_BLOCK, &new_set, &old_set))exit(-1);/* 受保护的关键代码段 */....../**********************//* 恢复信号掩码 */if (-1 == sigprocmask(SIG_SETMASK, &old_set, NULL))exit(-1);pause();/* 等待信号唤醒 */
执行受保护的关键代码时不希望被 SIGINT 信号打断,所以在执行关键代码之前将 SIGINT 信号添加到进程的信号掩码中,执行完毕之后再恢复之前的信号掩码。最后调用了 pause()阻塞等待被信号唤醒,如果此时发生了信号则会被唤醒、从 pause 返回继续执行;考虑到这样一种情况,如果信号的传递恰好发生在第二次调用 sigprocmask()之后、pause()之前,如果确实发生了这种情况,就会产生一个问题,信号传递过来就会导致执行信号的处理函数,而从处理函数返回后又回到主程序继续执行,从而进入到 pause()被阻塞,知道下一次信号发生时才会被唤醒,这有违代码的本意。
虽然信号传递发生在这个时间段的可能性并不大,但并不是完全没有可能,这必然是一个缺陷,要避免这个问题,需要将恢复信号掩码和 pause()挂起进程这两个动作封装成一个原子操作,这正是 sigsuspend()系统调用的目的所在
确定进程中处于等待状态的是哪些信号
确定进程中处于等待状态的是哪些信号,可以使用 sigpending()函数获取
实时信号
等待信号集只是一个掩码,仅表明一个信号是否发生,而不能表示其发生的次数。换言之,如果一个同一个信号在阻塞状态下产生了多次,那么会将该信号记录在等待信号集中,并在之后仅传递一次(仅当做发生了一次),这是标准信号的缺点之一。
实时信号较之于标准信号,其优势如下:
⚫ 实时信号的信号范围有所扩大,可应用于应用程序自定义的目的,而标准信号仅提供了两个信号可用于应用程序自定义使用:SIGUSR1 和 SIGUSR2。
⚫ 内核对于实时信号所采取的是队列化管理。如果将某一实时信号多次发送给另一个进程,那么将会多次传递此信号。相反,对于某一标准信号正在等待某一进程,而此时即使再次向该进程发送此信号,信号也只会传递一次。
⚫ 当发送一个实时信号时,可为信号指定伴随数据(一整形数据或者指针值),供接收信号的进程在它的信号处理函数中获取。
⚫ 不同实时信号的传递顺序得到保障。如果有多个不同的实时信号处于等待状态,那么将率先传递具有最小编号的信号。换言之,信号的编号越小,其优先级越高,如果是同一类型的多个信号在排队,那么信号(以及伴随数据)的传递顺序与信号发送来时的顺序保持一致。