免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 6520 | 回复: 9

[Linux] sigwait捕获了信号,但程序不往下走,仍在sigwait.为什么? [复制链接]

论坛徽章:
0
发表于 2013-07-02 10:03 |显示全部楼层
20可用积分
我写了个很小的测试程序,尝试学习sigwait的用法,在RHEL6.3+GCC4.4下运行,如下:

  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <pthread.h>
  5. #include <errno.h>
  6. #include <unistd.h>
  7. void sig_fun(int signo){
  8.     printf("received signal=%d\n",signo);
  9. }
  10. void *func(void*)
  11. {
  12.     printf("thread start\n");
  13.     sigset_t sigset;
  14.     sigemptyset(&sigset);
  15.     sigaddset(&sigset,SIGALRM);
  16.     pthread_sigmask(SIG_SETMASK,&sigset,NULL);
  17.     signal(SIGALRM,sig_fun);

  18.     int sigRet;
  19.     sigwait(&sigset,&sigRet);
  20.     printf("End of sub-thread\n");
  21.     return NULL;
  22. }
  23. int main(void)
  24. {
  25.     pthread_t tid;
  26.     pthread_create(&tid,NULL,func,NULL);
  27.     pthread_join(tid,NULL);
  28.     exit(0);
  29. }
复制代码
运行的结果是,程序启动以后,停在sigwait那里,我pgrep xxx|xargs kill -s SIGALRM的话,程序就打印
received signal=14

但是这个之后,线程函数并没有从sigwait返回往下执行printf,而是好像仍然停在sigwait那里,我如果继续发送信号的话,还是每次都能打印。
received signal=14

我期望的结果是sigwait捕获了以后,这个函数应该返回然后程序继续往下执行啊。我的理解有误吗?
还请指点一下哈!

最佳答案

查看完整内容

楼上贴的man里面的代码,解释的也比较到位,还有个问题就是linux多线程和信号处理这块本身就有问题,不同版本的内核和glibc版对于这样的行为需要一一测试,因为linux的线程库标准化的时间也不长。要想可靠的使用sigwait,应该在所有不需要处理指定信号的线程中block指定的信号(最安全的作法),因为信号本身是基于进程的,否则会出现不知道哪个线程处理相应信号的情况,而且posix标准也没有保证调用sigwait的线程可以接收到指定的 ...

论坛徽章:
17
处女座
日期:2013-08-27 09:59:352015亚冠之柏太阳神
日期:2015-07-30 10:16:402015亚冠之萨济拖拉机
日期:2015-07-29 18:58:182015年亚洲杯之巴勒斯坦
日期:2015-03-06 17:38:17摩羯座
日期:2014-12-11 21:31:34戌狗
日期:2014-07-20 20:57:32子鼠
日期:2014-05-15 16:25:21亥猪
日期:2014-02-11 17:32:05丑牛
日期:2014-01-20 15:45:51丑牛
日期:2013-10-22 11:12:56双子座
日期:2013-10-18 16:28:17白羊座
日期:2013-10-18 10:50:45
发表于 2013-07-02 10:03 |显示全部楼层
本帖最后由 myworkstation 于 2013-07-03 10:43 编辑

楼上贴的man里面的代码,解释的也比较到位,还有个问题就是linux多线程和信号处理这块本身就有问题,不同版本的内核和glibc版对于这样的行为需要一一测试,因为linux的线程库标准化的时间也不长。
要想可靠的使用sigwait,应该在所有不需要处理指定信号的线程中block指定的信号(最安全的作法),因为信号本身是基于进程的,否则会出现不知道哪个线程处理相应信号的情况,而且posix标准也没有保证调用sigwait的线程可以接收到指定的信号。在linux系统上默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的, 在Linux中的posix线程模型中,线程拥有独立的进程号,可以通过getpid()得到线程的进程号,而线程号保存在pthread_t的值中。而主线程的进程号就是整个进程的进程号,因此向主进程发送信号只会将信号发送到主线程中去。如果主线程设置了信号屏蔽,则信号会投递到一个可以处理的线程中去(这解释了man中的示例程序行为)。以下一段文字解释了linux的实现造成了楼主所遇到的问题,关键是最后一句话(another  thread  is  blocked  in !sigwait! on thatsignal, it will not be restarted),楼主遇到的问题
实际上应该是调用signal发送的信号由进程的主线程进行了处理,然后sigwait所在的线程永远不会restart。

