Обработчик macOS sigaction () с SA_SIGINFO не включает si_pid

Я пытаюсь написать обработчик сигнала, который должен знать pid процесса, отправляющего сигнал. Мне не удалось получить что-то полезное из siginfo_t, переданного в мой обработчик на macOS 10.14 с Xcode 10.

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

Предполагая, что вы хотите создать и протестировать это на Mac, вы, вероятно, захотите сказать lldb, чтобы он не останавливался при получении сигнала. Вы можете использовать эту команду lldb: pro hand -p true -s false SIGTERM.

Я также компилирую с C++, но я считаю, что исключил все это, и теперь образец кода должен быть чистым C.

Обратите внимание, что не имеет значения, исходит ли сигнал от дочернего, терминального или другого процесса, результатом всегда будет то, что si_pid всегда равен 0 (вместе со всем, кроме si_signo и si_addr). Не имеет значения, сколько раз я отправляю сигнал, так что это, похоже, не просто состояние гонки.

Как я могу получить pid процесса, отправляющего сигнал в macOS 10.14? Я не помню, чтобы у меня была эта проблема 10.12, которую я использовал раньше.

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

Если кажется, что код должен работать так, как я ожидал, то мне было бы интересно увидеть комментарии о системах, на которых он работает.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>

volatile sig_atomic_t histogram[3] = {0,0,0};
volatile sig_atomic_t signaled = 0;
const int testsig = SIGTERM;

void sigaction_handler(int sig, siginfo_t* info, void* context)
{
    switch (info->si_pid) {
        case 0:
        case 1:
            histogram[info->si_pid]++;
            break;

        default:
            histogram[2]++;
            break;
    }
    signaled = 1;
}

int main(int argc, const char * argv[]) {

    pid_t mainpid = getpid();
    pid_t pid = fork();
    if (pid == 0) {
        while (kill(mainpid, 0) == 0) {
            sleep(1);
            kill(mainpid, testsig);
        }
        _exit(0);
    }

    struct sigaction sigAction;
    memset( &sigAction, 0, sizeof( sigAction ) );

    sigAction.sa_sigaction = sigaction_handler;
    sigemptyset (&sigAction.sa_mask);
    sigAction.sa_flags = SA_SIGINFO;
    sigaction(testsig, &sigAction, NULL);

    while (1) {
        if (signaled) {
            printf("pid 0: %d, pid 1: %d, others: %d\n", histogram[0], histogram[1], histogram[2]);
            signaled = 0;
        }
        sleep(1);
    }
}

Необходимо добавить #include <stdbool.h> для компиляции как C, кстати.

Shawn 20.11.2018 20:20

@Shawn спасибо, я отредактировал код.

Brad Allred 20.11.2018 20:38

Обработчик сигналов также вызывает неопределенное поведение в соответствии с POSIX, потому что он назначает переменные, которые не являются переменными volatile sig_atomic_t. (Ссылка). Я могу изобразить цикл в основном, всегда видящий signaled как ложный с агрессивными настройками оптимизации (но не может дублироваться с помощью gcc или clang в Linux). Однако у вас нет Mac для реального полезного тестирования.

Shawn 20.11.2018 20:47

@Shawn Я ценю это, однако мой IRL-код этого не делает. Я подумаю об обновлении образца с помощью совместимого кода, но не понимаю, как это может быть проблемой. Отладчик тоже всегда говорит, что это 0.

Brad Allred 20.11.2018 22:06

Я почти уверен, что struct sigaction sigAction = {}; - это недопустимый код C. Фактически, cppreference.com заявляет «В C список инициализаторов в фигурных скобках не может быть пустым.», Но я не собираюсь анализировать 6.7.9 Инициализация стандарта C и проверять, что это правильно.

Andrew Henle 20.11.2018 22:39

