多线程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_tblocked

        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(&current->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);不能完成的任务是:

    1. 不知道信号产生的原因;
    2. 处理信号中不能阻塞其他的信号

    而signaction,则可以设置比较多的消息。尤其是在信号处理函数过程中接受信号,进行何种处理。

    sigaction函数用于改变进程接收到特定信号后的行为。

  • sigprocmask函数只能用于单线程(进程),在多线程中使用pthread_sigmask函数。

  • 信号是发给进程的特殊消息,其典型特性是具有异步性。

  • 信号集代表多个信号的集合,其类型是sigset_t。

  • 每个进程都有一个信号掩码(或称为信号屏蔽字),其中定义了当前进程要求阻塞的信号集。

  • 同时注册了信号处理函数,同时又用sigwait来等待这个信号,谁会取到信号?经过实验,Linux上sigwait的优先级高。

 

posted @ 2023-02-07 11:13  codestacklinuxer  阅读(30)  评论(0编辑  收藏  举报