pthread访问调用信号线程的掩码(pthread_sigmask )

掩码:

信号掩码 在POSIX下,每个进程有一个信号掩码(signal mask)。简单地说,信号掩码是一个"位图",其中每一位都对应着一种信号。如果位图中的某一位为1,就表示在执行当前信号的处理程序期间相应的信号暂时被"屏蔽",使得在执行的过程中不会嵌套地响应那种信号。
为什么对某一信号进行屏蔽呢?我们来看一下对CTRL_C的处理。大家知道,当一个程序正在运行时,在键盘上按一下CTRL_C,内核就会向相应的进程发出一个SIGINT 信号,而对这个信号的默认操作就是通过do_exit()结束该进程的运行。但是,有些应用程序可能对CTRL_C有自己的处理,所以就要为SIGINT另行设置一个处理程序,使它指向应用程序中的一个函数,在那个函数中对CTRL_C这个事件作出响应。但是,在实践中却发现,两次CTRL_C事件往往过于密集,有时候刚刚进入第一个信号的处理程序,第二个SIGINT信号就到达了,而第二个信号的默认操作是杀死进程,这样,第一个信号的处理程序根本没有执行完。为了避免这种情况的出现,就在执行一个信号处理程序的过程中将该种信号自动屏蔽掉。所谓"屏蔽",与将信号忽略是不同的,它只是将信号暂时"遮盖"一下,一旦屏蔽去掉,已到达的信号又继续得到处理。

一:设置信号掩码

每个线程都有自己的信号掩码,用pthread_sigmsk()可以来修改信号掩码,当一个线程被创建时,他继承了创造它的线程的信号掩码(如果你想在所有地方屏蔽一个信号,首先在主线程中屏蔽它)

pthread_sigmask

函数原型:

int pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
#include <pthread.h>

#include <signal.h>
int ret;

sigset_t old, new;
ret = pthread_sigmask(SIG_SETMASK, &new, &old); /* set new mask */
ret = pthread_sigmask(SIG_BLOCK, &new, &old); /* blocking mask */
ret = pthread_sigmask(SIG_UNBLOCK, &new, &old); /* unblocking */

how 用来确定如何更改信号组。how 可以为以下值之一:

  • SIG_BLOCK。结果集是当前集合参数集的并集,向当前的信号掩码中添加 new,其中 new 表示要阻塞的信号组。

  • SIG_UNBLOCK。结果集是当前集合参数集的差集,从当前的信号掩码中删除 new,其中 new 表示要取消阻塞的信号组。

  • SIG_SETMASK。结果集是由参数集指向的集,将当前的信号掩码替换为 new,其中 new 表示新的信号掩码。

当 new 的值为 NULL 时,how 的值没有意义,线程的信号掩码不发生变化。要查询当前已阻塞的信号,请将 NULL 值赋给 new 参数。

除非 old 变量为 NULL,否则 old 指向用来存储以前的信号掩码的空间。

pthread_sigmask() 在成功完成之后返回零。其他任何返回值都表示出现了错误。如果出现以下情况,pthread_sigmask() 将失败并返回相应的值。

EINVAL

描述:

未定义 how 的值。

 Linux内核中有一个专门的函数集合来执行设置和修改信号掩码,它们放在kernel/signal.c中,其函数形式和功能如下:

int sigemptyset(sigset_t *mask)                  清所有信号掩码的阻塞标志
int sigfillset(sigset_t *mask, int signum)       设置所有信号掩码的阻塞标志  
int sigdelset(sigset_t *mask, int signum)        删除个别信号阻塞
int sigaddset(sigset_t *mask, int signum)        增加个别信号阻塞
int sigisnumber(sigset_t *mask, int signum)      确定特定的信号是否在掩码中被标志为阻塞。

    另外,进程也可以利用sigprocmask()(sigprocmask函数只能用于单线程,在多线程中使用pthread_sigmask函数。)系统调用改变和检查自己的信号掩码的值,其实现代码在kernel/signal.c中,原型为:

    int sys_sigprocmask(int how, sigset_t *set, sigset_t *oset)

pthread_sigmask使用

sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask,SIGQUIT);
pthread_sigmask(SIG_BLOCK,&mask,NULL);

二:收到信号信号时所做的动作

sigaction函数的功能是检查或修改与指定信号相关联的处理动作(可同时两种操作)。

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

参数说明: 
signum : 要操作的信号 
act : 要设置的对信号的新处理方式 
oldact : 原来对信号的处理方式 
返回值: 成功返回0,失败返回-1,并设置errno

struct sigaction 结构体原型:

struct sigaction 
{
    void     (*sa_handler)(int);  //老类型的信号处理函数指针,与 single 函数一样
    void     (*sa_sigaction)(int, siginfo_t *, void *); //新类型的信号处理函数指针
    sigset_t   sa_mask;  //将要被阻塞的信号集合
    int        sa_flags;  //信号处理方式
    void     (*sa_restorer)(void); //保留
};

