多线程sigpipe
之前记录过socket读写异常相关情况,socket 链接错误以及原因
以及信号相关处理:多线程信号处理
目前调试引擎时候出现了 错误;signal SIGPIPE, Broken pipe
一开始以为是没有忽略sigpipe信号,立即在代码里面加上如下代码,
signal(SIGPIPE, SIG_IGN);
测试结果还是出现了received signal SIGPIPE, Broken pipe
错误,进程退出。
仔细分析了一下,才想起来,多线程对于信号的处理有所不同,也就是说这个bug一直存在这个产品很长时间了。
也就是多线程中对sigpipe信号的处理,解决方法是每一个线程启动之前时,先执行下面代码:
#ifndef WIN32 sigset_t signal_mask; sigemptyset (&signal_mask); sigaddset (&signal_mask, SIGPIPE); int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL); if (rc != 0) { printf("block sigpipe error\n"); } #endif
pthread_sigmask 设置block 信号mask的时候, 是赋值类型为sigset_t 的blocked
new_blocked = current->blocked; switch (how) { case SIG_BLOCK: sigaddsetmask(&new_blocked, new_set);
当创建线程/进程的时候, 所有的线程进程都会copy继承一份。
问题场景还原分析:
对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.
具体的分析可以结合TCP的”四次握手”关闭. TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端已经完全关闭.
对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
问题:为什么signal(SIGPIPE, SIG_IGN);将信号转为sig_ign后还是会出现 epipe错误呢?
首先:创建thread线程的时候, 只会共享shared_pending 队列,每个线程会初始化 init_sigpending(&p->pending)信号私有队列,
(1)在操作系统内核”struct task_struct“结构体内部有一个变量"struct sigpending pending;"
(2)内核定义的结构体"struct sigpending"当中有两个变量:一个是内核定义的双向链表;一个是:”sigset_t signal“
(3)内核定义的类型”sigset_t“为一个结构体,在结构体内部有一个变量,该变量为一个数组(无符号长整型的数组)
struct sigpending { struct list_head list; sigset_t signal; };
同时每一个sigal都对应一个 action[signal]
当选择action 为sig_ign的时候其sa_handler为SIG_IGN,但是线程的sighand 共享。
if (clone_flags & CLONE_SIGHAND) { atomic_inc(¤t->sighand->count); return 0; }
先注册信号处理函数为sig_ign,但是创建线程后,每个线程的私有pending被初始化,只是公共shared_penging还在。
int do_sigaction(int sig, struct k_sigaction *act, struct k_sigaction *oact) { struct task_struct *p = current, *t; struct k_sigaction *k; sigset_t mask; if (!valid_signal(sig) || sig < 1 || (act && sig_kernel_only(sig))) return -EINVAL; k = &p->sighand->action[sig-1]; spin_lock_irq(&p->sighand->siglock); if (oact) *oact = *k; if (act) { sigdelsetmask(&act->sa.sa_mask, sigmask(SIGKILL) | sigmask(SIGSTOP)); *k = *act; /* * POSIX 3.3.1.3: * "Setting a signal action to SIG_IGN for a signal that is * pending shall cause the pending signal to be discarded, * whether or not it is blocked." * * "Setting a signal action to SIG_DFL for a signal that is * pending and whose default action is to ignore the signal * (for example, SIGCHLD), shall cause the pending signal to * be discarded, whether or not it is blocked" */ if (sig_handler_ignored(sig_handler(p, sig), sig)) { sigemptyset(&mask); sigaddset(&mask, sig); flush_sigqueue_mask(&mask, &p->signal->shared_pending); for_each_thread(p, t) flush_sigqueue_mask(&mask, &t->pending); } } spin_unlock_irq(&p->sighand->siglock); return 0; }
如果设置为sig_ign,当前进程线程组会删除对应的信号集mask以及信号队列中已经生成的信号
ps:统中所有的进程都组织在init_task的tasks链表下面,每个进程的线程组织在每个进程task_sturct->signal的链表下
这样看代码,每个线程会执行对应sig_ign互联动作函数啊???
怎么不生效呢??为什么必须给线程设置信号屏蔽集呢?
查阅文档:
Initially I tried signal(SIGPIPE,SIG_IGN)
, but signal(2) man says:
The effects of signal() in a multithreaded process are unspecified.
为什么是这样还是没搞清楚!!!!
PS:如果需要用相同的方式处理信号的多次出现,建议使用sigaction函数;若可以保证信号长时间内只出现并只需要处理一次,则可以使用signal函数;
signal函数,只能生效一次;sigaction函数设置后一直有效
目前自己使用多线程demo测试, 不使用屏蔽信号的方式, 设置多线程sigpipe默认动作为sig_ign, 测试的时候是正常的,没有出现sigpipe错误!!!这尼玛。。。。。。风中凌乱了,不知道
The effects of signal() in a multithreaded process are unspecified.
不知道这个unspecified到底是针对那个版本的结论
所以 不同内核版本不同版本的库 显示的结论完全不一样
总结一下:
-
Linux 多线程应用中,每个线程可以通过调用pthread_sigmask() 设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如SIGSEGV;另外不能被忽略处理的信号SIGKILL 和SIGSTOP 也无法被阻塞。
-
当一个线程调用pthread_create() 创建新的线程时,此线程的信号掩码会被新创建的线程继承。
-
信号安装最好采用sigaction方式,sigaction,是为替代signal 来设计的较稳定的信号处理,signal的使用比较简单。signal(signalNO,signalproc);不能完成的任务是:
- 不知道信号产生的原因;
- 处理信号中不能阻塞其他的信号
而signaction,则可以设置比较多的消息。尤其是在信号处理函数过程中接受信号,进行何种处理。
sigaction函数用于改变进程接收到特定信号后的行为。
-
sigprocmask函数只能用于单线程(进程),在多线程中使用pthread_sigmask函数。
-
信号是发给进程的特殊消息,其典型特性是具有异步性。
-
信号集代表多个信号的集合,其类型是sigset_t。
-
每个进程都有一个信号掩码(或称为信号屏蔽字),其中定义了当前进程要求阻塞的信号集。
- 同时注册了信号处理函数,同时又用sigwait来等待这个信号,谁会取到信号?经过实验,Linux上sigwait的优先级高。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2021-02-07 IPV6 tproxy代理问题