在学习一个东西的时候我总是喜欢去问这样做的理由是什么?也喜欢去究竟他的历史。从中你可以发现所有的设计都在不断改进出来的,从来就没有一个设计是一开始就是完美的。好比是人,之初,性也许是善的,如果我们不通过后天的学习去让自己的心灵完美的话,他就只停留在了人的初级阶段了。
对于信号(signal)也是如此,硬件的中断中得出他的模型,然后不断的的去完善它。当一个事件发生时内核就会对该进程发送相应的信号,比如,内存错误了就是发送SIGSEGV,这个也就是我们常看到的段错误。再比如当我们做除零操作是就会发送SIGFPE。具体的信号定义的话可以参照http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_21.html#SEC336 。下面就来看看具体的代码函数。
1.信号的注册。
typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
这里不管你在做什么事情,产生一个软中断后内核deliver一个信号给进程,然后进程马上中止当前的任务去做信号处理相关的动作,但是我之前的事情也不能丢掉啊,等你做完信号处理的紧急的事情之后你应该继续把之前的时候完成 吧。于是乎就出现了可重入函数,等执行完信号处理函数之后,进程再次的回到之前的处理位置接着执行。关于那些事可重入的函数可以参考http://man7.org/linux/man-pages/man7/signal.7.html. 一个解决方案的引入通常带入了另外一个问题,如果这个函数是不可重入的呢?,在某一段代码中我们接收到信号,但是,我们又不想中断当前的函数?直接忽略这个型号吗?NO,操作系统又给我们提供了一些和信号集相关的函数。如果一段代码中你不想接受到某些信号你可以先将其阻挡在外,等处理完之后可以看下该型号是不是在block列表里,如果一个信号在被阻挡的列表了你可以对其单独的处理。这里的处理正的和生活中的某些处理很相似。看来我们的程序设计也是来源于生活的。下面使一些与之相关的函数:
int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum);
int sigpending(sigset_t *set);
下面是一个简单samplecode
#include <signal.h> #include <stdio.h> #include <string.h> #include <unistd.h> static int got_signal = 0; static void hdl (int sig) { got_signal = 1; printf("signal handle hd1"); } int main (int argc, char *argv[]) { sigset_t mask; sigset_t orig_mask; if (signal(SIGTERM,hdl)) { perror ("signal error"); return 1; } sigemptyset (&mask); sigaddset (&mask, SIGTERM); if (sigprocmask(SIG_BLOCK, &mask, &orig_mask) < 0) { perror ("sigprocmask"); return 1; } sleep (10); if (sigpending(&mask) == 0){ printf("Signal TERM is pending\n"); } if (sigprocmask(SIG_SETMASK, &orig_mask, NULL) < 0) { perror ("sigprocmask"); return 1; } sleep (1); if (got_signal) puts ("Got signal"); return 0; }
一个函数的引入通常也引入了另一个问题,好像我的bug都是这样产生的,一个设计方案的导入通常会产生新的bug。现在有这样一个问题,我们希望变更一些信号处理函数,当前如果产生SIGA我们去做handA的操作,但是我们希望之后按照handB的操作去处理,最后又回到handA中又应该怎么处理呢?我们会使用以下的处理方式吗?
signal(SIGA, handA);
signal(SIGA, handB);
signal(SIGA, handA);
这种模式的缺点又在哪里呢?由于handA---->handB, handB--->handA的过程中不是原子操作,在handB--->handA的过程中产生一个SIGA信号会出现什么样的情况?这个信号岂不是丢失了。这里提供了两个信号处理函数无缝切换的函数。
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
关于信号这块剩余的最后一个函数就是 sigsuspend了。我们先来看看他出现的理由吧!
现在考虑一下的模型:
.
.
.
sigprocmask(SIG_SETMASK, &oldmask, NULL);
pause();
以上的模型中如果在sigprocmask和pause之间发生了该信号,会发生什么事情?还没有执行pause函数,进程 接受到该信号后也不会被唤醒。最后导致该信号被丢失。导致信号丢失的原因在于sigprocmask和pause不是原子操作,在sigprocmask和pause之间有一个时间段,从而导致了信号丢失的发生。解决这个问题之要将sigprocmask和pause合并成要给原子操作就可以,于是就有了如下函数:
int sigsuspend(const sigset_t *sigmask);
以上是对APUEsignal一章的学习总结,错误之处还望指出。