Unix环境高级编程---信号

参考博客:http://blog.csdn.net/alex_my/article/details/39494129


1. 信号概念

 何为信号?

信号是一种软中断,可以由以下情形触发:

 

-1: 用户按下某些终断键,例如ctrl + C ,这可以停止一个失去控制的程序。(ctrl + D并不是发送信号,而是抛出一个    

        EOF 结束符,使程序退出。)

-2: 硬件异常,例如除数为0,无效的内存引用

-3kill(2), kill(1) (接收信号的进程必须与发送信号进程的所有者相同,或者发送信号进程的uid为root)

-4: 当软件条件达成,且有进程需要得到此通知

 注:信号是异步事件的经典实例。


当信号发生时,可以告诉内核进行以下处理:

 

-1:忽略信号(SIG_IGN): 有两个信号不能被忽略,SIGKILL(9), SIGSTOP(19), 不能被忽略的原因是这两个信号为内核和超级用户提供了一条可靠的方法去杀死和暂停进程。当忽略一些来自硬件的异常信号,产生的结果是未定义的,不如引用了无效的内存,除数为0

 

-2:抓取信号: 在抓取信号之前,首先要定义抓取信号之后要如何处理,SIGKILLSIGSTOP信号不能被捕捉。

 

-3:默认处理信号(SIG_DEF): 每一个信号都有一个默认的处理,大部分处理方式都是终止进程。如果想看各个的处理方式,可以在终端上输入man 7 signal,以下列出备查。红色标注为常用信号。

 

Signal     Value     Action   Comment

------------------------------------------------------------------------------------------------------------------------------------------------

SIGHUP        1        Term    Hangup detected on controlling terminal

                                           or death of controlling process

SIGINT          2        Term      Interrupt from keyboard

SIGQUIT       3        Core    Quit from keyboard

SIGILL           4        Core    Illegal Instruction

SIGABRT      6        Core    Abort signal from abort(3)

SIGFPE         8        Core    Floating point exception

SIGKILL         9        Term    Kill signal

SIGSEGV      11       Core    Invalid memory reference

SIGPIPE        13       Term    Broken pipe: write to pipe with no

                                                                  readers

SIGALRM      14         Term    Timer signal from alarm(2)

SIGTERM      15         Term    Termination signal

SIGUSR1   30,10,16    Term    User-defined signal 1

SIGUSR2   31,12,17    Term    User-defined signal 2

SIGCHLD   20,17,18    Ign     Child stopped or terminated

SIGCONT   19,18,25    Cont    Continue if stopped

SIGSTOP   17,19,23     Stop    Stop process

SIGTSTP   18,20,24     Stop    Stop typed at terminal

SIGTTIN   21,21,26       Stop    Terminal input for background process

SIGTTOU   22,22,27    Stop    Terminal output for background process


Next the signals not in the POSIX.1-1990 standard but described in SUSv2 and POSIX.1-2001.


SIGBUS      10,7,10     Core    Bus error (bad memory access)

SIGPOLL                     Term    Pollable event (Sys V).

                                       Synonym for SIGIO

SIGPROF     27,27,29    Term    Profiling timer expired

SIGSYS      12,31,12    Core    Bad argument to routine (SVr4)

SIGTRAP        5        Core    Trace/breakpoint trap

SIGURG      16,23,21    Ign     Urgent condition on socket (4.2BSD)

SIGVTALRM   26,26,28    Term    Virtual alarm clock (4.2BSD)

SIGXCPU     24,24,30    Core    CPU time limit exceeded (4.2BSD)

SIGXFSZ     25,25,31    Core    File size limit exceeded (4.2BSD)

 

  Up to and including Linux 2.2, the default behavior for SIGSYS, SIGXCPU, SIGXFSZ, and  (on  architec-
tures other than SPARC and MIPS) SIGBUS was to terminate the process (without a core dump).  (On some
 other Unix systems the default action for SIGXCPU and SIGXFSZ is to terminate the process  without  a
 core  dump.)   Linux 2.4 conforms to the POSIX.1-2001 requirements for these signals, terminating the
process with a core dump.

 Next various other signals.

SIGIOT         6        Core    IOT trap. A synonym for SIGABRT

SIGEMT       7,-,7      Term

SIGSTKFLT    -,16,-     Term    Stack fault on coprocessor (unused)

SIGIO       23,29,22    Term    I/O now possible (4.2BSD)

SIGCLD       -,-,18     Ign     A synonym for SIGCHLD

SIGPWR      29,30,19    Term    Power failure (System V)

SIGINFO      29,-,-             A synonym for SIGPWR

SIGLOST      -,-,-      Term    File lock lost (unused)

SIGWINCH    28,28,20    Ign     Window resize signal (4.3BSD, Sun)

SIGUNUSED    -,31,-     Core    Synonymous with SIGSYS

 

2. signal 函数

 

#include <signal.h>

 

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

                                                                               若出错,返回SIG_ERR

这个signal函数接受两个参数

参数1: 信号名,SIGCHLD

