本杂文主要是讲解了下信号和进程的关系。前面主要是一些man式的资料描述和书上一些例子的摘要。因为我想一篇记载性的东西多少得放点让人有点回忆性的代码和知识点。主要内容可从sigaction直接开始看。。。
正文我觉得这是挺好理解的,就好比在系统这个大进程里运行许多派生的进程,为了协调这些派生出的子进程,就必然要使用一些手段来通知监视。而信号就是这样一种系统级别的全局变量的通知。想想在写程序中,多个函数协调一个全局函数的情形。。。
the signal is an event generated by the UNIX and Linux systems in response to some condition,upon receipt of which a process may in turn take some action.
函数我想我需要如下系列的函数,修改本身的信号处理函数,对其他进程发送信号,
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
which
is the previous value of the function set up to handle this signal, or one of these two special values:
- SIG_IGN Ignore the signal.
- SIG_DFL Restore default behavior.
比如想捕捉SIGINT信号,但是我们又只想捕捉一次,就可以用到DFL信号来恢复之前的行为,可能会是这样
但要注意的是,It is no safe to call all function, such as printf, from within a signal handler.A useful technique is to use a signal handler to set flag and then check that flag from the main
pg and print a message if required.Toward the end of the chapter, you will find a list of calls that can safely be made inside signal handlers.
- void ouch(int sig)
- {
- printf("OUCH ! - I got signal %d\n", sig);
- signal(SIGINT, SIG_DFL);
- }
-
- int main(int argc, char **argv)
- {
- signal(SIGINT, ouch);
-
- while(1)
- {
- printf("Hello World!\n");
- sleep(1);
- }
-
- return 0;
- }
来点强壮的X/Open规范的更新更健壮的接口
#include <signal.h>
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);
oact如果不为空,将把前次act的状态保存下来。
这个函数主要的是加入了信号集(sa_mask)这个功能。比如前面提到的,如果信号先发出而后调用pause(),则遗失掉这个信号。采用信号集他可以先收集或者说阻塞不传递给主进程,由主进程再来自主调用和处理。
- void (*) (int) sa_handler /* function, SIG_DFL or SIG_IGN
- sigset_t sa_mask /* signals to block in sa_handler
- int sa_flags /* signal action modifiers,SA_RESETHAND,具有reset功能
对这个信号集有如下几种操作:
初始为空集,初始为所有已有的信号,增加新信号,删除指定信号
- #include <signal.h>
- int sigaddset(sigset_t *set, int signo);
- int sigemptyset(sigset_t *set);
- int sigfillset(sigset_t *set);
- int sigdelset(sigset_t *set, int signo);
然后是一个批处理的函数
- int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
- SIG_BLOCK The signals in set are added to the signal mask.
- SIG_SETMASK The signal mask is set from set.
- SIG_UNBLOCK The signals in set are removed from the signal mask.
判断是否是当前信号集里的信号
int sigismember(sigset_t *set, int signo);
最后就是最重要的,对信号集里的信号进行操作,假设已经把信号收集到,主程序可以阻塞/非阻塞式的调用。(不过非阻塞的调用好象不能自动把收到的信号清除掉,阻塞式的就可以自动清除)。
If a signal is blocked by a process, it won’t be delivered, but will remain pending. A program can determine
which of its blocked signals are pending by calling the function sigpending.
#include <signal.h>
int sigpending(sigset_t *set);
A process can suspend execution until the delivery of one of a set of signals by calling sigsuspend. This
is a more general form of the pause function we met earlier.
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
sigaction Flags
The sa_flags field of the sigaction structure used in sigaction may contain the following values to
modify signal behavior:
- SA_NOCLDSTOP Don’t generate SIGCHLD when child processes stop.
- SA_RESETHAND Reset signal action to SIG_DFL on receipt.
- SA_RESTART Restart interruptible functions rather than error with EINTR.
- SA_NODEFER Don’t add the signal to the signal mask when caught.
通过以上的操作介绍,似乎已经很完美了。但是最大的问题来了,先了解几个概念:
不完全重入函数:即可能被其他信号中断触发EINTR
假设我们现在正在执行一个信号处理函数,突然发生一个中断,那么该信号函数将被打断。在一次情况下似乎没什么问题。但是假如连续性的被信号打断,而导致函数不断重启。就比如执行到一半退出,又执行,又退,又执行。。那么可能就有问题发生。有两个方法可以解决
1、根本方法,用完全重入函数。如下图所示英文版 P474
2、如果非不得以,那就不让信号来阻断信号函数的运行。可以用SA_RESTART标志把信号先放到屏蔽集的缓冲区里
- SA_RESTART(似乎默认也是这个行为)
- void ouch(int sig)
- {
-
- select()
-
- }
-
- int main(int argc, char **argv)
- {
- struct sigaction act, act_g;
- act.sa_handler = ouch;
- act.sa_flags = SA_RESTART;
- sigemptyset(&act.sa_mask);
- sigaddset(&act.sa_mask, SIGTERM);
-
- if( -1==sigaction(SIGTERM, &act, 0))
- {
- perror("sigaction\n");
- }
-
- select
-
- }
操作如下,首先运行该程序,然后在另一个tty里发送一个kill命令(在第一个select未结束前),那么将进入信号处理函数ouch,接着在ouch的select运行之际,再次发送kill,因为select也不完全函数,将会被再次打断,而又一次进入信号。但是因为设置了SA_RESTART,ouch将不会被打断,而是执行完后,接着执行响应第二次的信号函数。
SA_NODEFER和上面一样的操作,发送第二次KILL信号时,第一个信号函数马上中断(由于是select),又再次进入信号函数。这里加点东西打印可以更清楚些。
流程图以上这两种实现,有个东西起了至关重要的作用。那就是进程的信号屏蔽字。原理流程大概是这样的,当向一个进程发送信号时,可根据是否被屏蔽掉而发送至进程信号屏蔽字集合中或者进程本身。对于后者,sigaction可直接捕捉到,而前者,可以看成是一个暂存的集合,可用sigpending来取得。通常的情况是,已经用了sigaction函数直接获取信号,如果再次触发信号会马上跳出当前正在执行的信号函数而再次执行信号函数。而SA_RESTART这个标志应该是将信号保存到被屏蔽的信号集合里等待下次取出后执行,保证了当前函数的运行。SA_NODEFER就不做这一操作,直接发送到进程,让sigaction继续马上捕捉。
整个走向可看下图,当然这里没有参考系统源码,必然存在着一定的疏忽
目前已知的对屏蔽集的操作有pending/suspend函数,SA_RESTART,SA_NODEFER标志操作。写到这里,把屏蔽集合集看成一个临时的信号存放缓冲区更形象点。