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

基于信号的C语言IPC程序稳定性问题求助

问题

用C语言实现基于SIGUSR1/SIGUSR2的进程间通信时遇到稳定性问题:

  1. 编译运行server.c可正常打印PID;
  2. 编译运行client.c传入服务端PID及100字符字符串时,使用usleep(3000)能得到预期输出,但usleep(100)会出现未定义行为(如字符错乱、丢失)。

server.c 代码

void    handle_signal(int signo, siginfo_t *info, void *context)
{
        static unsigned char     ch = 0;
        static int               idx = 0;
    
        (void)context;
        if (signo == SIGUSR1)
            ch <<= 1;   
        else if (signo == SIGUSR2)
            ch = (ch << 1) | 1;
        idx++;
        if (idx == 8)
        {
            kill(info->si_pid, SIGUSR1);
            write(1, &ch, 1);
            idx = 0;
        }
}
int main(){
        struct sigaction sa;
    
        printf("Server PID: %d\n", getpid());
    
        sa.sa_handler = (void (*)(int))handle_signal;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_SIGINFO;
    
        sigaction(SIGUSR1, &sa, NULL);
        sigaction(SIGUSR2, &sa, NULL);
    
        while (1) {
            pause();
        }
        return 0;
}

client.c 代码

void    handle_input(int signo){
    static int  number = 0;

    if (signo == SIGUSR1)
        printf("Received letter %d!\n", ++number);
    else
        number = 0;
}

void    handle_output(char **input, pid_t pid){
    int             i = 2;
    int             j = 0;
    int             bits = 8;
    unsigned char   send;

    while (input[i])
    {
        while (input[i][j] != '\0')
        {
            while (bits-- > 0)
            {
                send = input[i][j] >> bits & 1;
                if (send == 0)
                    kill(pid, SIGUSR1);
                if(send == 1)
                    kill(pid, SIGUSR2);
                usleep(100);
            }
            bits = 8;
            j++;
        }
        j = 0;
        i++;
    }
}

int main(int argc, char **argv){
    char                *ptr;
    struct sigaction    sa;
    
    if (argc < 3 || !argv[2][0])
        return (printf("Not enough values!\n"));
    ptr = argv[1];
    while (isdigit(*ptr))
        ptr++;
    if (*ptr != '\0')
        return (printf("Wrong pid format!\n"));
    
    sa.sa_handler = handle_input;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_RESTART;

    sigaction(SIGUSR1, &sa, NULL);

    handle_output(argv, (pid_t)atoi(argv[1]));

    return (0);
}
解决方案

核心问题是信号的不可靠性:内核不会对相同信号排队,当发送速度过快时,后续信号会被丢弃或合并,导致服务端接收的比特序列错乱。同时原代码存在信号处理函数类型不匹配、无同步机制等问题,需补充以下控制逻辑:

1. 实现字节级ACK确认机制

客户端发送完一个字节的8个比特后,必须等待服务端的确认信号(SIGUSR1),再发送下一个字节,彻底替代固定延时的不可靠同步:

  • 在client.c中添加全局volatile变量标记ACK状态(防止编译器优化):
    volatile int ack_received = 0;
    
  • 修改客户端信号处理函数,收到ACK时设置标记:
    void handle_input(int signo){
        static int number = 0;
        if (signo == SIGUSR1) {
            ack_received = 1;
            printf("Received letter %d!\n", ++number);
        } else {
            number = 0;
        }
    }
    
  • 修改handle_output,发送完一个字节后阻塞等待ACK:
    void handle_output(char **input, pid_t pid){
        int             i = 2;
        int             j = 0;
        int             bits;
        unsigned char   send;
    
        while (input[i])
        {
            while (input[i][j] != '\0')
            {
                bits = 8;
                while (bits-- > 0)
                {
                    send = input[i][j] >> bits & 1;
                    // 检查kill调用是否成功
                    if (send == 0) {
                        if (kill(pid, SIGUSR1) == -1) {
                            perror("kill SIGUSR1 failed");
                            exit(EXIT_FAILURE);
                        }
                    } else {
                        if (kill(pid, SIGUSR2) == -1) {
                            perror("kill SIGUSR2 failed");
                            exit(EXIT_FAILURE);
                        }
                    }
                }
                // 等待服务端ACK
                ack_received = 0;
                while (!ack_received) {
                    pause();
                }
                j++;
            }
            j = 0;
            i++;
        }
    }
    

2. 修正服务端信号处理函数的类型匹配

原服务端错误地将sa_handler指向带3个参数的处理函数(设置了SA_SIGINFO标志时,应使用sa_sigaction),导致参数调用混乱,修正如下:

int main(){
    struct sigaction sa;

    printf("Server PID: %d\n", getpid());

    // 替换sa_handler为sa_sigaction,匹配SA_SIGINFO的函数签名
    sa.sa_sigaction = handle_signal;
    sigemptyset(&sa.sa_mask);
    // 处理信号时阻塞SIGUSR1/SIGUSR2,避免并发修改静态变量
    sigaddset(&sa.sa_mask, SIGUSR1);
    sigaddset(&sa.sa_mask, SIGUSR2);
    sa.sa_flags = SA_SIGINFO;

    sigaction(SIGUSR1, &sa, NULL);
    sigaction(SIGUSR2, &sa, NULL);

    while (1) {
        pause();
    }
    return 0;
}

3. 阻塞信号避免并发冲突

在服务端的信号掩码中添加SIGUSR1和SIGUSR2,确保处理一个信号时不会被另一个信号打断,防止静态变量ch和idx被并发修改(已在上一步的代码中实现)。

4. 添加信号发送错误检查

客户端调用kill时检查返回值,确保信号成功发送到服务端,避免因PID无效或权限问题导致的静默失败(已在handle_output的修改中实现)。

经过以上修改,程序不再依赖固定延时,通过ACK机制实现可靠的信号同步,可在任意输入长度下稳定运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 05:22:12