《Unix环境高级编程》读书笔记 第10章-信号
2014-08-31 23:23
351 查看
1.引言
信号是软件中断。
信号提供了一种处理
异步事件的方法。
2. 信号概念
信号的名字都是以3个字符SIG开头。
Linux3.2.0支持31种信号。FreeBSD、Linux和Solaris作为实时扩展都支持另外的应用程序定义的信号。
在头文件signal.h(其中include的bits/signum.h)中,信号名都被定义为正整数常量,不存在编号为0的信号。kill函数对信号编号0有特殊的应用。
很多条件可以产生信号:
用户按下某些终端键时:Ctrl+C、Ctrl+\、Ctrl+Z
硬件异常产生信号:除数为0、无效的内存引用
进程调用kill函数可将任意信号发送给另一个进程或进程组
当检测到某些软件条件已经发生,并应将其通知有关进程时产生信号。如:SIGURG(网络连接上传来带外数据)、SIGPIPE(在管道的读进程已经终止后,一个进程写此管道)、SIGALRM(进程所设置的定时器超时)
硬件异常产生信号:除数为0、无效的内存引用
进程调用kill函数可将任意信号发送给另一个进程或进程组
当检测到某些软件条件已经发生,并应将其通知有关进程时产生信号。如:SIGURG(网络连接上传来带外数据)、SIGPIPE(在管道的读进程已经终止后,一个进程写此管道)、SIGALRM(进程所设置的定时器超时)
信号是异步事件的经典实例。产生信号的事件对进程而言是随机出现的。进程不能简单地测试一个变量(如errno)来判断是否发生了一个信号,而是必须告诉内核“在此信号发生时,请执行下列操作”。
当某个信号出现时,可以告诉内核按下列3种方式之一进行处理,称之为
信号的处理
忽略此信号。SIG_IGN。只有两种信号不能被忽略:SIGKILL和SIGSTOP。原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(如非法内存引用或除以0),则进程的运行行为是未定义的。
捕捉信号。即通知内核在某种信号发生后,调用一个用户函数。
执行系统默认动作。对大多数信号的默认动作是终止该进程。
捕捉信号。即通知内核在某种信号发生后,调用一个用户函数。
执行系统默认动作。对大多数信号的默认动作是终止该进程。
终止+core。大多数Unix系统调试程序都使用core文件检查进程终止时的状态。
在下列条件下不产生core文件:
进程是设置用户ID的,而且当前用户并非程序文件的所有者
进程是设置组ID的,而且当前用户并非程序文件的组所有者
用户没有写当前工作目录的权限
文件已存在,而且用户对该文件没有写权限
进程是设置组ID的,而且当前用户并非程序文件的组所有者
用户没有写当前工作目录的权限
文件已存在,而且用户对该文件没有写权限
4个平台对各种signal的支持及默认处理方式
![](http://images.cnitblog.com/blog/404381/201408/312314247206701.png)
主要信号简要说明:
SIGABRT。调用abort函数时产生此信号。
SIGALRM。当用alarm函数设置的定时器超时时,产生此信号。
SIGCHLD。在一个进程终止或停止时,该信号被送给其父进程。按系统默认,将忽略此信号。
SIGFPE。表示算术运算异常,如除以0、浮点溢出等。
SIGHUP。如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)。通常使用此信号通知守护进程再次读取它们的配置文件。选用此信号的理由是:守护进程不会有控制终端,通常决不会接收到这种信号。
SIGILL。表示进程执行一条非法硬件指令。
SIGINT。当用户按下中断键Ctrl+C时,终端驱动程序产生此信号并发送至前台进程组的每一个进程。
SIGIO。指示一个异步I/O事件。
SIGTERM。由kill命令发送的系统默认终止信号。
SIGKILL。不能捕获或忽略。它向管理员提供了一红杀死任一进程的可靠方法。
SIGPIPE。如果在管道的读进程已终止时写管道,则产生此信号。
SIGQUIT。当用户在终端上按下退出键Ctrl+\时,终端驱动程序产生此信号并发送给前台进程组中的所有进程。此信号除了终止前台进程组(和SIGINT一样),同时产生一个core文件。
SIGSEGV。指示进程进行了一次无效的内存引用。
SIGTSTP。交互停止信号。当用户在终端上按下挂起键Ctrl+Z时,终端驱动程序产生此信号,并发送至前台进程组的所有进程。
SIGSTOP。类似于交互停止信号(SIGTSTP),但它不能被捕获或忽略。
SIGCONT。此作业控制信号发送给需要继续运行,但当前处于停止状态的进程。
SIGTTIN。当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。下列情况例外:1. 读进程忽略或阻塞此信号;2. 读进程所属的进程组是孤儿进程组,此时读操作返回出错,errno设置为EIO
SIGTTOU。当一个后台进程组进程试图写其控制终端时,终端驱动程序产生此信号。
SIGURG。通知进程已经发生一个紧急情况。如带外数据到达。
SIGUSR1、SIGUSR2。用户定义的信号,可用于应用程序。
SIGALRM。当用alarm函数设置的定时器超时时,产生此信号。
SIGCHLD。在一个进程终止或停止时,该信号被送给其父进程。按系统默认,将忽略此信号。
SIGFPE。表示算术运算异常,如除以0、浮点溢出等。
SIGHUP。如果终端接口检测到一个连接断开,则将此信号送给与该终端相关的控制进程(会话首进程)。通常使用此信号通知守护进程再次读取它们的配置文件。选用此信号的理由是:守护进程不会有控制终端,通常决不会接收到这种信号。
SIGILL。表示进程执行一条非法硬件指令。
SIGINT。当用户按下中断键Ctrl+C时,终端驱动程序产生此信号并发送至前台进程组的每一个进程。
SIGIO。指示一个异步I/O事件。
SIGTERM。由kill命令发送的系统默认终止信号。
SIGKILL。不能捕获或忽略。它向管理员提供了一红杀死任一进程的可靠方法。
SIGPIPE。如果在管道的读进程已终止时写管道,则产生此信号。
SIGQUIT。当用户在终端上按下退出键Ctrl+\时,终端驱动程序产生此信号并发送给前台进程组中的所有进程。此信号除了终止前台进程组(和SIGINT一样),同时产生一个core文件。
SIGSEGV。指示进程进行了一次无效的内存引用。
SIGTSTP。交互停止信号。当用户在终端上按下挂起键Ctrl+Z时,终端驱动程序产生此信号,并发送至前台进程组的所有进程。
SIGSTOP。类似于交互停止信号(SIGTSTP),但它不能被捕获或忽略。
SIGCONT。此作业控制信号发送给需要继续运行,但当前处于停止状态的进程。
SIGTTIN。当一个后台进程组进程试图读其控制终端时,终端驱动程序产生此信号。下列情况例外:1. 读进程忽略或阻塞此信号;2. 读进程所属的进程组是孤儿进程组,此时读操作返回出错,errno设置为EIO
SIGTTOU。当一个后台进程组进程试图写其控制终端时,终端驱动程序产生此信号。
SIGURG。通知进程已经发生一个紧急情况。如带外数据到达。
SIGUSR1、SIGUSR2。用户定义的信号,可用于应用程序。
3. 函数signal
#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);
Returns: previous disposition of signal (see following) if OK, SIG_ERR on error
[/code]signal函数由ISO C定义。不涉及多进程、进程组以及终端I/O等,所以它对信号的定义非常含糊,以致于对Unix系统而言几乎毫无用处。
因为signal的语义与实现有关,所以最好使用sigaction函数代替signal函数。
本书中的所有实例均使用图10-18中给出的signal函数,该函数使用sigaction函数是一个平台无关、语义一致的实现。
signo参数是上面的信号名。func参数可以是常量SIG_IGN、SIG_DFL或接收到该信号后要调用的函数的地址,即信号处理程序的地址。signal函数的返回值是指向在此之前的信号处理程序的指针。
typedef void Sigfunc(int);
Sigfunc* signal(int, Sigfunc*);
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)())0
#define SIG_IGN (void (*)())1
[/code]exec,程序启动
当exec执行一个程序时,所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号(则继续保持忽略)。也就是说,exec函数将原先设置为要捕获的信号都更改为默认动作,其他保持不变。因为当exec一个新程序时,信号处理程序的地址很可能在新程序中已无意义。
fork,进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。因为信号处理程序的地址在子进程中是有意义的。
4. 不可靠的信号
早期的Unix版本中,信号是不可靠的。不可靠指的是,信号可能会丢失:一个信号发生了,当进程却可能一直不知道。同时,进程对信号的控制能力很差,它能捕获或忽略它,但不能阻塞。早期版本的另一个问题是:在进程每次接到信号对其进行处理时,随即将该信号动作重置为默认值。故需要再次建立对该信号的捕获,但在此期间有一个时间窗口。
早期版本的另一个问题是:在进程不希望某种信号发生时,它不能关闭该信号,只能忽略它。
5. 中断的系统调用
早期Unix系统的一个特性是:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续运行。该系统调用返回出错,其errno设置为EINTR。为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。
低速系统调用是可能会使进程永远阻塞的一类系统调用。
与被中断的系统调用相关的问题是必须显式地处理出错返回。典型的代码序列如下:
again:
if ((n = read(fd, buf, BUFFSIZE)) < 0) {
if (errno == EINTR)
goto again; /* just an interrupted system call */
/* handle other errors */
}
[/code]4.2 BSD引进了某些被中断系统调用的自动重启动,包括ioctl、read、readv、write、writev、wait、waitpid。但是这种自动重启动的处理方式也会带来问题,某些应用程序并不希望这些函数被中断后重启动。为此,4.3 BSD运行进程基于每个信号禁用此功能。
POSIX.1要求只有中断信号的SA_RESTART标志有效时,实现才重启动系统调用。
历史上,使用signal函数建立信号处理程序时,对于如何处理被中断的系统调用,各种实现的做法各不相同。
![](http://images.cnitblog.com/blog/404381/201408/312314476571228.png)
6. 可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。但是,在信号处理程序中,不能判断捕捉到信号时进程执行到何处:如果进程正在执行malloc,而在信号处理程序中又再次调用malloc,这时会?
如果进程正在执行getpwnam,这是将其结果存放在静态存储单元中的函数,而在信号处理程序中又再次调用getpwnam,这时会?
如果进程正在执行getpwnam,这是将其结果存放在静态存储单元中的函数,而在信号处理程序中又再次调用getpwnam,这时会?
SUS说明了在信号处理程序中保证调用安全的函数。这些函数是
可重入的,并被称为
异步信号安全的。
![](http://images.cnitblog.com/blog/404381/201408/312315026886402.png)
没有列入上图的大多数函数是
不可重入的,因为:
它们使用静态数据结构
它们调用malloc或free
它们是标准的I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。
它们调用malloc或free
它们是标准的I/O函数。标准I/O库的很多实现都以不可重入方式使用全局数据结构。
应当了解,即使信号处理程序调用的是上图中的函数,但是由于每个线程只有一个errno变量,所以信号处理程序可能会修改其原先值。故作为一个通用的规则,先保存,后恢复。
7. SIGCLD语义
8. 可靠信号术语和语义
首先,当造成信号的事件发生时,向进程发送一个信号。当对信号采取了某种动作时,我们说向进程
递送了一个信号。在信号产生和递送之间的时间间隔内,称信号是
未决的。
进程可以选用“阻塞信号递送”。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。因此,进程在信号递送给它之前仍可改变对该信号的动作。
每个进程都有一个
信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。进程可以调用
sigprocmask函数来检测和更改其当前信号屏蔽字。
进程调用
sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
如果在进程解除对某个信号的阻塞之前,该信号发生了多次,那么?如果递送该信号多次,则称这些信号进行了
排队。除非支持POSIX.1实时扩展,否则大多数Unix并不对信号排队,而只递送一次。
9. 函数kill和raise
kill函数将信号发送给进程或进程组raise函数则允许进程向自身发送信号。
#include <signal.h>
int kill(pid_t pid, int signo);
int raise(int signo);
Both return: 0 if OK, −1 on error
[/code]raise(signo); 等价于 kill(getpid(), signo);
kill的pid参数有以下4种情况:
pid > 0,发送给进程ID为pid的进程
pid == 0,发送给与发送进程属于同一进程组的所有进程
pid < 0,发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程
pid == -1,发送给发送进程具有权限向它们发送信号的所有进程
pid == 0,发送给与发送进程属于同一进程组的所有进程
pid < 0,发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程
pid == -1,发送给发送进程具有权限向它们发送信号的所有进程
关于发送信号的权限
超级用户可将信号发送给任一进程。
非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。特例:如果被发送的信号是SIGCONT,则进程可以将它发送给属于同一会话的任一其他进程。
非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。特例:如果被发送的信号是SIGCONT,则进程可以将它发送给属于同一会话的任一其他进程。
POSIX.1 将信号编号为0定义为
空信号。如果signo参数为0,则kill仍执行正常的错误检查,当不发送信号。这常被用来确定一个特定进程是否仍然存在。但是,在返回测试结果时,原来存在的被测试进程可能已经终止,所以这种测试并无多大意义。
10. 函数alarm、pause
当定时器超时时,产生SIGALRM信号。信号由内核产生。#include <unistd.h>
unsigned int alarm(unsigned int seconds);
Returns: 0 or number of seconds until previously set alarm
[/code]每个进程只能有一个闹钟时间。多次调用alarm以新值代替旧值,并返回旧值的余留值。参数为0,则取消以前的闹钟。
pause函数使调用进程挂起直到捕捉到一个信号
#include <unistd.h>
int pause(void);
Returns: −1 with errno set to EINTR
[/code]只有执行了一个信号处理程序并从其中返回时,pause才返回。返回-1,errno设置为EINTR。
11. 信号集
POSIX.1定义数据类型sigset_t包含一个信号集,并定义以下5个处理信号集的函数#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
All four return: 0 if OK, −1 on error
int sigismember(const sigset_t *set, int signo);
Returns: 1 if true, 0 if false, −1 on error
[/code]
12. 函数sigprocmask
调用函数sigprocmask可以检测或更改,或同时检测和更改进程的信号屏蔽字#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
Returns: 0 if OK, −1 on error
[/code]how参数:SIG_BLOCK、SIG_UNBLOCK、SIG_SETMASK
在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在其返回之前,至少将其中之一递送给该进程。
13. 函数sigpending
sigpending函数返回一信号集,它对于调用进程而言,其中的各信号是阻塞不能递送的#include <signal.h>
int sigpending(sigset_t *set);
Returns: 0 if OK, −1 on error
[/code]
14. 函数sigaction
sigaction函数的功能是检查或修改与指定信号相关联的处理动作#include <signal.h>
int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);
Returns: 0 if OK, −1 on error
[/code]
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 */
void (*sa_sigaction)(int, siginfo_t *, void *); /* alternate handler */
};
[/code]
![](http://images.cnitblog.com/blog/404381/201408/312315222821017.png)
原创文章,转载请声明出处:/article/7049541.html
相关文章推荐
- 《unix环境高级编程》-10.信号-读书笔记
- 《UNIX环境高级编程》读书笔记之信号(1)
- UNIX信号(signal)编程 - UNIX高级环境编程第10章读书笔记
- 《UNIX环境高级编程》读书笔记之信号(2)
- UNIX信号(signal)编程 - UNIX高级环境编程第10章读书笔记
- UNIX信号(signal)编程 - UNIX高级环境编程第10章读书笔记
- 《UNIX环境高级编程》第10章 信号
- 《Python语言及其应用》读书笔记(9)之第10章系统(关键词:系统)
- 【读书笔记】linux信号
- 《UNIX环境高级编程》四文件和目录读书笔记
- 《UNIX环境高级编程》六系统数据文件和信息读书笔记
- 《UNIX环境高级编程》十二线程控制读书笔记
- 《UNIX环境高级编程》十七高级进程间通信读书笔记
- 《.NET4.0 面向对象编程漫谈》读书笔记 ——第10章 异步编程模式
- UNIX环境高级编程-第10章- 信号 - 一
- 《Android开发艺术探索》读书笔记 (10) 第10章 Android的消息机制
- 《UNIX环境高级编程》第11章线程【读书笔记】
- C++ Primer 读书笔记:第10章 关联容器
- 《UNIX环境高级编程》读书笔记之文件和目录(1)