信号相关

信号

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

  • 用户可以通过输入特殊的终端字符给进程发送信号,例如Ctrl+C发送中断信号。这样结束进程不会产生僵尸进程。
  • 系统异常。除0,非法内存段访问。
  • 系统状态变化。alarm定时器到期将引起SIGALRM信号。
  • kill命令等。

服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。

发送信号

Linux下,一个进程给其他进程发送信号的API是kill函数。

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

将sig设置为0,可以用来检测目标进程或进程组是否存在。

信号处理方式

信号处理函数

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

参数为一个int,用来指示信号类型。

除了用户自定义信号处理函数外,bits/signum.h头文件中还定义了 信号的两种其他处理方式——SIG_IGN和SIG_DEL。

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

SIG_IGN表示忽略目标信号,SIG_DFL表示使用信号的默认处理方式。

信号的默认处理方式:

  • 结束进程(Term)
  • 忽略信号(Ign)
  • 结束进程并生成核心转储文件(Core)
  • 暂停进程(Stop)
  • 继续进程(Cont)

中断系统调用

如果程序在执行处于阻塞状态的系统调用时,接收到了信号,并且为该信号设置了信号处理函数,那么默认情况下系统调用将被中断,并且errno被设置为EINTR,可以使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用。

对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN), 如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统 调用(比如connect、epoll_wait)。

信号函数

  1. signal系统调用

    #include<signal.h> 
    _sighandler_t signal(int sig,_sighandler_t_handler)
    

    sig:信号类型,$handler$用于指定信号sig的处理函数。

    signal函数成功时返回一个函数指针,该函数指针的类型也是 _sighandler_t。这个返回值是前一次调用signal函数时传入的函数指针, 或者是信号sig对应的默认处理函数指针SIG_DEF(如果是第一次调用 signal的话)。

  2. sigaction系统调用

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

    sig:信号类型

    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_hander成员指定信号处理函数;

    sa_mask成员设置 进程的信号掩码,指定哪些信号不能发送给本进程;

    sa_flags成员用于设置程序收到信号时的行为;

    sa_restorer成员已经过时,最好不要使用.

信号集函数

Linux使用数据结构sigset_t来表示一组信号。

#include<bits/sigset.h> 
#define_SIGSET_NWORDS(1024/(8*sizeof(unsigned long int))) 
typedef struct 
{ 
    unsigned long int__val[_SIGSET_NWORDS];
}__sigset_t;
#include<signal.h> 
int sigemptyset(sigset_t*_set)/*清空信号集*/ 
int sigfillset(sigset_t*_set)/*在信号集中设置所有信号*/ 
int sigaddset(sigset_t*_set,int_signo)/*将信号_signo添加至信号集中*/ 
int sigdelset(sigset_t*_set,int_signo)/*将信号_signo从信号集中删除*/ 
int sigismember(_const sigset_t*_set,int_signo)/*测试_signo是否在信 号集中*/

sigset_t实际上是一个长整型数组,数组的每个元素的每个位表示一个信号

进程信号掩码

利用sigaction结构体的sa_mask成员来设置进程的信号掩码

#include<signal.h> 
int sigprocmask(int_how,_const sigset_t*_set,sigset_t*_oset);

_set参数指定新的信号掩码,_oset参数则输出原来的信号掩码(如果不为NULL的话)。

如果_set参数不为NULL,则_how参数指定设置进程信号掩码的方式:

如果_set为NULL,则进程信号掩码不变,此时我们仍然可以利用 _oset参数来获得进程当前的信号掩码。

被挂起的信号

设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。

获得进程当前被挂起的信号集:

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

统一事件源

信 号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往 管道的写端写入信号值,主循环则从管道的读端读出该信号值。

使用 I/O复用系统调用来监听管道的读端文件描述符上的可读事件.

int setnonblocking(int fd) 
{ 
    int old_option=fcntl(fd,F_GETFL); 
    int new_option=old_option|O_NONBLOCK; 
    fcntl(fd,F_SETFL,new_option);
    return old_option; 
} 
void addfd(int epollfd,int fd) 
{ 
    epoll_event event; 
    event.data.fd=fd; 
    event.events=EPOLLIN|EPOLLET; 
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,event); 
    setnonblocking(fd); 
} 
/*信号处理函数*/ 
void sig_handler(int sig) 
{ 
    /*保留原来的errno,在函数最后恢复,以保证函数的可重入性*/
    int save_errno=errno; 
    int msg=sig; 
    send(pipefd[1],(char*)msg,1,0);/*将信号值写入管道,以通知主循环*/ 
    errno=save_errno; 
} 
/*设置信号的处理函数*/ 
void addsig(int sig) 
{ 
    struct sigaction sa; 
    memset(sa,'\0',sizeof(sa)); 
    sa.sa_handler=sig_handler; 
    sa.sa_flags|=SA_RESTART; 
    sigfillset(sa.sa_mask); 
    assert(sigaction(sig,sa,NULL)!=-1); 
}

网络编程相关信号

  • SIGHUP

当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,它们通常利用SIGHUP信号来强制服务器重读配置文件。

  • SIGPIPE

往一个读端关闭的管道或socket连接中写数据将引 发SIGPIPE信号。程序接收到SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为错误的写操作而导致程序退出。引起SIGPIPE信号的写操作将设置errno为EPIPE。

可以使用send函数的MSG_NOSIGNAL标志来禁 止写操作触发SIGPIPE信号,使用send函数反 馈的errno值来判断管道或者socket连接的读端是否已经关闭。

利用I/O复用系统调用来检测管道和socket连接 的读端是否已经关闭。当管道的读端关闭时,写端文件 描述符上的POLLHUP事件将被触发;当socket连接被对方关闭时, socket上的POLLRDHUP事件将被触发。

  • SIGURG

带外数据。