Linux信号总结

信号是由用户、系统或者进程发送给目标进程的信息,用来通知目标进程的状态改变或系统异常。

发送信号

kill

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

该函数可以把sig信号发送给pid指定的目标进程

pid 参数含义
pid > 0信号发送给PID为pid的进程
pid = 0信号发送给本进程组内的其他进程
pid = -1信号发送给除init进程外的所有进程,但发送者需要拥有对目标发送信号的权限
pid < -1信号发送给组ID为-pid的进程组中的所有成员

特别的如果sig为0的时候,kill函数不发送任何信号。
kill函数成功时返回0,失败返回-1,并设置errno

errno含义
EINVAL无效的信号
EPERM该进程没有权限发送信号给任何一个目标进程
ESRCH目标进程或进程组不存在

alarm

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

seconds表示设定的秒数,经过seconds秒后,内核将给调用函数的进程发送SIGALRM信号。
成功:如果调用此alarm()前,进程已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0
出错:-1

raise

#include<signal.h>
int raise(int sig);

sig为要发送的信号,成功返回0,失败返回非0值。

sigqueue

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);

该函数可以给指定进程

对信号的处理

进程在收到信号时,需要定义一个接受函数来处理。信号处理函数的原型为

#include<signal.h>
typedef void (*sighandler_t)(int);

唯一带有的int参数用来指示信号类型。用户可以按照该模板自定义信号处理函数。
除了用户自定义的信号处理函数,还有两种其他处理方式——SIG_IGN和SIG_DEL

#include<bits/signum.h>
#define SIG_DFL ((__sighandler_t) 0)
#define SIG_IGN ((__sighandler_t) 1)

SIG_IGN表示忽略目标信号,SIG_DEL表示使用信号的默认处理方式:结束进程、忽略信号、结束进程并生成核心转储文件、暂停进程,以及继续进程

信号函数

signal
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

signum为信号类型,handler为函数指针。返回值为前一次调用signal函数时传入的函数指针,如果是第一次调用则为SIG_DEF。
signal系统调用出错时返回SIG_ERR并设置errno。

sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act,
               struct sigaction *oldact);

signum为信号类型,act为新的信号处理方式,oact为之前的信号处理方式。

struct sigaction
{
#ifdef __USE_POSIX199309
    union
    {
        _sighandler_t sa_handler;
        void (*sa_sigaction)(int, siginfo_t*, void*);
    }
    _sigaction_handler;
#define sa_handler     __sigaction_handler.sa_handler
#define sa_sigaction   __sigaction_handler.sa_sigaction
#else
    _sighandler_t sa_handler;
#endif
    _sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void)
};

sa_handler和sa_sigaction为共用体,这两个值只有一个生效。
sa_handler为函数指针,指向用户自定义的信号处理函数,也可以是SIG_DFL或SIG_IGN
sa_sigaction也是用来指定signum的处理函数,但是它有三个参数买第一个是信号编号,第二个是指向siginfo_t结构的指针,第三个是指向任何类型的指针,一般不使用。
sa_mask成员声明一个信号集。当某个信号被加入到信号集中后这个信号将不会被程序接收到
例:

#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>

void handler(int signo)
{
    if (signo == SIGINT)
        printf("Get SIGINT!\n");
    else
        printf("Get SIGQUIT!\n");
}

int main()
{
    sigset_t sigset, oset; /*sigset存放屏蔽信号,oset保存当前屏蔽信号*/
    sigemptyset(&sigset); /*清空信号集*/
    sigaddset(&sigset, SIGINT); /*添加SIGINT信号,信号集中仅有SIGINT*/
    sigprocmask(SIG_BLOCK, &sigset, &oset); /*加入屏蔽信号*/
    signal(SIGINT, handler);
    signal(SIGQUIT, handler);
    sleep(2);
    raise(SIGINT); /*发送SIGINT信号*/
    raise(SIGQUIT);
}

我们将SIGINT加入到了信号集当中,我们在给程序发送SIGINT信号的时候程序是接收不到的。这个操作其实为设置进程的信号掩码,我们后面还会再讲。
sa_flags用来说明信号的一些其他操作

选项含义
SA_BOCLDSTOP如果sig的参数为SIGCHLD,则设置该标志表示子进程暂时不生成SIGCHLD信号
SA_NOCLDWAIT如果sig的参数为SIGCHLD,则设置该标志表示子进程结束时不产生僵尸进程
SA_SIGINFO如果使用sa_sigaction作为信号处理函数(而不是sa_handler),它给进程提供更多相关的信息(可以传参)
SA_ONSTACK调用由siglstack函数设置的可选信号栈上的信号处理函数
SA_RESTART重新调用被该信号终止的系统调用
SA_NODEFER当接收到信号并进入其信号处理函数时,不屏蔽该信号。
SA_RESETHAND信号处理函数执行完以后恢复信号的默认处理方式
SA_INTERRUPT中断系统调用
SA_NOMASK同SA_NODEFER
SA_ONESHOT同SA_RESETGAND
SA_STACK同SA_ONSTACK

