《Linux C编程实战》笔记:信号处理函数的返回

信号处理函数可以正常返回,也可以调用其他函数返回到程序的主函数中,而不是从处理程序返回。

setjmp/longjmp

使用longjmp可以跳转到setjmp设置的位置

这两个函数原型如下

#include
int setjmp(jmp_buf env);
void longjmp(jmp_buf env,int val);

参数env是一个特殊类型jmp_buf 的变量。这一数据类型是某种形式的数组,其中存放的是在调用longjmp时能用来恢复栈状态的所有信息。一般来说env是个全局变量,因为需从另一个函数中引用它。我们可以在希望返回的位置使用setjmp,直接调用setjmp时返回0;当从longjmp返回时,setjmp的返回值是longjmp的第2个参数的值,可以利用这一点使多个longjmp返回到一个setjmp处。

示例程序1

演示这两个函数的用法

#include
#include
#include
#include
jmp_buf env;//保存待跳转位置的栈信息
void handler_sigrtmin(int signo){
    printf("recv SIGRTMIN\n");
    longjmp(env,1);//返回到env处,第二个参数是1
}
void handler_sigrtminplus1(int signo){
    printf("recv sigrtmin+1\n");
    longjmp(env,2);
}
int main(){
    printf("pid:%d\n",getpid());//打印出本进程的id,方便之后使用
    //设置返回点
    switch (setjmp(env))
    {
    case 0:
        break;
    case 1:
        printf("return from SIGRTMIN\n");
        break;
    case 2:
        printf("return from SIGRTMIN+1\n");
        break;
    default:break;
    }
    signal(SIGRTMIN,handler_sigrtmin);
    signal(SIGRTMIN+1,handler_sigrtminplus1);
    
    while (1);
    return 0;
}

程序在main函数内调用setjmp设置了返回点。信号处理函数内部打印出提示信息后没有正常返回,而是调用longjmp直接跨函数跳转,返回到setjmp处。

执行程序时,在一个终端执行本程序,在另一个终端用kill命令发送信号

首先打印出pid,之后我们打开另一个终端,往程序发信号

再回去看看

结果符合预期。

但是就没有问题了吗,我们继续用kill发送同样的信号

我们用kill连续发三个同样的信号

但是程序里只响应了一次

这是为什么呢?正如我们在《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客所介绍的,信号处理时会自动阻塞正在被处理的信号,在信号处理函数返回时把进程的信号屏蔽字恢复,即解除对当前信号的阻塞。示例程序没有让信号处理函数正常返回,而是使用longjmp直接跳转,所以进程的信号屏蔽字在第一次收到信号后, 就把信号设置为阻塞并且再也没有恢复,因而再也触发不了信号处理函数了,除非手动将进程对信号的屏蔽去除。如果既想使用跨函数跳转直接返回,又想避免每次都手动清除信号屏蔽的麻烦,就要使用下面的函数了。

sigsetjmp/siglongjmp

为了解决信号被屏蔽的问题,可以用下面两个函数来解决问题

#include
int sigsetjmp(sigjmp_buf env,int savesigs);
void siglongjmp(sigjmp_buf env,int val);

这两个函数和之前的函数的唯一区别就是sigsetjmp多了一个参数savesigs,如果savesigs非0,则sigsetjmp在env中保存进程的当前信号屏蔽字,在调用siglongjmp时会从其中恢复保存的信号屏蔽字。

示例程序2

#include
#include
#include
#include
#define ENV_UNSAVE 0
#define ENV_SAVED 1
int flag_saveenv=ENV_UNSAVE;
sigjmp_buf env;
void handler_sigrtmin(int signo){
    if(flag_saveenv==ENV_UNSAVE)
        return;
    printf("recv SIGRTMIN\n");
    sleep(10);
    printf("in handler_sigrtmin,after sleep");
    siglongjmp(env,1);
}
int main(){
    printf("pid:%d\n",getpid());
    switch (sigsetjmp(env,1))//第二个参数只要不是0就可以
    {
    case 0:
        printf("return 0\n");
        flag_saveenv=ENV_SAVED;
        break;
    case 1:
        printf("return from SIGRTMIN\n");
        break;
    default:
        printf("return else\n");
        break;
    }
    signal(SIGRTMIN,handler_sigrtmin);
    while (1);
    return 0;
}

本程序的信号处理函数先检查flag_saveenv的值是否为ENV_UNSAVE,如果是,则直接返回,因为此时程序还没来得及保存返回点的栈状态信息。在sigsetjmp 之后才将flag_ saveenv设置为ENV_ SAVED。如果不这样处理,那么当信号发生在调用sigsetjmp之前时,信号处理函数将返回到未知地点或程序崩溃(感兴趣的读者可以在sigsetjmp前面加上sleep (20),可以观察到程序崩溃)。使用siglongjmp从信号处理程序返回时都应该这样处理。

(这是书上所说的,我感觉书上说的情况可能代码是signal放在sigsetjmp之前,这样可能没执行sigsetjmp就有信号发生,示例的代码是先执行sigsetjmp再绑定的信号处理函数,不会发生上面所说的情况)

执行流程如图

《Linux C编程实战》笔记:信号处理函数的返回_第1张图片

你可能感兴趣的:(c语言,笔记,信号处理,linux)