Обработка SIGINT в программе на C, которая использует syslog () в ARM, заставляет выполнение программы перейти на несколько строк назад - PullRequest
1 голос
/ 05 апреля 2019

Рассмотрим следующую программу. Он входит в состояние ожидания в цикле 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 голосов
/ 07 апреля 2019

Обработчики сигналов не являются магией.Они вызываются в режиме пользователя, поэтому они должны вызываться в коде пользователя.Но они могут быть обнаружены только в режиме ядра, поэтому ядро ​​передает сигнал процессу и каким-либо образом указывает, что процесс должен выполняться в пользовательском режиме, когда вызывается обработчик сигнала.

Это происходит только тогда, когдапроцесс выполняет системный вызов или обычно, когда ядро ​​выгружает процесс, потому что он слишком долго работал.Поскольку код должен выполняться в пользовательском режиме, разработчики UNIX (и, боюсь, это преобладало до сих пор), вызов обработчиков сигналов происходит именно тогда, когда ядро ​​собирается вернуться к пользовательскому процессу (механизм состоит изпри манипулировании пользовательским стеком для перехода к обработчику сигналов при возврате из системного вызова, и позволить такому искалеченному стеку возвращаться к прерванному коду, как если бы прерывание было истинным аппаратным прерыванием.возможность запуска в режиме ядра.

Когда процесс останавливается при системном вызове, механизм прост, так как пользовательский процесс не выполняет пользовательский код, и обработчик сигнала будет вызываться очень специфичноточка, после возврата syscall (таким образом, место фактически находится в точке кода сразу после системного вызова --- который возвращает -1 с errno, установленным на EINTR, но вы действительно можете проверить это только после обработчик сигнала уже был вызван), ноn процесс вытеснен, есть проблема, что процесс может быть где угодно в его коде.Упомянутое выше искажение стека должно иметь дело с этим и быть готовым восстановить полное состояние процессора (как это происходит с возвратом из аппаратного прерывания), чтобы иметь возможность выполнить обработчик сигнала в любой точке пользовательского кода и выйтивсе правильно.С этим проблем нет, так как ядро ​​сохранило его, когда прервал процесс.Единственное отличие состоит в том, что полное восстановление состояния процессора откладывается до тех пор, пока не будет выполнен обработчик сигнала, после возврата в пользовательский режим.

Код для управления обработчиком сигналов обычно устанавливается ядром на карту памяти пользовательского режима.(в системах BSD это происходит в верхней части стека потоков основного процесса, прежде чем задавать параметры среды и параметры exec(2) args и argv, argc.) Но это может быть где угодно в виртуальном пространстве программы.

Ваш случай здесь - чистое выполнение в пользовательском коде, поэтому, пока ядро ​​не выгрузит процесс, он не получит сигнал.Это может произойти где-нибудь в цикле, когда прерывание по таймеру останавливает процесс и процесс перепланируется снова, непосредственно перед возвратом в пользовательский режим, стек искажается, чтобы вызвать переход к диспетчеру обработчика сигналов, происходит переключение в пользовательский режим., это заставляет программу переходить к диспетчеру обработчиков сигналов, она вызывает ваш обработчик сигналов, затем диспетчер обработчиков сигналов восстанавливает состояние полного процессора и возвращается в то место, где ядро ​​прервало процесс, как если бы прерывание вызвало аппаратное прерывание.

0 голосов
/ 05 апреля 2019

Вы говорите, что ваша программа "возобновляет" после того, как обработчик сигнала возвращается, но на самом деле программа никогда не останавливается, потому что она выполняет "ожидание занятости". Если вы хотите дождаться прихода сигнала, вам следует использовать функцию sigsuspend, которая фактически блокирует процесс до тех пор, пока ему не будет доставлен сигнал , см. Справку здесь . В любом случае непредвиденное поведение может быть вызвано флагом, установленным в цикле while, обратите внимание, что он используется совместно с обработчиком сигнала, поэтому переменная должна быть атомарной и объявлена ​​следующим образом: static volatile sig_atomic_t module_running;.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...