sa_handler : 老类型的信号处理函数指针,与 single 函数一样

sa_sigaction 函数指针的三个参数含义为:
int iSignNum : 传入的信号
siginfo_t *pSignInfo : 该信号相关的一些信息,他是一个结构体
原型如下
siginfo_t {
    int      si_signo;    /* 信号值,对所有信号有意义 */
    int      si_errno;    /* errno 值,对所有信号有意义 */
    int      si_code;     /* 信号产生的原因,对所有信号有意义 */
    int      si_trapno;   /* Trap number that caused
                             hardware-generated signal
                             (unused on most architectures) */
    pid_t    si_pid;      /* 发送信号的进程ID */
    uid_t    si_uid;      /* 发送信号进程的真实用户ID */
    int      si_status;   /* 对出状态,对SIGCHLD 有意义 */
    clock_t  si_utime;    /* 用户消耗的时间,对SIGCHLD有意义 */
    clock_t  si_stime;    /* 内核消耗的时间,对SIGCHLD有意义 */
    sigval_t si_value;    /* 信号值,对所有实时有意义,是一个联合数据结构,可以为一个整数(由si_int标示,也可以为一个指针,由si_ptr标示) */
    int      si_int;      /* POSIX.1b signal */
    void    *si_ptr;      /* POSIX.1b signal */
    int      si_overrun;  /* Timer overrun count; POSIX.1b timers */
    int      si_timerid;  /* Timer ID; POSIX.1b timers */
    void    *si_addr;     /* 触发fault的内存地址,对SIGILL,SIGFPE,SIGSEGV,SIGBUS 信号有意义 */
    long     si_band;     /* 对SIGPOLL信号有意义 */
    int      si_fd;       /* 对SIGPOLL信号有意义 */
    short    si_addr_lsb; /* Least significant bit of address
                             (since kernel 2.6.32) */
}

void *pReserved : 网上找的资料说明都是说(保留,占时无用)
sa_mask : 是一个包含信号集合的结构体,该结构体内的信号表示在进行信号处理时,将要被阻塞的信号。
sa_flags  是一组掩码的合成值,指示信号处理时所应该采取的一些行为,各掩码的含义为:
SA_RESETHAND : 处理完毕要捕捉的信号后,将自动撤消信号处理函数的注册,即必须再重新注册信号处理函数,才能继续处理接下来产生的信号。
SA_NODEFER : 在处理信号时,如果又发生了其它的信号,则立即进入其它信号的处理,等其它信号处理完毕后,再继续处理当前的信号,即递规地处理。如果sa_flags包含了该掩码,则结构体sigaction的sa_mask将无效;
SA_RESTART : 如果在发生信号时,程序正阻塞在某个系统调用,例如调用read()函数,则在处理完毕信号后,接着从阻塞的系统返回。该掩码符合普通的程序处理流程,所以一般来说,应该设置该掩码,否则信号处理完后,阻塞的系统调用将会返回失败;
SA_SIGINFO : 指示结构体的信号处理函数指针是哪个有效,如果sa_flags包含该掩码,则sa_sigactiion指针有效

sigaction的使用

struct sigaction act;//set struct sigaction
memset(&act,0,sizeof(act));//cleanup memory
sigaddset(&act,&sa_mask,SIGQUIT);//let SIGQUIT signal add to sa_mask
act.sa_handler=sig_handler1;//signal handler function
sigaction(SIGQUIT,&act,NULL);//signal register

注意使用结构体之前首先要对其进行清空处理!!!

此时就完成了对信号处理函数的注册,就差别人来发送信号,sigaction函数来捕获。当捕获到外来信号时候,就交给信号处理函数去处理信号(信号处理函数又在struct sigaction act的结构体中,而该结构体中又是一个联合体,所以调用sigaction函数时候,以最后一次调用为主,发送几次信号,就调用几次信号处理函数)。

实例:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
void sig_handlerl1(int arg) { printf("this is sig_handler 1\n"); } void sig_handlerl2(int arg) { printf("this is sig_handler 2\n"); } void *thread1(void *arg) { printf("this is thread 1\n"); struct sigaction act; sigset_t sa_mask; memset(&act,0,sizeof(act)); sigaddset(&sa_mask,SIGQUIT); act.sa_handler=sig_handlerl1; sigaction(SIGQUIT,&act,NULL); sigset_t mask; sigemptyset(&mask); sigaddset(&mask,SIGQUIT); pthread_sigmask(SIG_BLOCK,&mask,NULL); sleep(6); } void *thread2(void *arg) { printf("this is thread 2\n"); struct sigaction act; sigset_t sa_mask; memset(&act,0,sizeof(act)); sigaddset(&sa_mask,SIGQUIT); act.sa_handler=sig_handlerl2; sigaction(SIGQUIT,&act,NULL); sigset_t mask; sigemptyset(&mask); sigaddset(&mask,SIGQUIT); pthread_sigmask(SIG_BLOCK,&mask,NULL); sleep(3); } int main() { pthread_t pid1,pid2; pthread_create(&pid1,NULL,thread1,NULL); pthread_create(&pid2,NULL,thread2,NULL); sleep(1); pthread_kill(pid1,SIGQUIT); pthread_kill(pid2,SIGQUIT); pthread_join(pid1,NULL); pthread_join(pid2,NULL); return 0; }

 

