`
fp_moon
  • 浏览: 971153 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

详解sigaction --转

 
阅读更多

详解sigaction

 

这是挺好理解的,就好比在系统这个大进程里运行许多派生的进程,为了协调这些派生出的子进程,就必然要使用一些手段来通知监视。而信号就是这样一种系统级别的全局变量的通知。想想在写程序中,多个函数协调一个全局函数的情形。。。 

the signal is an event generated by the UNIX and Linux systems in response to some condition,upon receipt of which a process may in turn take some action. 

 

函数 

我想我需要如下系列的函数,修改本身的信号处理函数,对其他进程发送信号, 

#include <signal.h> 

void (*signal(int sig, void (*func)(int)))(int); 

 

which 

is the previous value of the function set up to handle this signal, or one of these two special values:

代码   

1. SIG_IGN             Ignore the signal.

2. SIG_DFL             Restore default behavior.

 

 

比如想捕捉SIGINT信号,但是我们又只想捕捉一次,就可以用到DFL信号来恢复之前的行为,可能会是这样 

但要注意的是,It is no safe to call all function, such as printf, from within a signal handler.A useful technique is to use a signal handler to set flag and then check that flag from the main 

pg and print a message if required.Toward the end of the chapter, you will find a list of calls that can safely be made inside signal handlers.

代码   

1. void ouch(int sig)

2. {

3. printf("OUCH ! - I got signal %d\n", sig);

4. signal(SIGINT, SIG_DFL);

5. }

6.

7. int main(int argc, char **argv)

8. {

9. signal(SIGINT, ouch);

10.

11. while(1)

12. {

13. printf("Hello World!\n");

14. sleep(1);

15. }

16.

17. return 0;

18. }

 

 

来点强壮的 

X/Open规范的更新更健壮的接口 

#include <signal.h> 

int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); 

oact如果不为空,将把前次act的状态保存下来。 

这个函数主要的是加入了信号集(sa_mask)这个功能。比如前面提到的,如果信号先发出而后调用pause(),则遗失掉这个信号。采用信号集他可以先收集或者说阻塞不传递给主进程,由主进程再来自主调用和处理。

代码   

1. void (*) (int) sa_handler     /* function, SIG_DFL or SIG_IGN

2. sigset_t sa_mask          /* signals to block in sa_handler

3. int sa_flags             /* signal action modifiers,SA_RESETHAND,具有reset功能

 

 

对这个信号集有如下几种操作: 

初始为空集,初始为所有已有的信号,增加新信号,删除指定信号

Java代码   

1. #include <signal.h>

2. int sigaddset(sigset_t *set, int signo);

3. int sigemptyset(sigset_t *set);

4. int sigfillset(sigset_t *set);

5. int sigdelset(sigset_t *set, int signo);

 

然后是一个批处理的函数

代码   

1. int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

2. SIG_BLOCK   The signals in set are added to the signal mask.

3. SIG_SETMASK     The signal mask is set from set.

4. SIG_UNBLOCK     The signals in set are removed from the signal mask.

 

 

 

判断是否是当前信号集里的信号 

int sigismember(sigset_t *set, int signo); 

 

 

最后就是最重要的,对信号集里的信号进行操作,假设已经把信号收集到,主程序可以阻塞/非阻塞式的调用。(不过非阻塞的调用好象不能自动把收到的信号清除掉,阻塞式的就可以自动清除)。 

 

If a signal is blocked by a process, it won’t be delivered, but will remain pending. A program can determine 

which of its blocked signals are pending by calling the function sigpending. 

#include <signal.h> 

int sigpending(sigset_t *set); 

 

 

A process can suspend execution until the delivery of one of a set of signals by calling sigsuspend. This 

is a more general form of the pause function we met earlier. 

#include <signal.h> 

int sigsuspend(const sigset_t *sigmask); 

 

sigaction Flags 

The sa_flags field of the sigaction structure used in sigaction may contain the following values to 

modify signal behavior:

代码   

1. SA_NOCLDSTOP        Don’t generate SIGCHLD when child processes stop.

2. SA_RESETHAND        Reset signal action to SIG_DFL on receipt.

3. SA_RESTART      Restart interruptible functions rather than error with EINTR.

4. SA_NODEFER      Don’t add the signal to the signal mask when caught.

 

 

 

通过以上的操作介绍,似乎已经很完美了。但是最大的问题来了,先了解几个概念: 

不完全重入函数:即可能被其他信号中断触发EINTR 

 

假设我们现在正在执行一个信号处理函数,突然发生一个中断,那么该信号函数将被打断。在一次情况下似乎没什么问题。但是假如连续性的被信号打断,而导致函数不断重启。就比如执行到一半退出,又执行,又退,又执行。。那么可能就有问题发生。有两个方法可以解决 

1、根本方法,用完全重入函数。如下图所示英文版 P474 

2、如果非不得以,那就不让信号来阻断信号函数的运行。可以用SA_RESTART标志把信号先放到屏蔽集的缓冲区里

代码   

1. SA_RESTART(似乎默认也是这个行为)

2. void ouch(int sig)

3. {

4. //..

5. select()//10秒

6. //..

7. }

8.

9. int main(int argc, char **argv)

10. {

11. struct sigaction act, act_g;

12. act.sa_handler = ouch;

13. act.sa_flags = SA_RESTART;   //设置重启

14. sigemptyset(&act.sa_mask);

15. sigaddset(&act.sa_mask, SIGTERM);

16.

17. if( -1==sigaction(SIGTERM, &act, 0))  //捕捉SIGTERM

18. {

19. perror("sigaction\n");

20. }

21.

22. select//10秒

23.

24. }

 

操作如下,首先运行该程序,然后在另一个tty里发送一个kill命令(在第一个select未结束前),那么将进入信号处理函数ouch,接着在ouch的select运行之际,再次发送kill,因为select也不完全函数,将会被再次打断,而又一次进入信号。但是因为设置了SA_RESTART,ouch将不会被打断,而是执行完后,接着执行响应第二次的信号函数。 

 

SA_NODEFER 

和上面一样的操作,发送第二次KILL信号时,第一个信号函数马上中断(由于是select),又再次进入信号函数。这里加点东西打印可以更清楚些。 

 

流程图 

以上这两种实现,有个东西起了至关重要的作用。那就是进程的信号屏蔽字。原理流程大概是这样的,当向一个进程发送信号时,可根据是否被屏蔽掉而发送至进程信号屏蔽字集合中或者进程本身。对于后者,sigaction可直接捕捉到,而前者,可以看成是一个暂存的集合,可用sigpending来取得。通常的情况是,已经用了sigaction函数直接获取信号,如果再次触发信号会马上跳出当前正在执行的信号函数而再次执行信号函数。而SA_RESTART这个标志应该是将信号保存到被屏蔽的信号集合里等待下次取出后执行,保证了当前函数的运行。SA_NODEFER就不做这一操作,直接发送到进程,让sigaction继续马上捕捉。      

整个走向可看下图,当然这里没有参考系统源码,必然存在着一定的疏忽 

 

 

目前已知的对屏蔽集的操作有pending/suspend函数,SA_RESTART,SA_NODEFER标志操作。写到这里,把屏蔽集合集看成一个临时的信号存放缓冲区更形象点。

sigaction

#include <signal.h>

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

struct sigaction {

  void (*sa_handler)(int);

  void (*sa_sigaction)(int, siginfo_t *, void *);

  sigset_t sa_mask;

  int sa_flags;

  void (*sa_restorer)(void);

};

通过sa_mask设置信号掩码集。

信号处理函数可以采用void (*sa_handler)(int)或void (*sa_sigaction)(int, siginfo_t *, void *)。到底采用哪个要看sa_flags中是否设置了SA_SIGINFO位,如果设置了就采用void (*sa_sigaction)(int, siginfo_t *, void *),此时可以向处理函数发送附加信息;默认情况下采用void (*sa_handler)(int),此时只能向处理函数发送信号的数值。

sa_falgs还可以设置其他标志:

SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL

SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用

SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号

#include<stdio.h>

#include<signal.h>

#include<stdlib.h>

#include<string.h>

#define INPUTLEN 100

void inthandler(int);

int main(){

    struct sigaction newhandler;

    sigset_t blocked;       //被阻塞的信号集

    char x[INPUTLEN];

    newhandler.sa_flags=SA_RESETHAND;

    newhandler.sa_handler=inthandler;

    sigemptyset(&blocked);      //清空信号处理掩码

    sigaddset(&blocked,SIGQUIT);

    newhandler.sa_mask=blocked;

    if(sigaction(SIGINT,&newhandler,NULL)==-1)

        perror("sigaction");

    else

        while(1){

            fgets(x,INPUTLEN,stdin);        //fgets()会在数据的最后附加"\0"

            printf("input:%s",x);

        }

}

void inthandler(int signum){

    printf("Called with signal %d\n",signum);

    sleep(signum);

    printf("done handling signal %d\n",signum);

}

复制代码

Ctrl-C向进程发送SIGINT信号,Ctrl-\向进程发送SIGQUIT信号。

$ ./sigactdemo

^CCalled with signal 2

^\done handling signal 2

Quit (core dumped)

由于把SIGQUIT加入了信号掩码集,所以处理信号SIGINT时把SIGQUIT屏蔽了。当处理完SIGINT后,内核才向进程发送SIGQUIT信号。

$ ./sigactdemo

^CCalled with signal 2

^Cdone handling signal 2

由于设置了SA_RESETHAND,第一次执行SIGINT的处理函数时相当于执行了signal(SIGINT,SIG_DFL),所以进程第二次收到SIGINT信号后就执行默认操作,即挂起进程。

修改代码,同时设置SA_RESETHAND和SA_NODEFER。

newhandler.sa_flags=SA_RESETHAND|SA_NODEFER;

$ ./sigactdemo

^CCalled with signal 2

^C

在没有设置SA_NODEFER时,在处理SIGINT信号时,自动屏幕SIGINT信号。现在设置了SA_NODEFER,则当SIGINT第二次到来时立即响应,即挂起了进程。

 

 

首先,需要理解几个signal相关的函数。

sigaddset(sigset_t* sigSet, int sigNum ) :  将信号sigNum 添加到信号集 sigSet 中;

sigdelset(sigset_t* sigSet, int sigNum) : 将信号 sigNum 从信号集 sigSet 中删除;

sigemptyset(sigset_t* sigSet) : 清空信号集;

sigfillset(sigset_t* sigSet) : 在信号集中打开所有的信号。

 

但是这个时候只是定义好了如此一个信号集,还有对信号的操作函数:

pthread_sigmask(int opCode, sigset_t* sigSet, sigset_t* oldSigSet) : opCode 指定了如何对 sigSet 里的信号进行处理。opCode 有三个值: SIG_BLOCK (将sigSet中的信号加到当前线程的屏蔽集中),SIG_UNBLOCK (将sigSet 中的信号从当前线程屏蔽集中删除),SIG_SETMASK (将sigSet 设为当前线程的屏蔽集)。 若oldSigSet != NULL,则将之前的信号屏蔽集存入其中。

另外还有个函数 sigprocmask() 也有类似功能。区别是:pthread_sigmask() 是线程库函数,用于多线程进程。sigprocmask() 是旧的实现,用于单线程的进程。

 

sigwait(sigset_t* sigSet, int* sigNum) : 当前线程等待 sigSet 中的信号。没有捕获到信号时,线程挂起;当捕获到时,函数返回,并将信号值存入 sigNum。

sigaction(int sigNum, sigaction* newAct, sigaction* oldAct) :  捕获信号 sigNum,并调用相应的处理函数(定义在 newAct 中)。

 

虽然sigwait() 和 sigaction() 都是用于捕获信号,但两者还是有较大区别:sigwait() 是阻塞的,线程会一直挂起直到捕获到信号,并且对信号的处理是定义在 sigwait()后的,只会在当前线程内执行;而sigaction()是非阻塞的,当信号被捕获时,会由进程内当前被调度到的线程来执行处理函数(好像是,not very sure...),被哪个线程处理是随机的。

所以,sigaction()适用于对实时性要求很高的时候。而在普通情况下建议使用sigwait(),因为其具有较好的可控性。

另外,还需要注意的是,SIG_KILL(大家应该都用过kill -9 吧) 和 SIG_STOP 是不能被用户屏蔽或捕获的。

 

好了,当理解了这几个函数后,可以自己试着来对信号进行处理了。对于lz的需求,简单举例如下:

sigset_t blockSet, waitSet;

int sigNum;

 

sigfillset(&blockSet);     // open all signals in blockSet

pthread_sigmask(SIG_BLOCK, blockSet, NULL);  //block all signals (except SIG_KILL & SIG_STOP also) in current thread

 

sigemptyset(&waitSet);     // empty all signals in waitSet

sigaddset(&waitSet, SIG_XXX);    // a signal wanted to be captured

sigaddset(&waitSet, SIG_YYY);    // another signal wanted to be captured

 

if ( sigwait(&waitSet, &sigNum) != 0 )    // wait for wanted signals

{

perror("Error - sigwait() is failure!\n");

exit(1);

}

 

switch (sigNum)

{

case SIG_XXX:

printf("SIG_XXX is captured!\n");

break;

 

case SIG_YYY:

printf("SIG_YYY is captured!\n");

break;

 

default:

printf("Error - cannot reach here!\n");

exit(1);

}

 

return OK;

 

另外,用 sigaction() 也可以实现。

 

分享到:
评论

相关推荐

    信号捕捉函数sigaction

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

    sigaction.c 文件

    sigaction.c 文件下载后直接编译运行,详情看博客

    C例子:IPC-sigaction

    该程序是我写的博客“一起talk C栗子吧(v第八十六回:C语言实例--使用信号进行进程间通信三)”的配套程序,共享给大家使用

    Linux系统调用 sigaction 的用法

    sigaction 函数原型定义如下:  int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)  这个系统调用的作用是改变进程接收到的指定信号的行动。  使用这个函数需要包含头文件#...

    linux 下实现sleep详解及简单实例

    linux 下实现sleep详解及简单实例 sleep: 普通版本 1、基本设计思路:  1&gt;注册SIGALRM信号的处理函数;  2&gt;调用alarm(nsecs)设定闹钟;  3&gt;调⽤pause等待,内核切换到别的进程运行;  4&gt;nsecs秒之后,闹钟超时,内...

    my_gperftools-2.0.tar.gz

    sa.sa_sigaction_ = SignalHandler; 修改src/base/linuxthreads.cc static void SignalHandler(int signum, siginfo_t *si, void *data) 其中 siginfo_t *si 改成 siginfo *si echo "please modify the src/...

    UNIX环境高级编程_第2版.part1

    10.14 sigaction函数261 10.15 sigsetjmp和siglongjmp函数266 10.16 sigsuspend函数268 10.17 abort函数274 10.18 system函数276 10.19 sleep函数280 10.20 作业控制信号282 10.21 其他特征284 10.22 小结...

    UNIX环境高级编程_第2版.part2

    10.14 sigaction函数261 10.15 sigsetjmp和siglongjmp函数266 10.16 sigsuspend函数268 10.17 abort函数274 10.18 system函数276 10.19 sleep函数280 10.20 作业控制信号282 10.21 其他特征284 10.22 小结...

    c处理函数 学习c精通必备

     定义函数 int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);  函数说明 sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和...

    Linux被中断的系统如何调用详解

    在Linux中,当阻塞于某个慢系统调用的进程捕获一个信号,则该系统调用就会被中断,转而执行信号处理函数,这就是被中断的系统调用。 然而,当信号处理函数返回时,有可能发生以下的情况: 如果信号处理函数是用...

    Linux进程通信之信号

    sigaction函数的具体介绍

    NDK20_线程轮询实现双进程守护

    在Linux系统下,如果使用sigaction将信号SIGCHLD的sa_flags中的SA_NOCLDSTOP选项打开,当子进程停止(STOP作业控制)时, 不产生此信号(即SIGCHLD)。不过,当子进程终止时,仍旧产生此信号(即SIGCHLD)。 3....

    linux进程间通信——信号机制

    对linux信号机制的详细阐述,包括signal,sigaction等函数的用法,并配以实例,通俗易懂,适合初学者阅读。。。

    中文第一版-UNIX环境高级编程.rar

    10.14 sigaction函数 223 10.15 sigsetjmp 和siglongjmp函数 226 10.16 sigsuspend函数 229 10.17 abort函数 234 10.18 system函数 235 10.19 sleep函数 240 10.20 作业控制信号 241 10.21 其他特征 243 10.21.1 ...

    中文第一版-UNIX环境高级编程

    10.14 sigaction函数 223 10.15 sigsetjmp 和siglongjmp函数 226 10.16 sigsuspend函数 229 10.17 abort函数 234 10.18 system函数 235 10.19 sleep函数 240 10.20 作业控制信号 241 10.21 其他特征 243 10.21.1 ...

    宋劲彬的嵌入式C语言一站式编程

    4.2. sigaction 4.3. pause 4.4. 可重入函数 4.5. sig_atomic_t类型与volatile限定符 4.6. 竞态条件与sigsuspend函数 4.7. 关于SIGCHLD信号 34. 终端、作业控制与守护进程 1. 终端 1.1. 终端的基本概念 1.2. 终端...

    linux下几种最常用的IPC接口,这样一来,统一了接口,提高代码重用性.rar

    linux除了支持Unix早期信号语义函数signal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);...

    设置网络延时的时长(练习)

    利用setsockopt函数和sigaction函数分别实现修改网络延时的时长

    Linux高性能服务器编程

    10.1.3 Linux信号 10.1.4 中断系统调用 10.2 信号函数 10.2.1 signal系统调用 10.2.2 sigaction系统调用 10.3 信号集 10.3.1 信号集函数 10.3.2 进程信号掩码 10.3.3 被挂起的信号 10.4 统一事件源 10.5 ...

    UNIX环境高级编程英文第三版+源码

    10.14 sigaction Function 349 10.15 sigsetjmp and siglongjmp Functions 355 10.16 sigsuspend Function 359 10.17 abort Function 365 10.18 system Function 367 10.19 sleep, nanosleep, and clock_nanosleep ...

Global site tag (gtag.js) - Google Analytics