Signal handling in LinuxThreads departs significantly  from  the  POSIX
       standard.   According  to  the  standard,  ‘‘asynchronous’’  (external)
       signals are addressed to the  whole  process  (the  collection  of  all
       threads), which then delivers them to one particular thread. The thread
       that actually receives the signal is any thread that does not currently
       block the signal.

       In  LinuxThreads, each thread is actually a kernel process with its own
       PID, so external signals are always directed to one particular  thread.
       If,  for  instance,  another  thread  is  blocked  in !sigwait! on that
       signal, it will not be restarted.

论坛徽章:
4
白羊座
日期:2013-09-17 21:59:30技术图书徽章
日期:2013-10-12 22:16:03白羊座
日期:2013-10-14 11:01:40双子座
日期:2013-12-17 18:26:39
发表于 2013-07-02 10:58 |显示全部楼层
回复 1# ejeker
man -s 7 signal:
  1. Each thread in a process has an independent signal mask, which indicates the set of signals that the thread is currently blocking.  A thread can manipulate its signal mask using pthread_sigmask(3).
复制代码
  1. A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked.  If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread  to  which  to deliver the signal.
复制代码
最好在创建线程前调用signal(man -s 3p signal):
  1. Use of this function is unspecified in a multi-threaded process.
复制代码

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
发表于 2013-07-02 13:50 |显示全部楼层
信号处理函数是进程级的, 但你的主线程没有屏蔽SIGALRM信号, 所以信号将被投递到主线程或者子线程, 这里因为操作系统实现总是被投递给了主线程.

你应该在主线程里屏蔽掉该信号, 那样就可以保证信号总是给投递给子线程被sigwait处理.

论坛徽章:
11
技术图书徽章
日期:2014-03-01 14:44:34天蝎座
日期:2014-05-21 22:11:59金牛座
日期:2014-05-30 17:06:14
发表于 2013-07-02 14:54 |显示全部楼层
虽然是学习,严谨一些更好,以免在生产环境出事,LZ例子问题太多,请容我逐一列举,随之问题的原因和解决办法就很显然了:
1. 用严格的编译选项。我是CentOS 6.4,和你的环境是非常相似,用gcc的"-Wall"有警告。

2. 对于某个函数的用法,先看man再用。你的例子中,pthread_sigmask和sigwait非常关键,恰好man为pthread_sigmask提供了一个例子,很好的说明了sigwait的用法精髓。

3. sigwait的真正意义。信号和多线程一直是UNIX/LINUX下的大坑所在,两者合为一处更是坑中之坑,一定要遵循被总结出的良好实践。sigwait和pthead_sigmask相互配合,可以让异步的信号处理转化为单独一个线程的同步行为,大为简化实现,比如传统信号处理函数中必须使用可重入函数,但LZ所用的printf却不可重入,不过正确使用sigwait后,就可以不管这类烦恼的问题。

4. LZ例子不符合sigwait基本用法(如创建子线程前,先将待处理的信号block),先按照下面man提供的例子修改下代码,这就是多线程信号的推荐处理逻辑。
  1.       #include <pthread.h>
  2.        #include <stdio.h>
  3.        #include <stdlib.h>
  4.        #include <unistd.h>
  5.        #include <signal.h>
  6.        #include <errno.h>

  7.        /* Simple error handling functions */

  8.        #define handle_error_en(en, msg) \
  9.                do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

  10.        static void *
  11.        sig_thread(void *arg)
  12.        {
  13.            sigset_t *set = (sigset_t *) arg;
  14.            int s, sig;

  15.            for (;;) {
  16.                s = sigwait(set, &sig);
  17.                if (s != 0)
  18.                    handle_error_en(s, "sigwait");
  19.                printf("Signal handling thread got signal %d\n", sig);
  20.            }
  21.        }


  22.        int
  23.        main(int argc, char *argv[])
  24.        {
  25.            pthread_t thread;
  26.            sigset_t set;
  27.            int s;

  28.            /* Block SIGINT; other threads created by main() will inherit
  29.               a copy of the signal mask. */

  30.            sigemptyset(&set);
  31.            sigaddset(&set, SIGQUIT);
  32.            sigaddset(&set, SIGUSR1);
  33.            s = pthread_sigmask(SIG_BLOCK, &set, NULL);
  34.            if (s != 0)
  35.                handle_error_en(s, "pthread_sigmask");

  36.            s = pthread_create(&thread, NULL, &sig_thread, (void *) &set);
  37.            if (s != 0)
  38.                handle_error_en(s, "pthread_create");

  39.            /* Main thread carries on to create other threads and/or do
  40.               other work */

  41.            pause();            /* Dummy pause so we can test program */
  42.        }