总结:

linux 多线程信号总结(一)

1. 在多线程环境下,产生的信号是传递给整个进程的,一般而言,所有线程都有机会收到这个信号,进程在收到信号的的线程上下文执行信号处理函数,具体是哪个线程执行的难以获知。也就是说,信号会随机发个该进程的一个线程。

2 signal函数BSD/Linux的实现并不在信号处理函数调用时,恢复信号的处理为默认,而是在信号处理时阻塞此信号,直到信号处理函数返回。其他实现可能在调用信号处理函数时,恢复信号的处理为默认方式,因而需要在信号处理函数中重建信号处理函数为我们定义的处理函数,在这些系统中,较好的方法是使用sigaction来建立信号处理函数

3 发送信号给进程,哪个线程会收到?APUE说,在多线程的程序中,如果不做特殊的信号阻塞处理,当发送信号给进程时,由系统选择一个线程来处理这个信号。

4 如果进程中,有的线程可以屏蔽了某个信号,而某些线程可以处理这个信号,则当我们发送这个信号给进程或者进程中不能处理这个信号的线程时,系统会将这个信号投递到进程号最小的那个可以处理这个信号的线程中去处理

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

6 在Linux中的posix线程模型中,线程拥有独立的进程号,可以通过getpid()得到线程的进程号,而线程号保存在pthread_t的值中。而主线程的进程号就是整个进程的进程号,因此向主进程发送信号只会将信号发送到主线程中去。如果主线程设置了信号屏蔽,则信号会投递到一个可以处理的线程中去。

7 当调用SYSTEM函数去执行SHELL命令时,可以放心的阻塞SIGCHLD,因为SYSTEM会自己处理子进程终止的问题。

8 使用sleep()时,要以放心的去阻塞SIGALRM信号,目前sleep函数都不会依赖于ALRM函数的SIGALRM信号来工作。

 

linux 多线程信号总结(二)

1. 默认情况下,信号将由主进程接收处理,就算信号处理函数是由子线程注册的

2. 每个线程均有自己的信号屏蔽字,可以使用sigprocmask函数来屏蔽某个线程对该信号的响应处理,仅留下需要处理该信号的线程来处理指定的信号。

3. 对某个信号处理函数,以程序执行时最后一次注册的处理函数为准,即在所有的线程里,同一个信号在任何线程里对该信号的处理一定相同

4. 可以使用pthread_kill对指定的线程发送信号

APUE的说法:每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有的线程共享的,

这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的处理行为后,所

有的线程都共享这个处理行为的改变。这样如果一个线程选择忽略某个信号,而其他线程可

以恢复信号的默认处理行为,或者为信号设置一个新的处理程序,从而可以撤销上述线程的

信号选择。

进程中的信号是送到单个线程的,如果信号与硬件故障或者计时器超时有关,该型号就被发

送到引起该事件的线程中去,而其他的信号则被发送到任意一个线程。

sigprocmask的行为在多线程的进程中没有定义,线程必须使用pthread_sigmask

总结:一个信号可以被没屏蔽它的任何一个线程处理,但是在一个进程内只有一个多个线程共用的处理函数。......

 

linux 多线程信号总结(三)

1 Linux 多线程应用中,每个线程可以通过调用pthread_sigmask() 设置本线程的信号掩码。一般情况下,被阻塞的信号将不能中断此线程的执行,除非此信号的产生是因为程序运行出错如SIGSEGV;另外不能被忽略处理的信号SIGKILL 和SIGSTOP 也无法被阻塞。

2 当一个线程调用pthread_create() 创建新的线程时,此线程的信号掩码会被新创建的线程继承。

3 信号安装最好采用sigaction方式,sigaction,是为替代signal 来设计的较稳定的信号处理,signal的使用比较简单。signal(signalNO,signalproc);

不能完成的任务是:

  1.不知道信号产生的原因;

  2.处理信号中不能阻塞其他的信号

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

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

4 sigprocmask函数只能用于单线程,在多线程中使用pthread_sigmask函数。

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

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

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

