WARNING这是《深入理解计算机系统》(CS
)的笔记,彼时还未上CO,OS等计算机系统基础课,存在理解不到位与错误之处.
正常的函数调用在用户栈中留下痕迹,一个一个 call 然后一个个 return。非本地跳转允许跳过这些返回过程,直接通过强行设置调用条件(包括用户栈、用户堆等),“天降”式的跳转到代码的某个地方。这是由 setjmp longjmp 函数实现的。
#include<setjmp.h>
int setjmp(jmp_buf env);
int sigsetjmp(sigjmp_buf env,int savesigs); //savesigs作用待定
void longjmp(jmp_buf env,int retval);
void siglongjmp(sigjmp_buf env,int retval);
setjmp longjmp一起用,sigsetjmp siglongjmp一起用。后者是前者可以用在信号处理函数中的版本。
第一次调用 setjmp 的时候,函数在 jmp_buf 类型变量 env 中存储当前的调用环境 (可能是弱版上下文?),供 longjmp 使用。并返回一个0.
在后面使用longjmp 时,强行设置 为 env 中保存的调用环境,其结果就是控制流 回溯 到了调用 setjmp 的地方,并且回到了调用 setjmp的地方,此时setjmp依旧会产生一个返回值(setjmp调用一次返回多次),返回值就是 longjmp 中的 retval 。
由于未知原因,setjmp的返回值不能赋值给变量,其它用法都可以,包括 if switch 等。
它的一个用处是,当函数有复杂的嵌套调用(比如互相递归),出错的时候,可以直接用它跳出嵌套调用,回到调用之前,并且用 retval 传达错误信息。
尤其注意,一定要在调用了 setjmp 后才调用 longjmp,这是一个潜在的竞争。
sigsetjmp siglongjmp 是可以在信号处理函数中使用的变种。它们有更直接强烈的安全要求。它们的调用环境缓冲区是sigjmp_buf类型。sigsetjmp会额外保存信号的上下文信息(包括待处理信号和阻塞信号集合)。(==猜测==savesigs是标记是否保存信号上下文的。)
它们不被认为是异步安全的,因为siglongjmp可以跳到任意代码,所以在siglongjmp可以跳到、以及跳到后可以到达的地方都使用异步安全函数(这意味着大部分地方都要用异步安全函数)。
一个典型的例子是用 键盘 Ctrl+C 发送 SIGINT 实现软重启。
sigjmp_buf env;
void sigint_hindler(int sig)
{
siglongjmp(env,1);
}
int main()
{
if(!sigsetjmp(&env,1)){
signal(SIGINT,sigint_handler);
Sio_puts("start...\n");
}
else
Sio_puts("restarting...\n");
while(1){
do something...
}
exit(0)
}
有几个值得注意的地方。用siglongjmp给sigsetjmp 的返回值来判断是否第一次调用sigsetjmp。sigsetjmp必须在设置处理函数之前(否则将会冒设置了处理函数后、sigsetjmp之前被中断的可能,这会导致程序跳到未知的地方)。siglongjmp可达的地方(包括可以跳转到、跳转后可以走到的地方)都使用异步安全函数。