信号概念
信号是软件中断。
产生信号的条件有:
(1)当用户按某些终端键时,引发终端产生的信号。如Ctrl+C引发SIGINT。
(2)硬件异常产生信号:除数为0、无效内存的引用等等。
(3)进程调用kill(2)函数可将信号发送给另一个进程或进程组。
(4)用户可用kill(1)命令将信号发送给其他进程。
(5)当检测到某种软件条件已经发生,并应将其通知有关进程时也产生信号。例如SIGURG、SIGPIPE、SIGALRM。
内核处理信号的方式有:
(1)忽略此信号。
(2)捕捉信号。
(3)执行系统默认动作。
signal函数
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
不可靠的信号
在早起的unix版本(例如v7)中,信号是不可靠的。不可靠指的是信号可能会丢失。
早期版本中的一个问题是在进程每次接到信号对其进行处理时,随即将该信号动作复位为默认值。
例如下面代码:
int my_sigint()
{signal(SIGINT, my_sigint);
}
signal(SIGINT, my_sigint);
这段代码的一个问题是:从信号发生之后到在信号处理程序中调用signal函数之前这段时间中有一个时间窗口。
在此段时间中,可能发生另一次中断信号。第二个信号会导致执行默认动作,而针对中断信号的默认动作是终止该进程。
早期系统的另一个问题时:在进程不希望某种信号发生时,它不能关闭该信号。
再看下面代码:
int sigint_flag;
int my_sigint(){
signal(SIGINT, my_sigint);
sigint_flag = 1;
}
int main()
{signal(SIGINT, my_sigint);
while (sigint_flag == 0)
pause();
}
这段代码存在的问题是:从测试sigint_flag之后到调用pause之前这段时间中有一个时间窗口,如果在此段时间中发生信号,则此进程在调用pause时入睡,并且长眠不醒。
可重入函数
函数不可重入的原因有:(a)已知它们使用静态数据结构,(b)它们调用malloc或free,或(c)它们是标准I/O函数。
可靠信号术语和语义
当对信号采取了这种动作时,我们说向进程递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是未决的(pending)。
进程可以选用信号递送阻塞。如果为进程产生了一个选择为阻塞的信号,而且对该信号的动作时系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程(a)对此信号解除了阻塞,或者(b)将此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时,才决定对它的处理方式。于是进程在信号递送给它之前仍可改变对该信号的动作。
POSIX.1允许系统递送一个信号一次或多次。如果递送该信号多次,则称对这些信号进行了排队。但是除非支持POSIX.1实时拓展,否则大多数unix并不对信号排队,只递送该信号一次。POSIX.1没有规定多个信号的递送顺序。
每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。对于每种可能的信号,该屏蔽字中都有一位与之对应。
kill和raise函数
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
alarm和pause函数
使用alarm函数可以设置一个计时器,当计时器超时时,产生SIGALRM信号。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
每个进程只能有一个闹钟时钟。如果在调用alarm时,以前已为该进程设置过闹钟时钟,而且它还没有超时,则将该闹钟时钟的余留值作为本次alarm函数的返回值。
如果有以前为进程登记的尚未超过的闹钟时钟,而且本次调用的seconds值是0,则取消以前的闹钟时钟,其余留值仍作为alarm函数的返回值。
pasue函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void);
只有执行了一个信号处理程序并从其返回时,pause才返回。在这种情况下,pause返回-1,并将error设置为EINTR.
信号集
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set);
int sigdelset(sigset_t *set);
int sigismember(const sigset_t *set, int signo);
sigprocmask函数
调用sigprocmask函数可以检测或更改其信号屏蔽字,或者在一个步骤中同时执行这两个操作。
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程。
sigpending函数
sigpending函数返回信号集,其中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是当前未决的。
#include <signal.h>
int sigpending(sigset_t *set);
sigaction函数
sigaction函数的功能是检查或修改与指定信号相关联的处理动作。此函数取代了unix早期版本使用的signal函数。
#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
其中,参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。
此函数使用下列结构:
struct sigaction {void (*sa_handler)(int);
sigset_t sa_mask;
in sa_flag;
void (*sa_sigaction)(int, siginfo_t *, void *);
};
当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址,则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。
仅当从信号捕捉函数返回时再将进程的信号屏蔽字复位为原先值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号。
因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。
一旦对给定的信号设置了一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效。
act结构的sa_flags字段指定对信号进行处理的各个选项。
sa_sigaction字段是一个替代的信号处理程序,当在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。
sigsuspend函数
sigsuspend可以在一个原子操作中先恢复信号屏蔽字,再使进程休眠。
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
将进程的信号屏蔽字设置为sigmask指向的值。在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且将该进程的信号屏蔽字设置为调用sigsuspend之前的值。
abort函数
#include <stdlib.h>
void abort(void);
此函数将SIGABRT信号发送给调用进程。ISO C要求若捕捉到此信号而且相应信号处理程序返回,abort仍不会返回其调用者。如果捕捉到此信号,则信号处理程序不能返回的唯一方法是它调用exit、_exit、_Exit、longjmp或siglongjmp。
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作。如果进程并不在信号处理程序中终止自己,POSIX.1声明当信号处理程序返回时,abort终止该进程。
sleep函数
sleep函数的可靠实现
static void sig_alrm(int signo){
}
unsigned int sleep(unsigned int nsecs)
{
struct sigaction newact, oldact;
sigset_t newmask, oldmask, suspmask;
unsigned int unslept;
newact.sa_handler = sig_alrm;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGALRM, &newact, &oldact);
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
alarm(nescs);
suspmask = oldmask;
sigdelset(&suspmask, SIGALRM);
sigsuspend(&suspmask);
unslept = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return (unslept);
}