8 所谓阻塞,指Linux内核不向进程交付在掩码中的所有信号。于是进程可以通过修改信号掩码来暂时阻塞特定信号的交付,被阻塞的信号不会影响进程的行为直到该信号被真正交付。

9 忽略信号不同于阻塞信号,忽略信号是指Linux内核已经向应用程序交付了产生的信号,只是应用程序直接丢弃了该信号而已。

 附:信号表

在终端使用kill -l 命令可以显示所有的信号。
$kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT
17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU
25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH
29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN
35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4
39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12
47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14
51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6
59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX

其中前面31个信号为不可靠信号(非实时的,可能会出现信号的丢失),后面的信号为可靠信号(实时的real_time,对信号
排队,不会丢失)。

1) SIGHUP (挂起) 当运行进程的用户注销时通知该进程,使进程终止

2) SIGINT (中断) 当用户按下时,通知前台进程组终止进程

3) SIGQUIT (退出) 用户按下或时通知进程,使进程终止

4) SIGILL (非法指令) 执行了非法指令,如可执行文件本身出现错误、试图执行数据段、堆栈溢出

5) SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用

6) SIGABRT (异常中止) 调用abort函数生成的信号

7) SIGBUS 非法地址, 包括内存地址对齐(alignment)出错. eg: 访问一个四个字长的整数, 但其地址不是4的倍数.

8) SIGFPE (算术异常) 发生致命算术运算错误,包括浮点运算错误、溢出及除数为0.

9) SIGKILL (确认杀死) 当用户通过kill -9命令向进程发送信号时,可靠的终止进程

10) SIGUSR1 用户使用

11) SIGSEGV (段越界) 当进程尝试访问不属于自己的内存空间导致内存错误时,终止进程

12) SIGUSR2 用户使用

13) SIGPIPE 写至无读进程的管道, 或者Socket通信SOCT_STREAM的读进程已经终止,而再写入。

14) SIGALRM (超时) alarm函数使用该信号,时钟定时器超时响应

15) SIGTERM (软中断) 使用不带参数的kill命令时终止进程

17) SIGCHLD (子进程结束) 当子进程终止时通知父进程

18) SIGCONT (暂停进程继续) 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞.

19) SIGSTOP (停止) 作业控制信号,暂停停止(stopped)进程的执行. 本信号不能被阻塞, 处理或忽略.

20) SIGTSTP (暂停/停止) 交互式停止信号, Ctrl-Z 发出这个信号

21) SIGTTIN 当后台作业要从用户终端读数据时, 终端驱动程序产生SIGTTIN信号

22) SIGTTOU 当后台作业要往用户终端写数据时, 终端驱动程序产生SIGTTOU信号

23) SIGURG 有"紧急"数据或网络上带外数据到达socket时产生.

24) SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF (梗概时间超时) setitimer(2)函数设置的梗概统计间隔计时器(profiling interval timer)

28) SIGWINCH 窗口大小改变时发出.

29) SIGIO(异步I/O) 文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR 电源失效/重启动

31) SIGSYS 非法的系统调用。

在以上列出的信号中,
程序不可捕获、阻塞或忽略的信号有:SIGKILL,SIGSTOP
不能恢复至默认动作的信号有:SIGILL,SIGTRAP
默认会导致进程流产的信号有:SIGABRT,SIGBUS,SIGFPE,SIGILL,SIGIOT,SIGQUIT,SIGSEGV,SIGTRAP,SIGXCPU,SIGXFSZ
默认会导致进程退出的信号有:SIGALRM,SIGHUP,SIGINT,SIGKILL,SIGPIPE,SIGPOLL,SIGPROF,SIGSYS,SIGTERM,SIGUSR1,SIGUSR2,SIGVTALRM
默认会导致进程停止的信号有:SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOU
默认进程忽略的信号有:SIGCHLD,SIGPWR,SIGURG,SIGWINCH

此外,SIGIO在SVR4是退出,在4.3BSD中是忽略;SIGCONT在进程挂起时是继续,否则是忽略,不能被阻塞。


在Unix/Linux中signal函数是比较复杂的一个,其定义原型如下:
void (*signal(int signo,void (*func)(int))) (int)
这个函数中,最外层的函数体
void (* XXX )(int)表明是一个指针,指向一个函数XXX的指针,XXX所代表的函数需要一个int型的参数,返回void
signal(int signo, void(*func)(int))是signal函数的主体.
需要两个参数int型的signo以及一个指向函数的函数.
void (*func)(int).
正是由于其复杂性,在[Plauger 1992]用typedef来对其进行简化
typedef void Sigfuc(int);//这里可以看成一个返回值 .
再对signal函数进行简化就是这样的了
Sigfunc *signal(int,Sigfuc *);
 
在signal.h头文件中还有以下几个定义
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1

转载于:https://www.cnblogs.com/tianzeng/p/9192808.html

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值