5

我正在尝试编写一个信号处理程序,它需要知道发送信号的进程的 pid。siginfo_t在带有 Xcode 10 的 macOS 10.14 上,我无法从传递给我的处理程序中获得任何有用的东西。

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

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

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

请注意,信号是否来自子进程、终端或其他进程并不重要,结果始终si_pid为 0(以及除si_signoand之外的所有内容si_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);
    }
}
4

2 回答 2

4

我目前正在使用 macOS Mojave 10.14.1。

如何获取在 macOS 10.14 上发送信号的进程的 pid?我不记得在 10.12 上遇到过这个问题,这是我之前使用的。

以下代码简单地满足了您的愿望。如果你发送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;
}

对于您的代码,

经验法则:即使您确定子进程结束了先前的父进程,也不要忘记执行埋葬程序。通过调用wait(...)你告诉操作系统我已经为我的孩子做了我的事情,所以现在你可以清理分配的字段等。

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

另外,你没有break在你的if条件下使用main(). 一段时间后它不会进入,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

    }
}
于 2018-12-03T15:31:27.203 回答
2

事实证明,通过Xcode LLDB 进行调试是罪魁祸首。如果我正常构建并运行程序,它可以正常工作。如果我知道为什么我会更新这个答案。

如问题中所述,我已经在 lldb 中为 SIGTERM 设置了“PASS”,因此 Xcode 10.0 附带的 lldb 版本中似乎存在某种错误,它通过创建新结构来“传递”信号和设置信号编号而不是通常会收到的结构。正如我之前所说,这确实在 macOS 10.12 附带的任何版本的 lldb 中都可以正常工作

如果有人有更好的解释,请发布答案,我将接受并奖励赏金。

于 2018-12-04T17:22:53.523 回答