sigaltstack - set or/and get 信号的堆栈上下文
#include <signal.h>
int sigaltstack(const stack_t *restrict ss, stack_t *restrict old_ss);
Feature Test Macro Requirements for glibc (see
feature_test_macros(7)):
sigaltstack():
_XOPEN_SOURCE >= 500
|| /* Since glibc 2.12: */ _POSIX_C_SOURCE >= 200809L
|| /* Glibc <= 2.19: */ _BSD_SOURCE
sigaltstack() 允许线程定义新的备用信号堆栈和/或获取现有备用信号堆栈的状态。如果信号处理程序的建立(请参阅 sigaction(2))指定使用备用堆栈,即设置了SA_ONSTACK参数,则在信号处理程序的执行期间使用备用信号堆栈。
使用备用信号堆栈的一般步骤如下:
- 分配一块内存区域用于备用信号堆栈。
- 使用 sigaltstack() 通知系统备用信号堆栈的存在和位置。
- 使用 sigaction(2) 建立信号处理程序时,通过指定 SA_ONSTACK 标志通知系统应在备用信号堆栈上执行信号处理程序。
ss 参数用于指定新的备用信号堆栈,而 old_ss 参数用于获取有关当前建立的信号堆栈的信息。如果我们只对执行其中一项任务感兴趣,则可以将另一个参数指定为 NULL。
stack_t 类型定义如下:
typedef struct {
void *ss_sp; /* Base address of stack */
int ss_flags; /* Flags */
size_t ss_size; /* Number of bytes in stack */
} stack_t;
ss.ss_flags
此字段包含 0 或以下标志,此字段也是必须要配置的,起码要是0,否则将会备用栈配置会无效:
-
SS_AUTODISARM (since Linux 4.7)
在进入信号处理程序之前,清楚对备用信号栈的设置,并在信号处理程序返回时恢复。
添加此标志是为了使使用 swapcontext(3) 从信号处理程序中切换出来是安全的。 如果没有这个标志,随后处理的信号将破坏切换离开的信号处理程序的状态。 在不支持此标志的内核上,提供此标志时 sigaltstack() 会失败并显示错误 EINVAL。
根据我的理解是,如果没有这个标志,那么每一个信号都将使用备用栈,如果当前正在处理信号处理程序,那么当前的rsp指向的是备用信号堆栈的栈顶,如果此时新的信号到达,那么将继续使用备用栈,没有问题,但是如果在执行信号处理程序时用swapcontext切换了出去,那么rsp将不再指向备用堆栈,此时若新的信号到达,则将从信号备用栈的顶端开始使用,这样就会出现后来的信号处理程序覆盖前面处理程序栈的情况。
ss.ss_sp
该字段指定堆栈的起始地址。 当在备用堆栈上调用信号处理程序时,内核会自动将 ss.ss_sp 中给出的地址与底层硬件架构的合适地址边界对齐。
ss.ss_size
此字段指定堆栈的大小。 常量 SIGSTKSZ(8KB)定义了足够满足信号堆栈的通常大小要求的栈空间大小,常量 MINSIGSTKSZ(2KB)定义执行信号处理程序所需的最小大小。
要禁用现有备用信号堆栈,请将 ss.ss_flags 指定为 SS_DISABLE。 在这种情况下,内核会忽略 ss.ss_flags 中的任何其他标志以及 ss 中的其余字段。即若ss.ss_flags指定了SS_DISABLE,那么就停用备用信号堆栈
如果 old_ss 不为 NULL,则它用于返回有关在调用 sigaltstack() 之前生效的备用信号堆栈的信息。 old_ss.ss_sp 和 old_ss.ss_size 字段返回该堆栈的起始地址和大小。 old_ss.ss_flags 可能返回以下任一值:
-
SS_ONSTACK
该线程当前正在备用信号堆栈上执行,即正在备用栈上执行信号处理程序。 (请注意,如果线程当前正在其上执行,则无法更改备用信号堆栈。) -
SS_DISABLE
备用信号堆栈当前已禁用,线程当前正在使用 SS_AUTODISARM 标志建立的备用信号堆栈上执行。 在这种情况下,使用 swapcontext(3) 切换信号处理程序是安全的。 还可以使用对 sigaltstack() 的进一步调用来设置不同的替代信号堆栈。 -
SS_AUTODISARM
如上所述,备用信号堆栈已被标记为AUTODISARM。
通过将 ss 指定为 NULL,并将 old_ss 指定为非 NULL 值,可以获得备用信号堆栈的当前设置而无需更改它们。
sigaltstack() 成功时返回 0,失败时返回 -1,并设置 errno 以指示错误。
EFAULT
ss 或 old_ss 不为 NULL 并指向进程地址空间之外的区域。
EINVAL
ss 不是 NULL 并且 ss_flags 字段包含无效标志。
ENOMEM
新备用信号堆栈 ss.ss_size 的指定大小小于 MINSIGSTKSZ。
EPERM
尝试在备用信号堆栈处于活动状态时更改它(即,线程已经在当前备用信号堆栈上执行)。
Interface | Attribute | Value |
---|---|---|
sigaltstack() | Thread safety | MT-Safe |
备用信号堆栈最常见的用法是处理标准堆栈可用空间耗尽时生成的 SIGSEGV 信号:在这种情况下,无法在标准堆栈上调用 SIGSEGV 的信号处理程序;如果我们想处理它,我们必须使用备用信号堆栈。
如果线程可能耗尽其标准堆栈,则建立备用信号堆栈很有用。堆栈增长得太大以至于遇到向上增长的堆,或者它达到了调用 setrlimit(RLIMIT_STACK, &rlim) 设置的限制,都意味着标准的堆栈被用尽,此时内核会向线程发送一个 SIGSEGV 信号。在这些情况下,捕获此信号的唯一方法是在备用信号堆栈上。
在 Linux 支持的大多数硬件架构上,堆栈向下增长。 sigaltstack() 自动考虑堆栈增长的方向。
从在备用信号堆栈上执行的信号处理程序时调用的函数或者触发新的信号处理函数都将使用备用信号堆栈。与标准堆栈不同,系统不会自动扩展备用信号堆栈。超过备用信号堆栈的分配大小将导致不可预测的结果。
成功调用 execve(2) 会删除任何现有的备用信号堆栈。通过 fork(2) 或clone(2)创建的子进程继承其父进程的备用信号堆栈设置的副本。但是对于clone(2)创建的线程,将禁用在父进程中建立的任何备用信号堆栈。
sigaltstack() 取代了旧的 sigstack() 调用。为了向后兼容,glibc 还提供了 sigstack()。所有新应用程序都应使用 sigaltstack() 编写。历史 4.2BSD 有一个 sigstack() 系统调用。它使用了一个稍微不同的结构,主要的缺点是调用者必须知道堆栈增长的方向。
在 Linux 2.2 及更早版本中,唯一可以在 ss.sa_flags 中指定的标志是 SS_DISABLE。 在 Linux 2.4 内核发布之前,进行了更改以允许 sigaltstack() 允许 ss.ss_flags==SS_ONSTACK 具有与 ss.ss_flags==0 相同的含义(即,将 SS_ONSTACK 包含在 ss .ss_flags 是空操作)。 在其他实现中,根据 POSIX.1,SS_ONSTACK 仅作为 old_ss.ss_flags 中的报告标志出现。 在 Linux 上,没有必要在 ss.ss_flags 中指定 SS_ONSTACK,并且确实应该出于可移植性考虑避免这样做:如果 SS_ONSTACK 在 ss.ss_flags 中指定,则其他各种系统都会出错。
以下代码段演示了如何使用 sigaltstack()(和 sigaction(2))来安装备用信号堆栈,该堆栈由 SIGSEGV 信号的处理程序使用:
stack_t ss;
ss.ss_sp = malloc(SIGSTKSZ);
if (ss.ss_sp == NULL) {
perror("malloc");
exit(EXIT_FAILURE);
}
ss.ss_size = SIGSTKSZ;
ss.ss_flags = 0;
if (sigaltstack(&ss, NULL) == -1) {
perror("sigaltstack");
exit(EXIT_FAILURE);
}
sa.sa_flags = SA_ONSTACK;
sa.sa_handler = handler(); /* Address of a signal handler */
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, NULL) == -1) {
perror("sigaction");
exit(EXIT_FAILURE);
}