复制代码

论坛徽章:
7
天蝎座
日期:2013-09-28 10:45:42双子座
日期:2013-10-16 16:27:09射手座
日期:2013-10-23 10:21:32处女座
日期:2014-09-17 16:44:332015年亚洲杯之巴林
日期:2015-04-09 17:28:01冥斗士
日期:2015-11-26 16:19:0015-16赛季CBA联赛之山东
日期:2018-03-02 23:59:31
发表于 2013-07-03 00:46 |显示全部楼层
pthread_sigmask(SIG_SETMASK,&sigset,NULL);                --  我怎么感觉你这里有问题: 在线程里面屏蔽了alarm信号。
signal(SIGALRM,sig_fun);                                                --   在线程里面注册了sigalrm信号处理函数,根据apue第二版,12.8线程和信号:...这意味着尽管单个线程可以阻止某些信号(你上面一句的行为就是在单个线程内阻止了alarm信号), 但当线程修改了与某个信号相关的处理行为后,所有的线程都必须共享这个处理行为的改变(你这里修改了对alarm的处理行为,导致该线程,和主线程都注册了alarm信号的sig_fun函数)。

int sigRet;

sigwait(&sigset,&sigRet);                                                -- 这里在线程内部试图等待alarm信号发生,但实际上,alarm信号在你这个线程内被阻塞了,是不会被送达到你这个线程的。


因此你给程序发送alarm,只有主线程能够收到,并且调用sig_fun函数处理。而func线程由于屏蔽了alarm,则无法收到alarm信号。

论坛徽章:
17
处女座
日期:2013-08-27 09:59:352015亚冠之柏太阳神
日期:2015-07-30 10:16:402015亚冠之萨济拖拉机
日期:2015-07-29 18:58:182015年亚洲杯之巴勒斯坦
日期:2015-03-06 17:38:17摩羯座
日期:2014-12-11 21:31:34戌狗
日期:2014-07-20 20:57:32子鼠
日期:2014-05-15 16:25:21亥猪
日期:2014-02-11 17:32:05丑牛
日期:2014-01-20 15:45:51丑牛
日期:2013-10-22 11:12:56双子座
日期:2013-10-18 16:28:17白羊座
日期:2013-10-18 10:50:45
发表于 2013-07-03 10:50 |显示全部楼层
cxytz01 发表于 2013-07-03 00:46
pthread_sigmask(SIG_SETMASK,&sigset,NULL);                --  我怎么感觉你这里有问题: 在线程里面屏蔽 ...


你说的对,在需要处理指定信号的线程中block了需要处理的信号是有问题。但是哪怕在这个线程中不block这个信号也不能保证这个线程可以处理到这个信号,必须在其它线程中(针对楼主的代码而言是主线程)block这个信号才行。
如果其它线程没有block指定号,而调用sigwait的线程调用情况正常的话,也是不可移植的。没有标准规定这个行为,(系统在实现这种问题的情况下有选择的权利,可以自主选择一个线程进行处理)。

论坛徽章:
0
发表于 2013-07-03 12:20 |显示全部楼层
#include <langinfo.h>
确实是。我把代码改成下面这样就OK了,达到了我的预期: 主线程屏蔽了信号,信号被子线程接收

  1. #include <signal.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <pthread.h>
  5. #include <errno.h>
  6. #include <unistd.h>
  7. void sig_fun(int signo){
  8.     printf("received signal=%d\n",signo);
  9. }
  10. void *func(void*)
  11. {
  12.     printf("thread start\n");
  13.     sigset_t sigset;
  14.     sigemptyset(&sigset);
  15.     sigaddset(&sigset,SIGALRM);

  16.     int sigRet;
  17.     sigwait(&sigset,&sigRet);
  18.     printf("End of sub-thread\n");
  19.     return NULL;
  20. }
  21. int main(void)
  22. {
  23.     sigset_t sigset;
  24.     sigemptyset(&sigset);
  25.     sigaddset(&sigset,SIGALRM);
  26.     pthread_sigmask(SIG_SETMASK,&sigset,NULL);

  27.     pthread_t tid;
  28.     pthread_create(&tid,NULL,func,NULL);
  29.     pthread_join(tid,NULL);
  30.     exit(0);
  31. }