参数2: 接受信号处理函数指针,函数原型void func(int),也可以是常量SIG_IGN或SIG_DEF

 

另外,还有三个定义:

 

#define SIG_ERR (void(*)())-1    // 返回错误值

#define SIG_DFL (void(*)())0      //恢复为参数1所指信号的处理方法为默认方法     

#define SIG_IGN (void(*)())1      // 忽略参数1所指信号

 

可以作为参数2或者signal的返回值

 

程序用例:

 

<span style="font-size:14px;">#include "apue.h"

 

static void sig_func(int signo)

{

     printf("\nsigno: %d\n", signo);

     exit(EXIT_SUCCESS);

}

 

int main(int argc, char** argv)

{

     if(signal(SIGINT, sig_func) == SIG_ERR)

     {

          printf("signal failed. errno[%d]\t %s\n", errno, strerror(errno));

          exit(EXIT_FAILURE);

     }

 

     pause();

 

     exit(EXIT_SUCCESS);

}</span>

输出:

在键盘上按下ctrl + c,产生中断信号。

 

^C

signo: 2

 

注意点:

 -1:当执行一个程序时,所有信号的状态都是系统默认或者忽略,且信号响应后执行默认动作,直到在程序中做出改变。如同上一程序用例当中,按下ctrl + c后,默认是终止进程,当在程序中做出改变之后,变成执行函数sig_func了。

 

-2:当使用fork后,子进程继承父进程的信号处理方式。

 

-3:当fork后又使用exec后,新的程序中的信号又恢复了默认,因为替换旧的数据空间,堆栈等,使得原来处理函数对于新的程序来说,已经失效


-4:对于一个正在执行任务的进程,如果异步捕获了一个信号,并且进入相应的信号处理函数,当它处理完这个函数并想要回到进程中时,可能会使进程“忘记”刚才做到哪里了,这就引入了可重入函数的概念。书中列出了异步信号安全的函数。


一个具体的例子:

假如主线程用malloc申请一个动态空间,并向其中写数据,这是异步获得一个SIG,并跳进SIG处理函数,这个处理函数可能也会向此动态空间写入数据,从而导致破坏了malloc的数据结构。

 因此,要避免在信号处理程序中调用非可重入函数。


-5:不可靠的信号是指不具备阻塞信号能力的信号(早起Unix版本中存在的问题)


3.信号集:signal sets

 

#include <signal.h>

 

int sigemptyset(sigset_t* set);

int sigfillset(sigset_t* set);

int sigaddset(sigset_t* set, int signum);

int sigdelset(sigset_t* set, int signum);

                        return 0 on success and -1 on error

 

int sigismember(const sigset_t* set, int signum);

                        return 1 if is signum is a member of set, return 0 if not, -1 on error

 

以上函数可以从字面意思理解:

sigemptyset: (初始化)将set信号集清空

sigfillset : (初始化)将所有的信号填充到set

sigaddset  : 添加指定信号signumset

sigdelset  : set中移除指定信号signum

sigismember: 特使signum是否是set中的成员

 

 

4. sigprocmask

 

#include <signal.h>

 

int pthread_sigmask(int how, const sigset_t* restrict set, sigset_t* restrict oset);

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

 

sigprocmask: 用于设定信号屏蔽集内信号的处理方式

 

参数how可选值如下:

 

SIG_BLOCK  : set中包含了我们所希望添加的阻塞信号

 

SIG_UNBLOCK: set中包含了我们所希望解除阻塞的信号

 

SIG_SETMASK: set中为所设置屏蔽的信号

 

程序用例:

 

<span style="font-size:14px;">#include "apue.h"


int main(int argc, char** argv)

{

     sigset_t set;

     sigemptyset(&set);

     sigaddset(&set, SIGINT);

 

     if(sigismember(&set, SIGINT) == 1)

          printf("SIGINT is a member now\n");

     else

          printf("SIGINT is not a member or error occur\n");

 

     sigdelset(&set, SIGINT);

 

     if(sigismember(&set, SIGINT) == 0)

          printf("SIGINT is not a member now\n");

     else

          printf("SIGINT is still a member or error occur\n");

 

     sigaddset(&set, SIGINT);

 

     sigprocmask(SIG_SETMASK, &set, NULL);

 

     getchar();

     // try ctrl + c

     return 0;

}</span>


 

编译运行之后,可以ctrl + c,可以发现无效。

如果将sigprocmask(SIG_SETMASK, &set, NULL)注释,则可以了。

 

5. sigpending

 

#include <signal.h>

 

int sigpending(sigset_t* set);

          return 0 if success and -1 on error.

 

sigpending: 通过set返回已经通知进程,但被阻塞而挂起的信号(未决信号)。比如阻塞了SIGINT信号一段时间,而在这段时间中产生了这个信号,通知进程,这个信号称之为pending信号。如果产生多个SIGINT信号,在Linux实现中,也只会处理一次。可以通过程序用例观察到。

 

程序用例:

 

<span style="font-size:14px;">#include "apue.h"