sa_restorer已经作废,一些标准已经不支持该数据成员。

中断系统调用

一些IO系统调用执行时, 如 read 等待输入期间, 如果收到一个信号,系统将中断read, 转而执行信号处理函数. 当信号处理函数返回后, 系统遇到了一个问题: 是重新开始这个系统调用, 还是让系统调用失败?早期UNIX系统的做法是, 中断系统调用, 并让系统调用失败, 比如read返回 -1, 同时设置 errno 为 EINTR中断了的系统调用是没有完成的调用, 它的失败是临时性的, 如果再次调用则可能成功, 这并不是真正的失败, 所以要对这种情况进行处理。
比如:

while (1) {
    n = read(fd, buf, BUFSIZ);
    if (n == -1 && errno != EINTR) {
        printf("read error\n");
        break;
    }
    if (n == 0) {
        printf("read done\n");
        break;
    }
}

也可使用sigaction函数设置SA_RESTART标志以自动重启被该信号中断的系统调用。

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <unistd.h>

void sig_handler(int signum)
{
    printf("in handler\n");
    sleep(1);
    printf("==========handler========== return\n");
}

int main(int argc, char **argv)
{
    char buf[100];
    int ret;
    struct sigaction action, old_action;

    action.sa_handler = sig_handler;
    sigemptyset(&action.sa_mask);
    action.sa_flags = 0;
    /* 版本1:不设置SA_RESTART属性
     * 版本2:设置SA_RESTART属性 */
    //action.sa_flags |= SA_RESTART;

    sigaction(SIGINT, NULL, &old_action);
    if (old_action.sa_handler != SIG_IGN) {
        sigaction(SIGINT, &action, NULL);
    }

    bzero(buf, 100);

    ret = read(0, buf, 100);
    if (ret == -1) {
        perror("read");
    }

    printf("read %d bytes:\n", ret);==
    printf("%s\n", buf);
    return 0;
}

信号集

在之前我们在介绍sigaction的时候,sa_mask的类型为sigset_t。
在Linux中数据结构sigset_t来表示一组信号。其定义如下:

#include<asm-generic/bitsperlong.h>
#define __BITS_PER_LONG 32
#include<signal.h>
#define _NSIG       64
#define _NSIG_BPW   __BITS_PER_LONG
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
typedef struct {
    unsigned long sig[_NSIG_WORDS];
} sigset_t;

由定义可知,sigset_t实际上是一个长整形数组,数组的每个元素的每一位表示一个信号。Linux提供如下一组函数来设置、修改、删除和查询信号集。

include<signal.h>
int sigemptyset(sigset* _set) //清空信号集
int sigfillset(sigset* _set) //在信号集设置所有信号
int sigaddset(sigset* _set, int _signo) //将信号_signo添加到信号集中
int sigdelset(sigset* _set, int _signo) //将信号_signo从信号集删除
int sigismember(_const sigset_t* _set, int _signo) //测试_signo是否在信号集中

进程信号掩码

我们可以利用sigaction结构体的sa_mask成员来设置进程的信号掩码,当设置好信号掩码之后我们可以使用sigprocmask来使得设置生效

#include<signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

set参数指定新的信号掩码,oldset参数输入原来的信号掩码(如果不为NULL的话)。如果set参数不为NULL,则how参数指定设置进程信号掩码的方式为:

how参数含义
SIG_BLOCK按照参数 set 提供的屏蔽字,屏蔽信号。并将原信号屏蔽保存到oldset中。
SIG_UNBLOCK按照参数 set 提供的屏蔽字进行信号的解除屏蔽。针对Set中的信号进行解屏
SIG_SETMASK按照参数 set 提供的信号设置重新设置系统信号设置。

设置进程号掩码之后,被屏蔽的信号将不能被进程接收。
sigprocmask成功时返回0,失败返回-1并设置errno。

被挂起的信号

设置进程信号掩码后,如果给进程发送一个被屏蔽的信号,则该信号会被设置进程的一个被挂起的信号。若我们在后面取消对挂起信号的屏蔽,则它能立即被进程接收到。
例:

#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<signal.h>
/*sigprocmsk的使用*/
void msg(int signo)
{
    if (signo == SIGINT)
        printf("Get SIGINT!\n");
    else
        printf("Get SIGQUIT!\n");
}

