1

我正在开发一个简单的项目,该项目使 2 个进程使用信号相互通信。更具体地说,我将sigaction与标志SA_SIGINFO一起使用,以便每个进程都可以识别谁向它发送了信号并进行回复。问题是,在他们互相调用几次之后(它可能会发生很大变化,有时会发生在 3 次交换之后,其他时候会发生在 700 次之后),siginfo 返回一个等于 0 的 si_pid。这是我用来让他们交流的两个代码。一、“服务器”

#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);
}

笔记:

  • 如果我删除该行if (info ->si_pid != 0),则未接收信号的进程将永远暂停(呃!),有趣的是,另一个进程继续运行,就像它永远接收信号一样(不是杜赫!)。

  • 我环顾四周以了解如何使用 sigfillset 或 sigaddset 来防止在我的处理程序仍在工作时出现任何传入信号,似乎没有什么能破坏这种行为。

  • 如果您想知道,我正在 MacBook 上运行这个程序。

  • 我还在 Linux(基于 Ubuntu 的发行版)上运行了这些程序,但那里没有“错误”。这对我来说似乎很奇怪。

  • 如果你想测试代码,这很简单:用不同的名称(例如gcc -o server server.c && gcc -o client client.c)编译每个程序,首先运行服务器,然后使用服务器的 PID 作为参数运行客户端。

  • 在我因使用带有信号的printf而受到抨击之前,我知道在 printf 执行期间信号中断的情况下不建议这样做(请参阅如何避免printf()在信号处理程序中使用?),但理论上,处理程序在发出信号时完成,所以它应该运作良好。我尝试使用write函数,它具有相同的行为。

如果你有任何线索,我可以按照这个在没有打嗝的情况下工作,我将非常感激。

4

2 回答 2

1

诊断

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

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

支持证据

我修改了客户端和服务器程序以包含alarm(15);,以便进程在 15 秒后超时。大多数情况下,这对完成不到一秒钟。在那些事情失败的情况下,我让服务器以状态 0x001E 退出(这表明它死于 SIGUSR1 信号)并且在相同的运行中,客户端在 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(它是;rmk的变体,是一个有点像的程序,只是它写入进程而不是文件(另请参阅不幸命名的程序);以压缩的 ISO 8601 格式打印日期,例如;执行命令并将其计时到毫秒,报告命令和状态等)maketpipeteepeeisodate20220215.142505timecmd -m

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

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

脚本测试:

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

修改后的代码

JFTR,这是我修改后的代码。它使用我在 GitHub 上的SOQ(堆栈溢出问题)存储库中提供的一些代码作为文件stderr.csrc/libsoq子目录中的stderr.h代码。

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 调度程序不会tester在第一个孩子之前运行第二个孩子,因此服务器总是在客户端发送第一个信号之前设置其信号处理。

于 2022-02-15T21:46:03.380 回答
0

所以,在玩了一圈之后,我偶然发现了一个干净的解决方案。由于程序会随机丢失对 的跟踪,因此info->si_pid我将其值存储到 astatic 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);
}

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

于 2022-02-17T10:33:58.190 回答