ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 进程与信号(五)

进程与信号(五)

原创 Linux操作系统 作者:mylxiaoyi 时间:2009-08-10 19:16:00 0 删除 编辑

健壮的信号接口

我们已经讨论了使用signal来发出与捕获信号,因为他们在较为旧的Unix程序中很常见。然而,X/Open与Unix规范推荐了一个更为健壮的用于信号处理的新的编程接口:sigaction。

#include
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact);

sigaction结构定义在signal.h中,这个结构用于定义依据由sig所指定的信号而所要采取的动作,而且这个结构至少有以下几个成员:

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

sigaction函数设置也sig信号相关联的动作。如果oact不为空,sigaction就会将前一个信号动作写入他所指向的位置。如果act为空,这就是sigaction所做的所有事情。如果act不为空,就会设置指定信号的动作。

作为信号,如果成功,sigaction就会返回0,否则返回-1。如果指定的信号不存在或是尝试捕获或忽略不能捕获或是忽略的信号时,错误变量errno就会被设置EINVAL。

在参数act所指向的sigaction结构内部,sa_handler是一个指针,指向当接收到sig信号时所调用的函数。这与我们在前面所见到过的传递给signal的函数func相类似。我们可以在sa_handler域使用特殊值SIG_IGN与SIG_DFL分别表示要忽略此信号或是重新载入默认动作。

sa_mask域指定了一个在sa_handler函数调用之前要添加到进程的信号掩码中信号集合。有一些被屏蔽或是不会传递给进程的信号集合。这就会阻止了我们在前面所看到的在处理器函数运行完成之前接收到信号的情况。使用sa_mask域可以减少竞争条件。

然而,由sigaction所设置的处理器捕获的信号默认情况下并不会重新设置,而如要我们希望获得我们在前面所看到的信号行为,那么就必须设置sa_flags域来包含SA_RESETHAND值。在我们详细深入了解sigaction之前,让我们来使用sigaction来替换信号重新编写ctrlc.c程序。

试验--sigaction

进行如下的修改,从而SIGINT可以被sigaction所解释。我们将这个新程序称之为ctrlc2.c。

#include
#include
#include
void ouch(int sig)
{

printf(“OUCH! - I got signal %d\n”, sig);
}
int main()
{
struct sigaction act;
act.sa_handler = ouch;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGINT, &act, 0);
while(1) {
printf(“Hello World!\n”);
sleep(1);
}
}

当我们运行这个版本的程序时,当我们按下Ctrl+C时我们总会得到一条消息,因为SIGINT信号反复的被sigaction处理。要结束这个程序,我们必须按下Ctrl+-\,这在默认情况下会产生SIGQUIT信号。

$ ./ctrlc2
Hello World!
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
^C
OUCH! - I got signal 2
Hello World!
Hello World!
^\
Quit
$

工作原理

这个程序调用sigaction而不是signal来将Ctrl+C(SIGINT)的信号处理器设置为函数ouch。首先需要设置一个包含处理器,信号掩码与标志的sigaction结构。在这个例子中,我们并不需要任何标志,并且使用一个新函数sigemptyset设置一个空的信号掩码。

信号集合

头文件signal.h定义了类型sigset_t以及用来操作信号集合的函数。这个集合用在sigaction结构与其他的函数中,用来依据信号修改进程行为。

#include
int sigaddset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigdelset(sigset_t *set, int signo);

这些函数的执行他们的名字所显示的操作。sigemptyset将一个信号集合初始化为一个空集合。sigfillset将一个信号集合初始化为一个包含所有定义的信号的集合。sigaddset与sigdelset由一个信号集合中添加和删除一个指定的信号(signo)。这些函数都会在成功时返回0,而失败时返回-1,并且设置errno变量。如果指定的信号不可用,那么唯一的错误就是EINVAL。

函数sigismember确定一个指定的信号是否是一个信号集合中的成员。如果此信号是信号集合中的一员,函数就会返回1,如果不是则会返回0,而如果信号不可用,则返回-1,并且将errno设置为EINVAL。

#include
int sigismember(sigset_t *set, int signo);

进程信号掩码是通过调用函数sigprocmask来设置或是检测的。信号掩码是当前被屏蔽掉的信号集合,因而并不会当前的进程接收到。

#include
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);

sigprocmask函数会根据how参数以多种方式来改变进程信号掩码。如果参数set不为空,那么新的信号掩码值可以通过set传入,而前一个信号掩码值被写入信号集合oset。

how参数如下:

SIG_BLOCK set中的信号都被加入到信号掩码中
SIG_SETMASK 由set中设置信号掩码
SIG_UNBLOCK set中的信号由信号掩码中移除

如果set参数是一个空指,那么how参数并不会被使用,而这个调用的唯一作用就是获取当前的信号掩码值并写入oset。

如果函数调用成功,sigprocmask会返回0,如果how参数不可用则返回-1,并且将errno设置为EINVAL。

如果一个信号被一个进程屏蔽,那么他就不会被传输,但是仍保持等待状态。一个程序可以通过调用函数sigpending来他的被屏蔽的信号哪些处理等待状态。

#include
int sigpending(sigset_t *set);

这会输出一个被阻止传输的信号集合并且添加到由set所指向的信号集合中。如果成功会返回0,否则会返回-1,并且设置errno来表示错误。当一个程序需要处理信号并且要控制何时调用处理函数时,这个函数会很有用。

一个进程可以挂起执行直到通过调用sigsuspend传输一个信号。这就是我们在前面常见到的暂停功能的常见形式:

#include
int sigsuspend(const sigset_t *sigmask);

sigsuspend函数使用由sigmask信号集合来替换进程信号掩码,然后挂起执行。他会在信号处理函数执行之后恢复执行。如果接收到的信号终止程序,sigsuspend函数就不会再返回。如果接收的信号没有终止程序,sigsuspend函数会返回-1,并且将errno设置为EINTR。