int main()
{
    sigset_t sigset, oset; /*sigset存放屏蔽信号,oset保存当前屏蔽信号*/
    sigemptyset(&sigset); /*清空信号集*/
    sigaddset(&sigset, SIGINT); /*添加SIGINT信号,信号集中仅有SIGINT*/
    sigprocmask(SIG_BLOCK, &sigset, &oset); /*加入屏蔽信号*/
    signal(SIGINT, msg);
    signal(SIGQUIT, msg);
    sleep(2);
    raise(SIGINT); /*发送SIGINT信号*/
    sigprocmask(SIG_UNBLOCK, &sigset, &oset);
    raise(SIGQUIT);
}

执行结果为
^C^C^C^C^CGet SIGINT!
Get SIGINT!
Get SIGQUIT!
可知在取消屏蔽后果然生效了。

#include <signal.h>
int sigpending(sigset_t *set);

该函数可以获得进程当前被挂起的信号。set参数用于保存被挂起的信号集。根据上面的例子我们可知:即使进程多次接收到同一个被挂起的信号,该信号的信号处理函数只触发一次,sigpending函数也只反映一次。
sigpending函数成功时返回0,失败时返回-1,并设置errno。
特别的:fork调用产生的子进程将继承父进程的信号掩码,但具有一个空的挂起的信号集。

可以传参数的信号处理函数

之前我们有提到过sigaction函数sa_flags的SA_SIGINFO选项,在我们设置了该选项之后,并且使用sa_sigaction来指定信号处理函数时我们便可以给信号处理函数传递参数。其信号处理函数的模板为:

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

其中siginfo_t中的si_int和si_ptr可以用来传递参数给信号处理函数

typedef struct siginfo_t{ 
int si_signo;//信号编号 
int si_errno;//如果为非零值则错误代码与之关联 
int si_code;//说明进程如何接收信号以及从何处收到 
pid_t si_pid;//适用于SIGCHLD,代表被终止进程的PID 
pid_t si_uid;//适用于SIGCHLD,代表被终止进程所拥有进程的UID 
int si_status;//适用于SIGCHLD,代表被终止进程的状态 
clock_t si_utime;//适用于SIGCHLD,代表被终止进程所消耗的用户时间 
clock_t si_stime;//适用于SIGCHLD,代表被终止进程所消耗系统的时间 
sigval_t si_value; 
int si_int; 
void * si_ptr; 
void* si_addr; 
int si_band; 
int si_fd; 
};

例:

#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>

void handler(int sig,siginfo_t *s_t,void *p)//能够接受额外数据的信号处理函数签名
{
  int tmp = 0;
  tmp = s_t->si_int;  //si_int和si_value.sival_int是一样的--针对额外数据是int的时候。
    printf("Aloha recv a sig=%d\tvar :%d\n and var is also: %d", sig,tmp,s_t->si_value.sival_int);  
}


int main(int argc, char *argv[])
{   
  pid_t     pid;
  int ret = 0;
  int i = 0;
  union sigval mysigval;//用来存放额外数据
  struct sigaction act;//用来注册信号

  /*使用sigaction必须要初始化的三个成员*/
  act.sa_sigaction = handler;//指定回调函数
  act.sa_flags = SA_SIGINFO;//尤其重要--只有等于SA_SIGINFO,信号处理函数才能接受额外数据
  sigemptyset(&act.sa_mask);//清空屏蔽字
  if(sigaction(SIGINT,&act,NULL) < 0)//注册信号--指定毁掉函数
  {
    perror("sigaction error!\n");
    exit(-1);
  }
  pid = fork();//创建子进程
  if(-1 == pid)
  {
    perror("fork");
    exit(-1);
  }
  else if(0 == pid)
  {
    mysigval.sival_int = 125;//设置要随着信号发送的额外数据
    for(i = 0;i < 10;i++)//子进程发送十次信号--SIGINT是不可靠信号--传送有点慢
    {
      ret = sigqueue(getpid(),SIGINT,mysigval);//开始发送信号
      if(ret != 0)//发送失败
      {
        perror("sigqueue");
        exit(-1);
      }
      else{//返回0表示信号发送成功
        printf("send ok!\n");
        sleep(1);
      }
    }
  }
  else if(pid > 0)
  {
    while(1);//父进程死循环
  } 
    return 0;
}

系统调用sigqueue发送信号时,sigqueue的第三个参数就是sigval联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。

统一事件源

信号处理函数往管道的写端写入信号值,主循环从管道的读端读出该信号值。使用I/O多路复用来监听管道的读端文件描述符上的可读事件。从而将信号事件转化为一般的I/O事件,即统一事件源。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值