在ARM上使用syslog()的C程序中处理SIGINT会导致程序执行后退几行

时间:2019-04-05 12:20:14

标签: c linux signals

请考虑以下程序。它在while循环内进入忙等待状态,等待SIGINT信号处理程序取消设置循环条件,从而使其离开并允许main()正常为return而不是只是杀死进程:

#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <signal.h>
#include <syslog.h>

#define RES_ERROR                                   -1
#define RES_OK                                      1

#define ARG_MAX_SIZE                                30
#define MAX_BUFFER                                  64

static bool module_running = true;

static void SigHandlerIMU(int signal_number);
static int ProcessSignalConfig(void);

static void SigHandlerIMU(int signal_number)
{
    if(signal_number == SIGINT){

        module_running = false;
    }
    return;
}/*SigHandlerIMU*/

static int ProcessSignalConfig(void)
{
    int ret_value = RES_ERROR;
    struct sigaction signal_handler;

    syslog(LOG_USER | LOG_NOTICE, "Catching SIGINT...\n");
    signal_handler.sa_handler = SigHandlerIMU;
    if(sigaction(SIGINT, &signal_handler, NULL) == -1){
        syslog(LOG_USER | LOG_ERR, "can't catch SIGINT\n");
    }
    else{
        ret_value = RES_OK;
    }

    return ret_value;
}/*ProcessSignalConfig*/

int main(int argcount, char const *argvalue[])
{
    int main_return_val = RES_ERROR;

    struct sigaction signal_handler;

    (void)setlogmask (LOG_UPTO (LOG_DEBUG));
    openlog(NULL, LOG_PERROR | LOG_PID, LOG_USER);

    syslog(LOG_USER | LOG_NOTICE, "Starting program...\n");


    if(ProcessSignalConfig() < 0){
        syslog(LOG_USER | LOG_ERR, "Failed catching process signals\n");
        module_running = false;
    }

    syslog(LOG_USER | LOG_DEBUG, "Entering loop...\n");
    while(module_running == true){

    }

    syslog(LOG_USER | LOG_DEBUG, "Exiting...\n");

    closelog();

    return main_return_val;
} /*main*/

根据目标体系结构,我会得到不同的行为。

使用gcc signal_test.c -o signal_test编译程序,最后一次调用return的情况下,syslog()立即编译程序。

signal_test[4620]: Starting program...
signal_test[4620]: Catching SIGINT...
signal_test[4620]: Entering loop...
^Csignal_test[4620]: Exiting...

但是,使用arm-linux-gnueabihf-gcc signal_test.c -o signal_test进行编译似乎会跳回到对ProcessSignalConfig()的调用,然后从那里继续(观察重复的迹线):

signal_test[395]: Starting program...
signal_test[395]: Catching SIGINT...
signal_test[395]: Entering loop...
^Csignal_test[395]: Catching SIGINT...
signal_test[395]: Entering loop...
signal_test[395]: Exiting...

编辑:我一直在做进一步的测试,如果我使用了所有的printf()而不是syslog(),则该程序在ARM上也可以正常运行。我将问题标题更新为当前情况

2 个答案:

答案 0 :(得分:0)

您告诉您的程序是在信号处理程序返回后“恢复”,但实际上该程序永远不会停止运行,因为它正在执行“繁忙等待”。如果要等待信号到达,则应使用函数sigsuspend,该函数实际上会阻塞该过程,直到将信号传递给它see help here为止。无论如何,意外行为可能是由while循环中检查的标志引起的,请注意,它与信号处理程序共享,因此该变量应该是原子的,并声明如下:static volatile sig_atomic_t module_running;

答案 1 :(得分:0)

信号处理程序不是魔术。在用户模式下调用它们,因此必须在用户代码中调用它们。但是只能在内核模式下检测到它们,因此内核会在调用信号处理程序时将信号传递给进程,并以某种方式标记进程在用户模式下运行。

仅当进程正在执行系统调用时才会发生这种情况,或者通常是内核因进程运行时间过长而抢占进程时才发生。由于代码必须在用户模式下运行,因此UNIX的设计人员(恐怕到目前为止一直如此),信号处理程序的调用仅在内核即将返回用户进程时发生(该机制包括在处理用户堆栈以从系统调用返回时跳到信号处理程序,并让被堆栈处理的堆栈返回被中断的代码,就好像该中断是真正的硬件中断一样,这允许一切在用户代码中发生并且不会妥协在内核模式下运行的可能性。

当进程在系统调用中停止时,该机制很简单,因为用户进程未执行用户代码,并且在syscall返回之后将在非常特定的位置调用信号处理程序(因此该位置实际上是在系统调用之后的代码点---返回-1,且errno设置为EINTR,但实际上您只能在之后进行检查信号处理程序已经被调用),但是当抢占进程时,就会出现该进程可以在其代码中任何位置的问题。上面提到的堆栈处理必须处理这个问题,并准备恢复完整的cpu状态(例如发生从硬件中断返回的情况),以便能够在用户代码中的任何时候执行信号处理程序并离开没事。这没有问题,因为内核在中断进程时将其保存。唯一的不同是,在返回用户模式后,完整的cpu状态还原将推迟到执行信号处理程序之前。

用于信号处理程序管理的代码通常由内核安装在用户模式内存映射中(在BSD系统中,这发生在主进程线程堆栈的顶部,然后推送环境和exec(2) args和argvargc参数。)但是它可以在程序虚拟空间中的任何位置。

这里的情况是纯在用户代码中执行,因此在内核抢占进程之前,它不会收到信号。这可能发生在循环中的任何地方,当计时器中断停止进程并重新调度该进程时,就在返回用户模式之前,堆栈被重整以强制跳转到信号处理程序管理器, 切换到用户模式后,程序跳转到信号处理程序管理器,调用信号处理程序,然后信号处理程序管理器恢复完整的cpu状态并返回到内核中断进程的位置,就好像硬件中断引起了中断。