sigaction标记

用于sigaction中的sigaction结构的sa_flags域也许会包含下列值来修改信号行为:

SA_NOCLDSTOP 当子进程停止时不要生成SIGCHLD信号
SA_RESETHAND 当收到信号时将信号动作重设为SIG_DFL
SA_RESTART 重新启动可中断函数而不是以EINTR报错
SA_NODEFER 当捕获时不要将信号添加到信号掩码中

SA_RESETHAND可以用于当捕获一个信号时自动清除一个信号函数,正如我们在前面所看到的。

程序所用的许多系统调用都是可中断的;也就是说,当他们接收到的一个信号时,他们会返回一个错误,并且将errno设置为EINTR表明函数是因为信号而返回的。这个行为对于使用信号的程序要格外小心。如果在sigaction调用中的sa_flags域被设置为SA_RESTART,那么这个函数就不会被中断,而在信号处理函数被执行时重新启动。

通常而言,当一个信号处理函数正在执行时,所接收到的信号会在信号处理函数执行期间添加到进程的信号掩码中。这是为了阻止后面发生了同样的信号,从而使得信号处理函数再次运行。如果函数不是可重入的,在他完成处理之前为另一个信号的发生所调用,那么前一个就会出现问题。然而,如果调用了SA_NODEFER标记,当接收到这个信号时,信号掩码并不会提示。

信号处理函数可以在执行期间被中断,并且在其他的函数再次调用。当我们返回到的前一个调用时,他可正常运行是很重要的。他不仅是递归(调用自身),而是重入(无问题的进入并再次运行)。在内核中同时处理多个设备的中断服务例程需要是可重入的,因为在相同代码的执行期间也许会有一个更高优先级的中断进入。

下面列出的是X/Open规范认为在一个信号处理器内部是可以安全调用的,他们或者是可重入的或者是自身不会发出信号。

access alarm cfgetispeed cfgetospeed
cfsetispeed cfsetospeed chdir chmod
chown close creat dup2
dup execle execve _exit
fcntl fork fstat getegid
geteuid getgid getgroups getpgrp
getpid getppid getuid kill
link lseek mkdir mkfifo
open pathconf pause pipe
read rename rmdir setgid
setpgid setsid setuid sigaction
sigaddset sigdelset sigemptyset sigfillset
sigismember signal sigpending sigprocmask
sigsuspend sleep stat sysconf
tcdrain tcflow tcflush tcgetattr
tcgetpgrp tcsendbreak tcsetattr tcsetpgrp
time times umask uname
unlink utime wait waitpid
write

通用信号参考

在这一部分,我们会列出Linux与Unix程序默认行为通常需要的信号。

下表所列出的信号默认动作都是使用_exit的进程非正常终止(类似于exit,但是在返回内核之前并没有执行任何清理)。然而,状态可以用于wait,而waitpid可以通过特定的信号来表明非正常的终止。

信号名 描述
SIGALRM 由alarm函数所调用的计时器生成的信号
SIGHUP 通过非连接终端发往控制进程,或是通过终端控制进程发往前台进程
SIGINIT 通常是在终端由Ctrl+C或是配置的中断字符生成的信号
SIGKILL 通常由shell使用来强制终止一个不确定进程,因为这个信号是不能被捕获或是忽略的
SIGPIPE 尝试写入一个没有相应读端管道时生成的信号
SIGTERM 作为一个请求进程完成的请求发送。Unix用于停机的情况来请求系统服务停止。这是由kill命令所发送的默认信号。
SIGUSR1,SIGUSR2 也许会被进程用于彼此通信,可能使得他们报告状态信息

默认情况下,下表中的信号也许会引起非正常终止。另外,与实现相关的动作,例如也许会发生创建核心文件的情况。

信号名 描述
SIGFPE 由浮点算术异常生成的信号
SIGILL 处理器执行了一条非法指令。通常是由一个恶意程序或是不正确的共享内存模块引起的
SIGQUIT 通常是在终端输入Ctrl+\或是配置的退出字符发出的信号
SIGSEGV 段错误,通常是由读写非法内存地址(或者是超出数组边界或是引用非法指针)引起的。覆盖一个局部数组变量并破坏堆栈,从而使得函数返回非法地址,引起SIGSEGV信号。

进程默认情况下接收到下表中的信号时会挂起执行。

信号名 描述
SIGSTOP 停止执行(不能被捕获或是忽略)
SIGTSTP 终端停止信号,通常是由Ctrl+Z引起的
SIGTTIN,SIGTTOU shell用来表明后台作业已经停止,因为他们需要由终端读取或是产生输出

SIGCONT可以使得一个停止进程重新启动,如果被一个并没有停止的进程接收到,则会被忽略。SIGCHLD默认会被忽略。

信号名 描述
SIGCONT 如果停止,继续执行
SIGCHLD 当一个子进程停止或是退出时生成信号

小结

在这一章,我们了解了进程如何成为Linux操作系统的基础部分。我们了解了他们如何启动,终止,查看,并且我们如何使用他们来解决编程问题。我们也了解了可以用于控制运行程序动作的信号,事件。我们了解到所有的Linux进程,包括init,使用与程序可用的相同的系统调用集合。

已有 0 人发表留言,猛击->>这里<<-参与讨论 JavaEye推荐

Link URL: http://mylxiaoyi.javaeye.com/blog/407661

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/13670711/viewspace-611806/,如需转载,请注明出处,否则将追究法律责任。

上一篇: POSIX线程(一)
请登录后发表评论 登录
全部评论

注册时间:2008-07-09

  • 博文量
    25
  • 访问量
    11097