带有 `SA_SIGINFO` 的 macOS `sigaction()` 处理程序不包括 `si_pid`

macOS `sigaction()` handler with `SA_SIGINFO` does not include `si_pid`

我正在尝试编写一个信号处理程序,它需要知道发送信号的进程的 pid。在 macOS 10.14 和 Xcode 10.

上,我没有从 siginfo_t 传递到我的处理程序中得到任何有用的东西

我已将我的代码缩减为以下最小示例来演示该问题。在此示例中,我生成了一个子进程来发送我要测试的信号,该信号默认为 SIGTERM,但我尝试过的其他信号都没有更好的效果。

假设您想在 mac 上构建和测试它,您可能想告诉 lldb 在接收到信号时不要停止。您可以使用此 lldb 命令:pro hand -p true -s false SIGTERM.

我也在用 C++ 编译,但我相信我已经删除了所有这些,示例代码现在应该是纯 C。

请注意,无论信号来自子进程、终端还是其他进程,结果总是 si_pid 始终为 0(以及除si_signosi_addr)。不管我发送了多少次信号,所以它似乎不仅仅是一个竞争条件。

如何获取在 macOS 10.14 上发送信号的进程的 pid?我不记得在我之前使用的 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);
    }
}

我目前使用的是 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, '[=10=]', 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.

我更喜欢在分叉之前初始化信号实用程序,如果父进程没有机会注册信号操作怎么办?此外,我不明白你为什么要处理 01 个案例 switch。本质上,这些案例没有被命中,所以总是被省略。

此外,您在 main() 内的 if 条件中没有使用 break。一段时间后它没有进入 if 但以下情况是无法预料和期望的是程序永远停留在 while() 循环中。我更愿意将 signaled 放入 while() 循环的条件中。

最后但同样重要的是,由于 sleep() 在子进程中调用直到 signaled 变成 0SIGTERM 被成功捕获了几次。当信号为 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

    }
}

事实证明,通过Xcode LLDB 调试是罪魁祸首。如果我正常构建并运行程序,它就可以正常工作。如果我找出原因,我会更新这个答案。

如问题中所述,我已经在 lldb 中为 SIGTERM 设置了 "PASS",所以似乎 Xcode 10.0 附带的 lldb 版本中存在某种错误,它是"passing" 通过创建新结构并设置信号编号而不是通常会收到的结构来发送信号。正如我之前所说,这在 macos 10.12

附带的任何版本的 lldb 中都可以正常工作

如果有人有更好的解释,请post回答,我会接受并奖励赏金。