一个事件可使一个信号发送给一个进程,这个事件能够是硬件异常,能够是软件条件触发,能够是终端产生信号,也能够是一个kill函数调用。当信号产生后,内核一般会在进程表中设置某种形式的标志(flag)。咱们能够认为当进程中的信号处理函数被触发的时候认为信号下达到了(delivered)这个进程。从信号产生到信号下达到进程这段期间,信号被认为是挂起状态(pending)。进程拥有阻塞信号下达的选项。若是一个阻塞信号要发送给一个进程,并且信号的处理方式是默认处理或者被进程捕获,那么这个信号将一直处于挂起状态(pending)直到这个进程将信号设置成非阻塞状态或将信号的处理方式改成忽略。操做系统在信号下达(delivered)的时候决定如何处理这个阻塞信号,而不是在信号产生的时候。这样作容许进程在信号下达前更改信号的处理方式。编程
若是一个阻塞信号在进程解除它的阻塞前产生屡次,那么unix内核也仅仅会向进程下发一次这个信号。POSIX并无规定信号下发到进程的顺序,然而与进程当前状态相关的信号会较先到达进程。数组
每一个进程都有一个信号掩码(signal mask),它定义了一个当前被阻塞发送到该进程的信号的集合。咱们能够认为这个掩码对于每一个可能下发到该进程的信号有一个与之对应的bit位,对于一个给定的信号来讲,若是与之对应的bit位是打开状态,那么意味着这个信号当前应处于阻塞状态。进程能够经过sigprocmask来检查并更改他当前的信号掩码。由于信号的数量是有可能超过一个整数正bit位位数的,全部POSIX规范定义了一个叫作sigset_t的数据类型,它包含了全部信号集。信号掩码就是存储在这其中一个信号集中的。多线程
发送信号函数
kill & raisespa
可使用kill函数向一个进程或进程组发送信号。raise函数容许进程给他本身发送信号。操作系统
1 #include <signal.h> 2 3 /* return 0 if OK, -1 on error */ 4 int kill(pid_t pid, int signo); 5 6 /* return 0 if OK, -1 on error */ 7 int raise(int signo);
函数调用 raise(signo); 等同于 kill(getpid(), signo); 。线程
对于kill函数,pid有如下四种不一样的选择:unix
正如以前咱们提到的,进程须要足够的权限才能向另外一个进程发送信号,超级用户能够向任何进程发送信号。对于其余用户,能够发送信号的基本准则是:发送进程的真实用户Id(real user Id)或有效用户Id(effective user Id)必须等于接收进程的真实用户Id或有效用户Id。若是实现支持 _POSIX_SAVED_IDS的话,系统会检查接收进程的saved set-user-ID而不是有效用户Id。关于发送信号权限的一个特殊情景是:若是待发送信号是SIGCONT,那么发送进程能够将信号发送给他所属会话下的任何进程。rest
POSIX规范定义信号值为0的信号为空信号(null signal)。它能够用于经过kill函数来检测某一进程是否存在。kill函数在收到值为0的信号后会进程正常的错误检查,可是不会发送此信号。所以咱们能够经过 kill(pid, 0) 来判断进程id为pid的进程是否存在。然而, UNIX系统在必定时间后会循环使用进程 IDs,因此经过pid检查出来的进程未必真的是你认为的那个进程(即函数调用时,经过pid查询到的进程未必会是你认为的那个进程)。另外kill函数不是原子的,当kill函数返回时,有可能被发送信号的进程已经结束。code
alarm & pause
alarm函数容许咱们设置一个定时器,这个定时器在将来某个时间点触发,这个定时器触发后会产生一个SIGALRM信号,此信号的默认处理方式是结束进程。
1 #include <unistd.h> 2 3 /* 若是以前没有设置alarm返回0,不然返回以前 4 设置的alarm所剩余的秒数 */ 5 unsigned int alarm(unsigned int seconds);
一旦到了alarm所设置的时间点,内核就会发送alarm信号,而因为处理器调度延时进程此时可能还没法获取到此信号处理的控制权。每一个进程中只有一个alarm时钟。当咱们调用alarm时,若是当前进程以前注册的闹钟还未到期,那么此函数返回以前闹钟距到期剩余的秒数,而且以前注册的闹钟会被这个新的闹钟值取代。另外,若是以前注册的闹钟还未到期而且新注册的闹钟值为0的话,那么以前注册的闹钟会被取消。
pause函数的调用会阻塞调用进程直到调用进程捕获到一个信号。
1 #include <unistd.h> 2 3 /*Returns: -1 with errno set to EINTR*/ 4 int pause(void);
信号集
像咱们以前提到的,不一样信号的数量可能会超过一个整数的bit位所能表示的信号数量。POSIX规范定义了sigset_t类型用来表示信号集,并使用下面的5个函数来管理信号集:
1 #include <unistd.h> 2 3 /*All four return 0 if OK, -1 on error*/ 4 5 /*清空set信号集中的全部信号*/ 6 int sigemptyset(sigset_t* set); 7 /*使set包含全部信号*/ 8 int sigfillset(sigset_t* set); 9 /*将信号signo加入到信号集set中*/ 10 int sigaddset(sigset_t* set, int signo); 11 /*从信号集set中删除signo*/ 12 int sigdelset(sigset_t* set, int signo); 13 14 /*Returns 1 if true, 0 if false, -1 on error*/ 15 int sigismember(const sigset_t* set, int signo);
sigprocmask
一个进程的信号掩码是指被阻止下发到此进程的全部信号的集合。进程能够检查并更改他的信号掩码。
#include <signal.h> /*
若是oset不为空,那么此进程信号掩码以前的值会被复制到oset中。
若是set为空,那么此进程的信号掩码不会被更改,而信号掩码的当前值
也不会复制到oset中。
*/ int sigprocmask(int how, const sigset_t* restrict set, sigset_t* restrict oset);
how参数的值指明如何修改信号掩码:
sigprocmask 不支持多线程环境。
sigpending
#include <unistd.h> /*经过set返回发送给当前进程但被阻塞的信号*/ int sigpending(sigset_t* set);
sigaction
咱们能够经过sigaction方法检查并修改特定信号的处理方式(action)。他是早期sinal函数的取代版本。
#include <unistd.h> /*若oact不为空,函数经过oact返回当前signo的action * 若act不为空,则修改signo的当前action*/ int sigaction(int signo, const struct sigaction* restrict act, struct sigaction* restrict oact); 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_flag; /*signal options*/ /*alternate handler*/ void (*sa_sigaction)(int,siginfo_t *, void *); };
当使用sigaction改变signo的action时,若是sa_handler指向了一个信号处理函数(SIG_IGN和SIG_DFL除外),sigaction函数会将sa_mask指向的信号集合在这个信号处理函数(sa_handler)被调用前加入到当前进程掩码中,当信号处理函数返回时,进程的信号掩码会恢复为他原来的值。这样,使咱们可以在信号处理函数被调用是阻塞一部分信号的到达。一旦咱们为一个信号安装了action, 那么对于这个信号这个action将一直处于安装状态,除非咱们使用sigaction方法明确的更改可它。
sa_flags:
sigsuspend
等待信号到达的一个整洁而可靠地方式是先阻塞这个信号而后使用sigsuspend。
#include <signal.h> /* 将当前进程信号掩码设置为sigmask, 函数返回后将进程掩码恢复为调用前 的值, 该函数老是返回-1,并设置 errno 为 -1 */ int sigsuspend(const sigset_t* sigmask);
sigsuspend 可用于等待指定信号的到达,他的经常使用用法以下:
1 sigset_t mask, oldmask; 2 3 … 4 5 /* Set up the mask of signals to temporarily block. */ 6 sigemptyset (&mask); 7 sigaddset (&mask, SIGUSR1); 8 9 … 10 11 /* Wait for a signal to arrive. */ 12 sigprocmask (SIG_BLOCK, &mask, &oldmask); 13 while (!usr_interrupt) 14 sigsuspend (&oldmask); 15 sigprocmask (SIG_UNBLOCK, &mask, NULL);
经过user_interrupt 判断是否等待的SIGUSR1信号已到达,sigsuspend再返回时将进程信号掩码设置为他被调用前的值,所以咱们最后须要将添加mask移除掉。
Signal Names and Numbers
数组sys_siglist能够帮助咱们匹配信号与信号名:
1 extern char* sys_siglist[]; 数组索引为信号值, 数组元素值为信号名。
信号与信号名的转换:
1 #include <signal.h> 2 3 /* 若是msg不为空,则向stderr 输出msg紧跟一个冒号加一个空着在加信号描述;若是msg为空则只向stderr输出信号描述*/ 4 void psignal(int signo, const char* msg); 5 6 void psiginfo(const siginfo_t info, const char* msg); 7 8 /*获取信号描述*/ 9 char* strsignal(int signo); 10 11 void sig2str(int signo, char* str); 12 void str2sig(const char* str, int* signop);
总结
信号一般用于一些相对复杂的程序, 理解如何及为什么处理信号对于UNIX高级编程是必要的。