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); }
关键修改说明
客户端:添加反馈等待逻辑
- 新增全局变量
g_ack标记是否收到服务端反馈 - 实现客户端信号处理函数,收到
SIGUSR1时标记可以继续发送,收到SIGUSR2时退出 send_bit函数发送信号后,通过pause()阻塞等待服务端反馈,确保每一位都被正确处理后再发下一位
- 新增全局变量
服务端:调整位处理逻辑+每位反馈
- 修正原代码的位拼接顺序:原逻辑先赋值再左移会导致位顺序颠倒,改为先左移再赋值
- 每处理完一位就立即给客户端发送
SIGUSR1反馈,允许客户端发送下一位 - 收到结束符
\0时发送SIGUSR2通知客户端传输完成
为什么原
sa_mask无效?
Linux系统中,相同类型的信号只会在队列中保留一个实例。如果客户端快速连续发送多个SIGUSR1或SIGUSR2,后续的信号会被丢弃,导致服务端无法收到完整的位序列。同步反馈机制从根源上避免了这种情况,确保信号发送和处理严格一一对应。
内容的提问来源于stack exchange,提问作者murmur
相关产品推荐
相关产品推荐