static void sigfunc(int signo)
{
     printf("signo: %d\n", signo);
}

int main(int argc, char** argv)
{
     sigset_t set, oset, pset;

     if(signal(SIGINT, sigfunc) == SIG_ERR)
     {
          printf("signal failed, error[%d]: %s\n", errno, strerror(errno));
          exit(EXIT_FAILURE);
     }

     sigemptyset(&set);
     sigaddset(&set, SIGINT);
     if(sigprocmask(SIG_BLOCK, &set, &oset) == -1)
     {
          printf("sigprocmask failed, error[%d]: %s\n", errno, strerror(errno));
          exit(EXIT_FAILURE);
     }
     //此时,阻塞掉信号集内的信号
     // 在此期ctrl + c,可以多次输入,看会处理几次
     sleep(5);

     //处理未决信号,把未决信号SIGINT放入pset信号集
     if(sigpending(&pset) == -1)
     {
          printf("sigpending failed, error[%d]: %s\n", errno, strerror(errno));
          exit(EXIT_FAILURE);
     }
     
     if(sigismember(&pset, SIGINT) == 1)
          printf("SIGINT is pending\n");
     else
          printf("SIGINT is not pending\n");

     // 不再屏蔽,也可以用SIG_UNBLOCK
     if(sigprocmask(SIG_SETMASK, &oset, NULL) == -1)
     {
          printf("sigprocmask failed2, error[%d]: %s \n", errno, strerror(errno));
          exit(EXIT_FAILURE);
     }

     printf("sleep again\n");
     // ctrl + c, 可以多次输入,看会处理几次
     sleep(5);

     exit(EXIT_SUCCESS);
}</span>

输出:

 

第一次sleep时,可以多次输入ctrl + c5秒结束之后,会显示"SIGINT is pending",且函数sigfunc仅会被调用一次,说明多次输入,在当前的Linux实现中只会被调用一次。

第二次sleep期间,由于SIGINT不再被阻塞,因此输入ctrl + c后,会立即响应。

 

6. sigaction

 

#include <signal.h>

 

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

 

sigaction: 指定或者修改与指定信号相关联的处理动作。可用sigaction实现signal

 

struct sigaction:{

void    (*sa_handler )(int);                         /*addr of signal handler,

                                                 or  SIG_IGN or SIG_DFL.*/

sigset_t   sa_mask                               /*additional signals to block */                                              

int     sa_flags                          /* Special flags to affect behavior of signal. */

void(*sa_sigaction )(int, siginfo_t *, void *)             Pointer to a signal-catching function.

 

sa_handler: 信号处理函数,参数为int,如同signal函数的参数意愿。

sa_sigaction: 信号处理函数,参数有3个。

至于选择sa_handler指向的函数还是sa_sigaction指向的函数,需要依靠sa_flags判断。

 

sa_flags: 指定信号处理行为,以下值可以使用或组合

 

SA_NOCLDSTOP: 父进程在子进程暂停或者继续运行时不会收到SIGCHLD信号

SA_ONSTACK  : 如果设置此标志,且使用了sigaltstack设置了备用信号堆栈,则信号会传递给该堆栈中的进程,否则,在当前的进程中

SA_RESETHAND: 信号处理之后,重新设置为默认的处理方式

SA_RESTART   : 被信号打断的系统调用自动重新发起

SA_SIGINFO  : 使用sa_sigaction做为处理函数,而不是sa_handler

SA_NOCLDWAIT: 父进程在子进程中退出不会收到SIGCHLD信号,且子进程不会变成僵尸进程

SA_NODEFER  : 使sa_mask设置的屏蔽无效

 

sa_mask: 在信号处理函数执行过程中,屏蔽哪些信号。注意的是,这个屏蔽仅在信号处理函数执行过程中有效,而不是整个进程。

 

程序用例:

 

#include "apue.h"

static void sigfunc(int signo)
{
     printf("signo: %d\n", signo);
     sleep(10);
     printf("sleep over\n");
}

static void sigfunc_int(int signo)
{
     printf("SIGINT occur\n");
}

int main(int argc, char** argv)
{
     struct sigaction act;
     act.sa_flags = SA_NODEFER;

     sigemptyset(&act.sa_mask);

     act.sa_handler = sigfunc_int;
     sigaction(SIGINT, &act, NULL);

     act.sa_handler = sigfunc;
     sigaddset(&act.sa_mask, SIGINT);   
     
/*阻塞SIGINT信号,在输出时,如果我们先用ctrl+c产生SIGINT信号,
则立即处理该信号退出程序,但是如果程序先捕获到的是SIGQUIT信号,
则sigaddset处理会将SIGINT阻塞,先执行sigfunc()函数,sleep10秒后,
执行sigfunc_int函数,完成后退出。
*/
     if(sigaction(SIGQUIT, &act, NULL) == -1)
     {
          printf("sigaction failed");
          exit(EXIT_FAILURE);
     }

     sleep(20);

     printf("\n");
     return 0;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值