You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unix信号同步捕获故障:低usleep值引发信号混叠排查

信号通信的信号混叠问题解决

问题详情

我已经使用了信号掩码sa_mask,但当客户端设置的usleep值较低时,仍然出现信号混叠问题,导致部分信号未被正确处理。客户端逻辑是按顺序发送字符的二进制位:位为1时发送SIGUSR2,位为0时发送SIGUSR1;服务端收集这些位,每凑够8位就拼接成一个字符输出。

由于客户端异步发送信号,信号处理事件无法保证按顺序执行,甚至出现处理函数并发运行、事件重叠的情况。虽然通过usleep延迟发送下一位来等待上一信号处理完成,但这种方式没有可靠保障。

我尝试通过sa_mask阻塞SIGUSR1和SIGUSR2,确保当前信号处理完成后再处理后续信号,但这个方案无效:程序运行初期输出正常,随后因为处理函数冲突导致位序列混乱,输出错误字符。

客户端代码(原版本)

static void get_bit(int pid, char c)
{
    int bit;
    int n;
    int i;

    n = 7;
    i = 0;
    while (n >= 0)
    {
        bit = (c >> n) & 1;
        if (bit == 0)
            kill(pid, SIGUSR1);
        else
            kill(pid, SIGUSR2);
        usleep(50);
        n--;
    }
}

服务端代码(原版本)

static void sighandler(int sig, siginfo_t *info, void *context)
{
    static int              i = 0;
    static pid_t            client_pid = 0;
    static unsigned char    c = 0;

    (void)context;
    if (!client_pid)
        client_pid = info->si_pid;
    c |= (sig == SIGUSR2);
    if (++i == 8)
    {
        i = 0;
        if (!c)
        {
            kill(client_pid, SIGUSR2);
            client_pid = 0;
            return ;
        }
        ft_putchar_fd(c, 1);
        c = 0;
        kill(client_pid, SIGUSR1);
    }
    else
        c <<= 1;
}

int main(int argc, char *argv[])
{
    int                 pid;
    struct sigaction    act;
    sigset_t            set;
    
    pid = getpid();
    memset(&act, 0, sizeof act);
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    act.sa_mask = set;
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = sighandler;
    ft_putnbr_fd(pid, 1);
    write(1, "\n", 1);
    sigaction(SIGUSR1, &act, NULL);
    sigaction(SIGUSR2, &act, NULL);
    while (1)
    {
        pause();
    }
    return (0);
}

测试方式

$ make
$ ./server
> PID
$ ./client PID "$(cat a)"

可用函数限制

  • signal
  • sigemptyset
  • sigaddset
  • sigaction
  • kill
  • getpid
  • pause
  • sleep
  • usleep
  • exit

解决方案:基于反馈的同步发送

核心思路是让客户端仅在收到服务端的反馈信号后,再发送下一位,彻底解决异步发送导致的信号堆积或丢失问题。

修改后的客户端代码

#include <signal.h>
#include <unistd.h>
#include "libft.h"

static int g_ack = 0;

static void client_sighandler(int sig)
{
    if (sig == SIGUSR1)
        g_ack = 1;
    else if (sig == SIGUSR2)
        exit(0);
}

static void send_bit(int pid, int bit)
{
    g_ack = 0;
    if (bit == 0)
        kill(pid, SIGUSR1);
    else
        kill(pid, SIGUSR2);
    while (!g_ack)
        pause(); // 等待服务端反馈
}

static void send_char(int pid, char c)
{
    int n;

    n = 7;
    while (n >= 0)
    {
        send_bit(pid, (c >> n) & 1);
        n--;
    }
}

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

    if (argc != 3)
    {
        ft_putstr_fd("Usage: ./client [server_pid] [message]\n", 2);
        exit(1);
    }
    pid = ft_atoi(argv[1]);
    sigemptyset(&act.sa_mask);
    act.sa_handler = client_sighandler;
    act.sa_flags = 0;
    sigaction(SIGUSR1, &act, NULL);
    sigaction(SIGUSR2, &act, NULL);
    while (*argv[2])
    {
        send_char(pid, *argv[2]);
        argv[2]++;
    }
    send_char(pid, '\0'); // 发送结束符
    return (0);
}

修改后的服务端代码

#include <signal.h>
#include <unistd.h>
#include <string.h>
#include "libft.h"

static void sighandler(int sig, siginfo_t *info, void *context)
{
    static int              i = 0;
    static pid_t            client_pid = 0;
    static unsigned char    c = 0;

    (void)context;
    if (!client_pid)
        client_pid = info->si_pid;
    // 修正位拼接顺序:先左移再赋值
    c <<= 1;
    if (sig == SIGUSR2)
        c |= 1;
    i++;
    if (i == 8)
    {
        i = 0;
        if (!c)
        {
            kill(client_pid, SIGUSR2); // 通知客户端传输结束
            client_pid = 0;
            return ;
        }
        ft_putchar_fd(c, 1);
        c = 0;
    }
    kill(client_pid, SIGUSR1); // 通知客户端可以发送下一位
}

int main(int argc, char *argv[])
{
    int                 pid;
    struct sigaction    act;
    sigset_t            set;
    
    (void)argc;
    (void)argv;
    pid = getpid();
    memset(&act, 0, sizeof(act));
    sigemptyset(&set);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);
    act.sa_mask = set;
    act.sa_flags = SA_SIGINFO;
    act.sa_sigaction = sighandler;
    ft_putnbr_fd(pid, 1);
    write(1, "\n", 1);
    sigaction(SIGUSR1, &act, NULL);
    sigaction(SIGUSR2, &act, NULL);
    while (1)
    {
        pause();
    }
    return (0);
}

关键修改说明

  1. 客户端:添加反馈等待逻辑

    • 新增全局变量g_ack标记是否收到服务端反馈
    • 实现客户端信号处理函数,收到SIGUSR1时标记可以继续发送,收到SIGUSR2时退出
    • send_bit函数发送信号后,通过pause()阻塞等待服务端反馈,确保每一位都被正确处理后再发下一位
  2. 服务端:调整位处理逻辑+每位反馈

    • 修正原代码的位拼接顺序:原逻辑先赋值再左移会导致位顺序颠倒,改为先左移再赋值
    • 每处理完一位就立即给客户端发送SIGUSR1反馈,允许客户端发送下一位
    • 收到结束符\0时发送SIGUSR2通知客户端传输完成
  3. 为什么原sa_mask无效?
    Linux系统中,相同类型的信号只会在队列中保留一个实例。如果客户端快速连续发送多个SIGUSR1或SIGUSR2,后续的信号会被丢弃,导致服务端无法收到完整的位序列。同步反馈机制从根源上避免了这种情况,确保信号发送和处理严格一一对应。

内容的提问来源于stack exchange,提问作者murmur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 15:00:54