好吧,信号这一章,我承认看到一半看不下去了。开始讲kill, raise, alarm还有耐心看一点,后面的sigprocmask, sigaction, sigsetjmp可是真没耐心啦。我倒是认为,这些函数光看不练的话,一毛钱用都没有,再说,我写博客的目的是把逻辑关系理清楚,又不是去罗列知识点。嘿嘿,所以我还是只看下函数作用吧。(持续自我安慰中。。。)
OK,那现在开始信号。第一句话:signals are software interrupts. 对信号机制已经不陌生了,所以就不需要强调什么了。首先,有一个很有意思的细节:Signal names are all defined by positive integer constants(signal number) in the header <signal.h>,但是实际上,信号的定义是在<sys/***.h>中,又被<signal.h>包含了进去。原因是:如果被定义在signal.h中,kernel包含了用于用户级程序的头文件是一种不好的形式。所以,如果应用程序和内核都要使用同一定义,那么就将相关信息放在内核头文件中,然后再由用户的头文件包含进去~
既然,提到signal number是正数,那么存在0的number吗?No signal has a signal number of 0. kill函数对signal number 0有特殊的应用:
既然提到了kill函数,那么就首先来说一下它吧。首先,不要被kill这个词所迷惑,它对其用法的表达不是那么准确。看定义:kill——send a signal to a process. The default signal for kill is SIGTERM. 是的,kill只是将一个signal发送给一个进程或进程组,虽然它的默认信号是SIGTERM,但它是否终止进程取决于其发送信号的类型,以及进程是否安排了捕捉它发送的信号。明白了吧~
Signals are classic examples of asynchronous events. 异步事件的经典实例~那么怎么处理一个信号呢?以前我们用signal函数,大概像
signal(int signo, void (*func)(int));
这样定义,参数分别是signal number和signal handler(或者signal-catching function),但它已经太老太老了,现在啊,我们该用sigaction函数啦,sigaction可以实现signal函数,看声明吧~
#include <signal.h> int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact); /*Returns: 0 if OK, −1 on error*/
说实话,这个sigaction有点复杂,那我们还是看signal函数,它只有两个参数signo和func。
那么,以前我们说过,当一个信号出现时,有如下三种处理方式,来具体看一下:
(1)Ignore;关于忽略信号的处理方式,说两点。
第一,SIGKILL和SIGSTOP这两个信号是绝对不能忽略滴,为啥?These two signals provide the kernel and the superuser with a surefire way of either killing or stopping any process. 注意这个surefire,“一定不会错的方法”(再吐槽一下中文版翻译,所以看的时候一定要结合英文版看);
第二点,signal中的func问题,如果它是SIG_IGN,那么就代表忽略这个信号;如果是SIG_DFL,那么就按系统默认处理;如果是signal handler,那么当然转到处理函数啦!
(2)Catch;比如说,接到一个SIGCHLD,那么说明它的子进程已经终止,这是如果要handle这个信号的话,可以在signal handler中调用waitpid来获得其子进程的ID和终止状态等。
(3)Default;系统怎么做?那谁知道啊,查表去10-1,但大多数信号的默认是终止该进程~
好,那我们回过头来看刚才那个sigaction函数,很复杂的样子啊,尤其是还有一个struct sigaction:
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; /* signal options, Figure 10.16 */ /* alternate handler */ void (*sa_sigaction)(int, siginfo_t *, void *); };这里边有个sa_mask是干嘛的呢?和函数sigprocmask很像吧,是用来阻塞信号集的。每个进程都有一个signal mask,对于每种可能的信号mask都有一位与之对应。想一想,如果多个相同的信号要送给同一个进程该怎么办?比如,多个同一种信号都要发给进程,当处理第一个信号前,把sa_mask加到进程的signal mask中,这样就阻塞了其它信号,直到处理完再恢复到刚开始的signal mask,再处理其它信号~
signal已经不靠谱啦,但用sigaction可以实现signal,或者用的时候直接调sigaction,但下面的程序我们为方便起见,用signal,程序是图10-2,我们来看看是怎样捕捉信号的:
#include "apue.h" static void sig_usr(int); /* one handler for both signals */ int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can’t catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can’t catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) /* argument is signal number */ { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); }在看这个程序之前可以看一看到底有哪些信号,当然,SIGUSR1和SIGUSR2是user-defined signal,是用在应用程序当中的,已经帮我们定义好啦!
还有pause呢?int pause(void);很easy的一个函数,作用是将进程挂起知道捕捉到一个信号;(书里把它和alarm放在了一起,unsigned int alarm(unsigned int seconds)的作用是设置定时器,经过seconds秒产生一个SIGALRM信号,如果忽略或不捕捉此信号,那么系统默认终止调用进程)。
好,那我们把它编译好之后来运行:
$ ./a.out & //start process in background [1] 7216 //job-control shell prints job number and process ID $ kill -USR1 7216 //send it SIGUSR1 received SIGUSR1 $ kill -USR2 7216 //send it SIGUSR2 received SIGUSR2 $ kill 7216 //now send it SIGTERM [1]+ Terminated ./a.out首先,运行时候加个&是让进程在后台运行,具体的可以Google。仔细回想一下最开始说的kill函数,可不是杀死哦~只有最后一条命令,向7216号进程发送的信号因为捕捉,所以是默认的SIGTERM。
那我们最后要说一说下面两种情况:
(1)当exec一个程序时,所有的信号状态不是default就是ignore。为什么呢?试想,一个进程原来要捕捉的信号,当exec一个新程序后,还能继续捕捉吗?当然不能!exec要把数据大换血啊,本来的signal handler地址很可能在新程序的数据空间中已经没意义了啊。
(2)fork一个子进程时,它会继承父进程的信号处理方式。为什么呢?父进程到子进程是直接copy memory image的嘛,这个时候signal handler的地址还是有意义的~
至于本章其它的sigpending, sigsetjmp, siflongjmpsigsuspend, abort(将SIGABRT发给调用进程), sleep,sigqueue,system(execute a shell command, 这个以前也被我略过了)等等等等等都不看啦,用到再说吧!