@AndrewHenle, конечно, я обновил код соответствующим образом. Все еще не проблема :(

Brad Allred 20.11.2018 23:51

Теперь он полностью неинициализирован, кроме явно установленных вами полей. memset( &sigAction, 0, sizeof( sigAction ) ); вместе с нынешним sigemptyset() были бы хороши.

Andrew Henle 21.11.2018 00:05

@AndrewHenle на Mac это единственные поля, но я все равно добавлю это.

Brad Allred 21.11.2018 00:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
5
8
892
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Сейчас я использую macOS Mojave 10.14.1.

How can I get the pid of the process sending the signal on macOS 10.14? I don't recall having this issue on 10.12 which is what I was using before.

Следующий код просто соответствует вашему желанию. Если вы отправляете SIGTERM, вы можете увидеть pid процесса отправителя.

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>

static void hdl (int sig, siginfo_t *siginfo, void *context)
{
    printf ("Sending PID: %ld, UID: %ld\n",
            (long)siginfo->si_pid, (long)siginfo->si_uid);
}

int main (int argc, char *argv[])
{
    struct sigaction act;

    fprintf(stderr, "%i pp %i\n",getpid(), getppid());

    memset (&act, '\0', sizeof(act));

    /* Use the sa_sigaction field because the handles has two additional parameters */
    act.sa_sigaction = &hdl;

    /* The SA_SIGINFO flag tells sigaction() to use the sa_sigaction field, not sa_handler. */
    act.sa_flags = SA_SIGINFO;

    if (sigaction(SIGTERM, &act, NULL) < 0) {
        perror ("sigaction");
        return 1;
    }

    while (1)
        sleep (10);

    return 0;
}

Для вашего кода

Rule of thumb: Don't forget to carry burial procedures out even though you are sure that child process ends prior parent process. By invoking wait(...) you tell the operating system that I'm done my things for my child so now you can clean allocated fields etc.

Я бы предпочел инициализировать сигнальные утилиты перед разветвлением. А что, если родительский процесс не имеет возможности зарегистрировать сигнальное действие? Более того, я не понимаю, зачем вы обрабатываете кейсы 0 и 1 в switch. По сути дела, случаи не затрагиваются, поэтому всегда опускаются.

Кроме того, вы не использовали break в своем состоянии if в main(). Через некоторое время она не попадает в if, но следующее обстоятельство, которое не ожидается и желательно, заключается в том, что программа навсегда остается в цикле while(). Я бы предпочел привести signaled в состояние петли while().

Наконец, что не менее важно, из-за вызова sleep() в дочернем процессе, пока signaled не выключен, 0, SIGTERM успешно ловится несколько раз. Когда сигнализируется 0, цикл останавливается.

#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <memory.h>
#include <sys/wait.h>

volatile sig_atomic_t histogram[3] = {0,0,0};
volatile sig_atomic_t signaled = 0;
const int testsig = SIGTERM;

void sigaction_handler(int sig, siginfo_t* info, void* context)
{
    switch (info->si_pid) {
    case 0:
    case 1:
        histogram[info->si_pid]++;
        break;

    default:
        fprintf(stderr, "sender pid -> %i\n", info->si_pid);
        histogram[2]++;
        break;
    }
    signaled = 1;
}

int main(int argc, const char * argv[]) {


    struct sigaction sigAction;
    memset( &sigAction, 0, sizeof( sigAction ) );

    sigAction.sa_sigaction = sigaction_handler;
    sigemptyset (&sigAction.sa_mask);
    sigAction.sa_flags = SA_SIGINFO;
    sigaction(testsig, &sigAction, NULL);

    pid_t mainpid = getpid();
    pid_t pid = fork();
    if (pid == 0) {
        fprintf(stderr, "my pid -> %i parent's pid-> %i\n", getpid(), getppid());
        if (kill(mainpid, 0) == 0) { // signals are not queued not need loop
            sleep(1);
            kill(mainpid, testsig);
        }
        _exit(0);
    } else {

        wait(NULL); // play with this line to see what the difference is
        while ( signaled ) {
            printf("pid 0: %d, pid 1: %d, others: %d\n", histogram[0], histogram[1], histogram[2]);
            signaled = 0;
            sleep(1);
        }
        // wait(NULL); // play with this line to see what the difference is

    }
}

странно, ваш безвилочный образец у меня тоже не работает на моей машине. и pid, и uid по-прежнему равны 0.

Brad Allred 04.12.2018 17:13

Чтобы было ясно, SIGTERM, которого я ожидаю, должен исходить либо от launchd, либо с терминала (у меня ни то, ни другое не работает). wait и цикл, о котором вы говорите, спорный и просто часть того, что я надеялся, будет ясным примером того, что pid отправителя всегда равен 0 в mojave; «исправление» их ничего не меняет.

Brad Allred 04.12.2018 17:13

Хорошо, поэтому в вашем втором примере wait, кажется, проясняет ситуацию в отношении сигналов, отправленных от детей. К сожалению, мой код IRL требует этого от терминального процесса, и я все еще не могу заставить это работать. Есть какие-нибудь идеи?

Brad Allred 04.12.2018 17:51
Ответ принят как подходящий

Оказывается, виновата отладка через Xcode LLDB. Если я собираю и запускаю программу в обычном режиме, она работает нормально. Если узнаю, зачем обновлю этот ответ.

У меня уже есть «PASS», установленный для SIGTERM в lldb, как указано в вопросе, поэтому кажется, что каким-то образом есть ошибка в версии lldb, поставляемой с Xcode 10.0, и он «передает» сигнал, создавая новую структуру и установка номера сигнала, а не структуры, которая обычно была бы получена. Как я уже говорил ранее, раньше это работало нормально в любой версии lldb, поставляемой с macos 10.12.

Если у кого-то есть более подробное объяснение, отправьте ответ, и я приму награду и вручу награду.

Другие вопросы по теме