在调用进程处理器函数时,内核通常会在进程栈中为其创建一栈。不过,如果进程对栈的扩展突破了对栈大小的限制时,这种做法就不太可行了。比如,栈的增长过大,以至于会触及到一片映射内存或者向上增长的堆,又或者栈的大小已经直逼RLIMIT_STACK资源限制,这些都会造成这种情况的发生。
当进程对栈的扩展试图突破其上限时,内核将为该进程产生 SIGSEGV信号号。不过,因为栈空间已然耗尽,内核也就无法为进程已经安装的SIGSEGV 处理器函数创建栈帧。结果是,处理器函数得不到调用,而进程也就终止了。
如果希望在这种情况下确保对 SIGSEGV 信号处理器函数的调用,就需要做如下工作
- 分配一块被称为“备选信号栈”的内存区域,作为信号处理函数的栈帧
- 调用sigaltstack() ,告知内核该备选信号栈的存在
- 在创建信号处理函数时指定SA_ONSTACK 标志,也就是通知内核在备选栈上为处理器函数创建栈帧。
利用系统调用 sigaltstack(),既可以创建一个备选信号栈,也可以将已创建备选信号栈的相关信息返回。
NAME
sigaltstack - set and/or get signal stack context
SYNOPSIS
#include <signal.h>
int sigaltstack(const stack_t *ss, stack_t *oss);
DESCRIPTION
ss 所指向的数据结构描述了新备选信号栈的位置及属性
oss 指向的结构则用于返回上一备选信号栈的相关信息(如果存在)
两个参数之一均可为 NULL.例如,将参数 ss设为 NULL 可以发现现有备选信号栈,并且不用将其改变。
不为 NULL时,这些参数所指向的数据结构类型如下
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_sp 和 ss_size 分别指定了备选信号栈的位置和大小.在实际使用信号栈时,内核会将 ss_sp
值自动对齐为与硬件架构相适宜的地址边界
ss_flags 可以包含如下值之一
SS_ONSTACK
如果在获取已创建备选信号栈的当前信息时该标志已然置位,就表明进程正在备选信号栈上执行。当
进程已经在备选信号栈上运行时,试图调用 sigaltstack()来创建一个新的备选信号栈将会产生一
个错误(EPERM)。
SS_DISABLE
在 old_sigstack 中返回,表示当前不存在已创建的备选信号栈。如果在 sigstack 中指定,则
会禁用当前已创建的备选信号栈
备选信号栈通常既可以静态分配,也可以在堆上动态分配。SUSv3 规定将常量 SIGSTKSZ作为划分备选栈大小的典型值,而将 MINSSIGSTKSZ 作为调用信号处理器函数所需的最小值。在 Linux/x86-32 系统上,分别将这两个值定义为 8192 和 2048。
内核不会重新划分备选栈的大小。如果栈溢出了分配给它的空间,就会产生混乱。这通常不会是一个问题,因为一般情况下会利用备选栈来处理标准栈溢出的特殊情况,尝尝只在这个栈上分配为数不多的几帧。SIGSEGV 处理器函数的工作不是在执行清理动作后终止进程,就是使用非本地跳转解开标准栈。
看个例子:在创建一个新的备选信号栈以及 SIGSEGV的信号处理器函数之后,程序将调用一个无限递归函数,这会导致栈溢出,同时系统会向进程发送 SIGSEGV 信号。
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <zconf.h>
#define _GNU_SOURCE /* Get strsignal() declaration from <string.h> */
#include <string.h>
#include <signal.h>
static void sigsegvHandler(int sig)
{
int x;
/* UNSAFE: 该处理程序使用非异步信号安全功能
(printf(), strsignal(), fflush(); ) */
printf("Caught signal %d (%s)\n", sig, strsignal(sig));
printf("Top of handler stack near %10p\n", (void *) &x);
fflush(NULL);
_exit(EXIT_FAILURE); /* Can't return after SIGSEGV */
}
/* The following stops 'gcc -Wall' complaining that "control reaches
end of non-void function" because we don't follow the call to
overflowStack() stack in main() with a call to exit(). */
#ifdef __GNUC__
static void
overflowStack(int callNum) __attribute__ ((__noreturn__));
#endif
static void /* A recursive function that overflows the stack */
overflowStack(int callNum)
{
char a[100000]; /* Make this stack frame large */
printf("Call %4d - top of stack near %10p\n", callNum, &a[0]);
overflowStack(callNum+1);
}
int
main(int argc, char *argv[])
{
stack_t sigstack;
struct sigaction sa;
int j;
printf("Top of standard stack is near %10p\n", (void *) &j);
/* 分配备用堆栈并通知内核其存在 */
sigstack.ss_sp = malloc(SIGSTKSZ);
if (sigstack.ss_sp == NULL){
perror("malloc");
exit(EXIT_FAILURE);
}
sigstack.ss_size = SIGSTKSZ;
sigstack.ss_flags = 0;
if (sigaltstack(&sigstack, NULL) == -1){
perror("sigaltstack");
exit(EXIT_FAILURE);
}
printf("Alternate stack is at %10p-%p\n",
sigstack.ss_sp, (char *) sbrk(0) - 1);
sa.sa_handler = sigsegvHandler; /* Establish handler for SIGSEGV */
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_ONSTACK; /* Handler uses alternate stack */
if (sigaction(SIGSEGV, &sa, NULL) == -1){
perror("sigaction");
exit(EXIT_FAILURE);
}
overflowStack(1);
}
运行该程序的结果如下
在这一 shell 会话中,命令 ulimit 负责移除 shell 之前可能设置的任何 RLIMIT_STACK 资源限制。