复制代码

论坛徽章:
7
天蝎座
日期:2013-09-28 10:45:42双子座
日期:2013-10-16 16:27:09射手座
日期:2013-10-23 10:21:32处女座
日期:2014-09-17 16:44:332015年亚洲杯之巴林
日期:2015-04-09 17:28:01冥斗士
日期:2015-11-26 16:19:0015-16赛季CBA联赛之山东
日期:2018-03-02 23:59:31
发表于 2013-07-10 22:44 |显示全部楼层
本帖最后由 cxytz01 于 2013-07-10 22:59 编辑

回复 6# cxytz01

我的回答错了,纠正下。

sigprocmask是针对进程的,其行为在多线程的进程中没有定义,pthread_sigmask是针对线程信号屏蔽字操作函数。

另外信号集是会被继承到子线程的,但是子线程的信号集的变更不会影响到其他线程,包含父线程。

sigwait会自动取消信号集的阻塞状态,直到有新的信号被递送。之后sigwait将恢复信号屏蔽字。   --这里apue我看得不是很明白,但是实验证明sigwait是可以收到被阻塞的信号的。

另外:在任何线程中注册信号行为,signal,sigaction,都会影响其他线程。

在这里,给出我的答案,楼主你的代码是没有问题的!
原因如下:
    1.你在子线程中注册sigalrm信号的动作,导致父子线程同时注册了sigalrm信号的行为。
    2.你运行的pgrep xxx|xargs kill -s SIGALRM, pgrep a.out获取的是父进程的pid, kill -s SIGALRM 父进程的pid,将导致信号丢入进程的处理队列中(这里我用了队列,假设信号是排队的)。
    3.发送到一个pid的信号,将由pid所代表的进程优先处理,处理完毕,自然就将信号从队列中丢掉(假设会丢掉)。  因此你pgrep xxx|xargs kill -s SIGALRM将导致信号发送给父线程(进程),父线程优先处理该信号,然后将信号丢了。子线程就收不到信号了。
    4.假如你直接将该信号发送给子线程,那么将由子线程处理该信号,而不是由父线程处理。
      1) 在你的main,和线程函数里面加上printf("tid = %d\n", syscall(SYS_gettid)); 来打印线程的tid,然后直接kill -14 子线程的tid,你将会得到你想要的结果。
      2) 或者pstree -p pid,会显示出进程树,从里面获得子线程的pid,kill -14 子线程pid,你也会得到你想要的结果
    5.还可以在main里面把alarm信号屏蔽了,然后pgrep xxx|xargs kill -s SIGALRM,也会得到你想要的结果。父线程阻塞该信号,信号会被递送到子线程内,由子线程来处理。

结合@myworkstation@井蛙夏虫@linux_c_py_php的答案:
01.A process-directed signal may be delivered to any one of the threads that does not currently have the signal blocked.  If more than one of the threads has the signal unblocked, then the kernel chooses an arbitrary thread  to  which  to deliver the signal.  -- 信号会被递送至任意没有阻塞该信号的线程内。

就目前的情况而言,是被操作系统实现为优先递送给父线程(假如父线程没有阻塞该信号)。
   

论坛徽章:
7
天蝎座
日期:2013-09-28 10:45:42双子座
日期:2013-10-16 16:27:09射手座
日期:2013-10-23 10:21:32处女座
日期:2014-09-17 16:44:332015年亚洲杯之巴林
日期:2015-04-09 17:28:01冥斗士
日期:2015-11-26 16:19:0015-16赛季CBA联赛之山东
日期:2018-03-02 23:59:31
发表于 2013-07-10 22:50 |显示全部楼层
ejeker 发表于 2013-07-03 12:20
#include
确实是。我把代码改成下面这样就OK了,达到了我的预期: 主线程屏蔽了信号,信号被子线程接收


你修改的之后的代码,同我上面说的第5点一样,在父线程里面也屏蔽了sigalrm信号(别忘了,子线程会继承父线程的信号屏蔽字)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP