SIGINFO 的 si_pid 在多次调用同一函数后将自身设置为 0

SIGINFO's si_pid sets itself to 0 after a few calls of the same function

我正在做一个简单的项目,使 2 个进程使用信号相互通信。更具体地说,我正在使用带有标志 SA_SIGINFOsigaction,这样每个进程都可以识别是谁向它发送了信号并进行回复。 事情是,在他们互相打电话几次之后(变化很大,有时发生在 3 次交换之后,其他时候发生在 700 次之后),siginfo returns a si_pid等于 0。 这是我用来使它们进行通信的两个代码。 首先,“服务器”

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

void    ft_respond(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;
    (void)context;
    if (sig == SIGUSR1)
    {
        i++;
        printf("received - %d PID: %d\n", i, info->si_pid);
        if (info ->si_pid != 0)
            kill(info->si_pid, SIGUSR1);
        if (i == 5000)
            exit(EXIT_SUCCESS);
    }
}

int main(void)
{
    struct sigaction    reaction;
    sigset_t            mask;

    reaction.sa_flags = SA_SIGINFO;
    reaction.sa_sigaction = ft_respond;
    sigaction(SIGUSR1, &reaction, NULL);
    printf("PID = %d\n", getpid());
    while (1)
        pause();
    return(0);
}

其次,“客户端”

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

void    ft_send_signal(int pid)
{
    kill(pid, SIGUSR1);
    printf("sent\n");
}

void    ft_signal_handler(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;

    (void)context;
    if (sig == SIGUSR1)
    {
        printf("recieved - %d PID: %d\n", i, info->si_pid);
        i++;
        if (info->si_pid != 0)
            kill(info->si_pid, SIGUSR1);
        if (i == 5000)
            exit(EXIT_SUCCESS);
    }
}

int main(int ac, char **av)
{
    struct sigaction    action;
    sigset_t            set;
    
    if (ac != 2)
        exit (EXIT_FAILURE);
    sigaddset(&set, SIGUSR1);
    action.sa_flags = SA_SIGINFO;
    action.sa_sigaction = ft_signal_handler;
    action.sa_mask = set;
    sigaction(SIGUSR1, &action, NULL);
    ft_send_signal(atoi(av[1]));
    while(1)
        pause();
    return (0);
}

备注:

如果您有任何线索,我可以跟随它使它正常工作,我将非常感激。

诊断

大多数时候,当我在 MacBook Pro 运行ning Big Sur 11.6.3 上进行测试时,命令 运行 完成。我正在使用程序 tester 到 运行 服务器,然后是客户端 — 该程序的优点是我可以准确地报告客户端和服务器程序的退出状态。我一直在使用越来越复杂的测试平台来捕获信息。

每隔一段时间,我似乎让服务器立即死机。我认为这是 时间问题 由于 o/s 调度程序。启动代码 运行 在启动服务器后发送给客户端,但碰巧系统调度程序 运行 在服务器设置其信号处理程序之前发送客户端,因此服务器被客户端的终止初始信号。

支持证据

我修改了客户端和服务器程序以包含 alarm(15); 以便进程在 15 秒后超时。大多数情况下,两人只需不到一秒钟的时间即可完成。在那些失败的情况下,我让服务器以状态 0x001E 退出(这表明它死于 SIGUSR1 信号)并且在相同的 运行s 上,客户端在 15 秒后以状态 0x000E 退出(这表明它死了来自 SIGALRM 信号)。日志文件不包含“received”消息。

$ rmk && timecmd -m -- ./tester | tpipe -sx "grep -c -e '^C received'" "grep -c -e '^S received'" "grep -E -e 'PID =|^Child|(Server|Client) PID'"  "cat > log.$(isodate -c)"
2022-02-15 14:25:07.172 [PID 10210] ./tester
tpipe: + grep -c -e '^C received'
tpipe: + grep -c -e '^S received'
tpipe: + grep -E -e 'PID =|^Child|(Server|Client) PID'
tpipe: + cat > log.20220215.142507
Server PID: 10211
Client PID: 10212
10212: sent signal to PID = 10211
Child 10211 exited with status 0x001E
Child 10212 exited with status 0x000E
2022-02-15 14:25:22.193 [PID 10210; status 0x0000]  -  15.021s
0
0
$

10211 的状态消息几乎立即出现; 10212 等待了 15 秒多一点。两个零来自 grep -c 命令——没有有趣的消息。

相比之下,之前的 运行 显示:

$ rmk && timecmd -m -- ./tester | tpipe -sx "grep -c -e '^C received'" "grep -c -e '^S received'" "grep -E -e 'PID =|^Child|(Server|Client) PID'"  "cat > log.$(isodate -c)"
2022-02-15 14:25:05.965 [PID 10196] ./tester
tpipe: + grep -c -e '^C received'
tpipe: + grep -c -e '^S received'
tpipe: + grep -E -e 'PID =|^Child|(Server|Client) PID'
tpipe: + cat > log.20220215.142505
Server PID: 10197
Client PID: 10198
PID = 10197
10198: sent signal to PID = 10197
Child 10197 exited with status 0x0000
Child 10198 exited with status 0x0000
2022-02-15 14:25:06.481 [PID 10196; status 0x0000]  -  0.515s
5000
5000
$

此处的 5000 个条目是 grep -c 命令 运行 通过 tpipe 程序的计数。 (rmkmake 的变体;tpipe 是一个有点像 tee 的程序,除了它写入进程而不是文件(另请参见不幸命名为 pee program); isodate 以压缩的 ISO 8601 格式打印日期,例如 20220215.142505; timecmd -m 执行命令并将其计时到毫秒,报告命令和状态等。 )

我没有记录过 info->si_pid == 0 的情况,也没有记录过一些中间数量的信号交换后出现问题的情况 — 它是 0 或 5000,没有其他值。因此,我可能没有准确地重现您的场景。

使用 shell 脚本启动服务器,然后客户端没有重现过早的信号 — 处理 shell 脚本的固有延迟似乎足以让服务器在客户端发送初始信号之前设置其信号处理。

已测试脚本:

time=$(isodate -c)
server > server.$time.log &
client $! > client.$time.log

修改代码

JFTR,这是我修改后的代码。它使用了我的 SOQ (Stack Overflow Questions) repository on GitHub as files stderr.c and stderr.h in the src/libsoq sub-directory.

中可用的一些代码

client.c

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "stderr.h"

static void ft_send_signal(int pid)
{
    if (kill(pid, SIGUSR1) != 0)
        err_syserr("failed to send initial signal to PID %d: ", pid);
    printf("%d: sent signal to PID = %d\n", getpid(), pid);
    fflush(stdout);
}

static void ft_signal_handler(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;

    (void)context;
    if (sig == SIGUSR1)
    {
        printf("C received - %d PID: %d\n", i, info->si_pid);
        fflush(stdout);
        i++;
        if (info->si_pid != 0)
        {
            if (kill(info->si_pid, SIGUSR1) != 0)
                err_syserr("failed to send signal to PID %d: ", info->si_pid);
        }
        else
            err_error("info->si_pid == 0 at iteration %d\n", i);
        if (i == 5000)
            exit(EXIT_SUCCESS);
    }
}

int main(int ac, char **av)
{
    err_setarg0("client");
    struct sigaction    action;
    sigset_t            set;

    if (ac != 2)
        err_usage("PID");

    alarm(15);
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    action.sa_flags = SA_SIGINFO;
    action.sa_sigaction = ft_signal_handler;
    action.sa_mask = set;
    sigaction(SIGUSR1, &action, NULL);
    ft_send_signal(atoi(av[1]));
    while(1)
        pause();
    return (0);
}

server.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "stderr.h"

static void ft_respond(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;
    (void)context;
    if (sig == SIGUSR1)
    {
        i++;
        printf("S received - %d PID: %d\n", i, info->si_pid);
        fflush(stdout);
        if (info->si_pid != 0)
        {
            if (kill(info->si_pid, SIGUSR1) != 0)
                err_syserr("failed to send signal to PID %d: ", info->si_pid);
        }
        else
            err_error("info->si_pid == 0 at iteration %d\n", i);
        if (i == 5000)
            exit(EXIT_SUCCESS);
    }
}

int main(void)
{
    err_setarg0("server");
    struct sigaction    reaction;

    sigemptyset(&reaction.sa_mask);
    alarm(15);
    reaction.sa_flags = SA_SIGINFO;
    reaction.sa_sigaction = ft_respond;
    sigaction(SIGUSR1, &reaction, NULL);
    printf("PID = %d\n", getpid());
    fflush(stdout);
    while (1)
        pause();
    return(0);
}

tester.c

#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include "stderr.h"

int main(void)
{
    err_setarg0("tester");
    alarm(20);
    pid_t server = fork();
    if (server < 0)
        err_syserr("failed to fork for server: ");
    if (server == 0)
    {
        char *args[] = { "./server", 0 };
        execv(args[0], args);
        err_syserr("failed to exec server: ");
    }
    printf("Server PID: %d\n", server);
    fflush(stdout);

    pid_t client = fork();
    if (client < 0)
        err_syserr("failed to fork for client: ");
    if (client == 0)
    {
        char buffer[20];
        snprintf(buffer, sizeof(buffer), "%d", server);
        char *argc[] = { "./client", buffer, 0 };
        execv(argc[0], argc);
        err_syserr("failed to exec client: ");
    }
    printf("Client PID: %d\n", client);
    fflush(stdout);

    int corpse;
    int status;
    while ((corpse = wait(&status)) > 0)
    {
        printf("Child %d exited with status 0x%.4X\n", corpse, status);
        fflush(stdout);
    }

    return 0;
}

处方

我不确定是否有解决此问题的好方法,除了添加对客户端代码的调用以延迟它发送初始信号一段重要的时间——一两毫秒可能就足够了。这种延迟意味着服务器有时间设置其信号处理。同样,tester 程序可以在启动服务器和客户端之间增加延迟。

为什么 Linux 没有问题?运气?或者 o/s 调度程序不会在第一个 child 之前 运行 tester 的第二个 child,因此服务器总是在之前设置其信号处理客户端发送第一个信号。

所以,在尝试之后,我偶然发现了一个干净的解决方案。由于程序随机丢失 info->si_pid,我将其值存储到 static int id 中并删除了条件 if (info->si_pid != 0)。从现在开始,如果 info->si_pid == 0,我的 id 仍然存储着 pid。 这是它的样子。我将交易所推到了 50000,每次都非常有效。

服务器:

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

void    ft_respond(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;
    static int  id = 0;

    if (info->si_pid != 0)
        id = info->si_pid;
    (void)context;
    if (sig == SIGUSR1)
    {
        i++;
        printf("received - %d PID: %d\n", i, id);
        kill(id, SIGUSR1);
        if (i == 50000)
            exit(EXIT_SUCCESS);
    }
    return ;
}

int main(void)
{
    struct sigaction    reaction;

    reaction.sa_flags = SA_SIGINFO;
    sigemptyset(&reaction.sa_mask);
    reaction.sa_sigaction = ft_respond;
    sigaction(SIGUSR1, &reaction, NULL);
    printf("PID = %d\n", getpid());
    while (1)
        pause();
    return(0);
}

客户:

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

void    ft_send_signal(int pid)
{
    kill(pid, SIGUSR1);
    printf("sent\n");
}

void    ft_signal_handler(int sig, siginfo_t *info, void *context)
{
    static int  i = 0;
    static int  id = 0;

    if (info->si_pid != 0)
        id = info->si_pid;
    (void)context;
    if (sig == SIGUSR1)
    {
        printf("recieved - %d PID: %d\n", i, id);
        i++;
        kill(id, SIGUSR1);
        if (i == 50000)
            exit(EXIT_SUCCESS);
    }
    return ;
}

int main(int ac, char **av)
{
    struct sigaction    action;
    
    if (ac != 2)
        exit (EXIT_FAILURE);
    sigemptyset(&action.sa_mask);
    action.sa_flags = SA_SIGINFO;
    action.sa_sigaction = ft_signal_handler;
    sigaction(SIGUSR1, &action, NULL);
    ft_send_signal(atoi(av[1]));
    usleep(100);
    while(1)
        pause();
    return (0);
}

现在看来,无论如何,进程都会继续相互发送信号。