第十章 信号
10.2 信号概念
产生信号的条件:
- 用户按下某些终端键时,引发终端产生的信号
- 硬件异常产生信号(除数为0,无效的内存引用等)
- kill函数/kill命令
- 软件条件
信号的处理:忽略,捕捉,执行系统默认动作
SIGKILL和SIGSTOP无法被忽略或者捕捉,只能执行系统默认动作
信号名称 | 说明 | 默认动作 |
---|---|---|
SIGABRT | 异常终止 | 终止+core |
SIGALRM | 定时器超时(alarm) | 终止 |
SIGBUS | 硬件故障 | 终止+core |
SIGCHLD | 子进程终止/停止 | 忽略 |
SIGCONT | 使暂停进程继续 | 继续/忽略 |
SIGEMT | 硬件故障 | 终止+core |
SIGFPE | 算术运算异常 | 终止+core |
SIGHUP | 连接断开 | 终止 |
SIGILL | 表示进程已执行一条非法硬件指令 | 终止+core |
SIGINT | 中断(Delete/Ctrl+C) | 终止 |
SIGIO(等效于SIGPOLL) | 终止 | |
SIGIOT(等效于SIGABRT) | 终止+core | |
SIGKILL | 杀掉进程 | 终止 |
SIGPIPE | 写至无读进程的管道 | 终止 |
SIGPOLL | 可轮询事件 | 终止 |
SIGPWR | 蓄电池也不能支持工作时候,发送该信号 | 终止 |
SIGQUIT | Ctrl+\类似于SIGINT,但是同时产生core文件 | 终止+core |
SIGSEGV | 无效的内存引用 | 终止+core |
SIGSTOP | 停止 | 停止进程 |
SIGSYS | 指示一个无效的系统调用 | 终止+core |
SIGTERM | 终止 | 终止 |
SIGTRAP | 硬件故障 | 终止+core |
SIGTSTP | Ctrl+Z | 停止进程 |
SIGTTIN | 后台读控制tty | 停止进程 |
SIGTTOU | 后台写向控制tty | 停止进程 |
SIGURG | 紧急情况(套接字) | 忽略 |
SIGUSR1/SIGUSR2 | 用户定义信号 | 终止 |
SIGWINCH | 终端窗口大小改变 | 忽略 |
SIGXCPU | 超过CPU限制 | 终止+core |
SIGXFSZ | 超过软文件长度限制 | 终止+core |
10.3 函数signal
函数原型:
#include
void (*sinal(int signo, void (*fun)(int)))(int);
返回值:成功返回以前的信号处理配置;失败返回SIG_ERR
或者写成:
typedef void sigfunc(int);
sigfunc *signal(int,sigfunc *);
用法如下:
第一个参数为信号
第二个参数为SIG_DFL/SIG_IGN或者函数地址:
SIG_IGN表示忽略该信号
SIG_DFL表示系统默认动作
当为函数地址时,调用该函数处理信号
中:
#define SIG_ERR (void(*)())-1
#define SIG_DFL (void(*)())0
#define SIG_IGN (void(*)())1
程序启动时:
exec函数将原先设置为要捕获的信号都更改为默认动作,其他信号的状态不变
进程创建时:
子进程继承父进程的信号处理方式
10.4 不可靠的信号
早期版本的UNIX系统中,信号是不可靠的。
- 在进程每次接到信号对其进行处理时,随即将该信号动作重置为默认值
int sig_int();
...
signal(SIGINT,sig_int);
...
sig_int()
{
signal(SIGINT,sig_int);
...
}
- 在进程不希望某种信号发生时,它不能关闭该信号。进程能做的就是忽略该信号。
int sig_int();
int sig_int_flag;
...
int main()
{
signal(SIGINT,sig_int);
...
while(sig_int_flag == 0)
pause();
...
}
sig_int()
{
signal(SIGINT,sig_int);
sig_int_flag = 1;
}
10.5 中断的系统调用
早期UNIX系统的一个特性是:
如果进程在执行一个低速系统调用而阻塞期间捕获一个信号,则该系统调用就被中断而不再执行。该系统调用返回出错,其errno设置为EINTR。
低速系统调用
VS 其他系统调用
低速系统调用(可能会使得进程永远阻塞的一类系统调用)包括:
- 如果某些类型文件的数据不存在,则读操作可能会使调用者永远阻塞
- 如果这些数据不嗯给你被相同的类型文件立即接受,则写操作可能会使得调用者永远阻塞
- 在某种条件发生之前打开某些类型文件,可能会发生阻塞
- pause函数和wait函数
- 某些ioctl函数
- 某些进程间通信函数
与被中断的系统调用相关的问题是必须显式地处理出错返回
典型的代码序列(假定进行一个读操作,它被中断,我们希望重新启动它)
again:
if(n = read(fd,buf,BUFFSIZE) < 0){
if(errno == EINTR)
goto again;
}
4.2BSD引进自动重启动的系统调用:ioctl、read、readv、write、writev、wait、waitpid
前五个函数只有对较低速设备进行操作时才会被信号中断
wait和waitpid在捕获信号时总是被中断
10.6 可重入函数
可重入函数是在信号处理程序中保证调用安全的函数
即使信号处理程序调用的是可重入函数,但是对于errno变量要进行处理
10.7 SIGCLD语义
SIGCLD
对SIGCLD的早期处理方式如下:
- 当将该信号的配置设置为SIG_DFL时,即不会理会这一个信号,但是没有调用wait/waitpid,产生了僵死进程
- 当将该信号的配置设置为SIG_IGN时,子进程在终止时的状态被丢弃,即不会产生僵死进程。但是如果父进程调用wait/waitpid,那么它将阻塞到所有子进程都终止,然后wait/waitpid返回-1,并且将errno设置为ECHILD
- 当将该信号的配置设置为信号处理函数的地址,则内核立即检查时候有子进程准备好被等待。注意会产生循环,所以在处理SIGCLD时,应该先wait处理掉了信号信息后,再调用signal。
SIGCHLD在配置信号处理方式时,是不会立即检查是否有子进程准备好被扽带,也不会在此时调用信号处理函数。
10.8 可靠信号术语和语义
- 当造成信号的事件(硬件异常、软件条件、终端产生的条件、调用kill函数)发生时,为进程产生一个信号
- 递送:当一个信号产生时,内核在进程表中以某种形式设置一个标志
- 信号未决:信号处于产生和递送之间的时间间隔内
- 阻塞信号递送:若进程产生一个阻塞的信号,而且对该信号的动作是系统默认动作/捕获该信号,则该进程将此信号保持为未决状态,直到该进程对此信号解除阻塞/对此信号的动作更改为忽略。sigpending函数可以判定信号是否被设置为阻塞并处于未决状态
- 信号屏蔽字:规定了当前要阻塞递送到该进程的信号集(sigset_t)
10.9 kill 和 raise 函数
#include
int kill (pid_t pid, int signo);//将信号发送给进程/进程组
int raise(int signo);//进程向自身发送信号
若成功,返回0;出错,返回-1
raise(signo); == kill(getpid(),signo);
kill的pid参数有四种情况:
- pid>0 将信号发送给进程ID为pid的进程
- pid==0 将信号发送给与发送进程属于同意进程组的所有进程,而且发送进程要具有权限向这些进程发送信号。这里的所有进程不包括
系统进程集
(内核进程和init(pid==1)) - pid<0 将信号发送给进程组ID等于|pid|,而且发送进程要具有权限向这些进程发送信号。这里的所有进程不包括
系统进程集
(内核进程和init(pid==1)) - pid==-1 将信号发送给发送进程有权限向他们发送信号的所有进程
关于权限
- 超级用户可将信号发送给任一进程
- 非超级用户:发送者的实际用户ID/有效用户ID == 接收者的实际用户ID/有效用户ID。
EXCEPT:
若实现支持_POSIX_SAVED_IDS,则检查接受者的保存设置用户ID(而不是有效用户ID) - 若被发送的信号是SIGCONT,则进程可将它发送给属于同一对话的任一其他进程
信号编号0定义为空信号。
若signo参数是0,则kill仍执行正常的错误检查,但不发送信号。(该用途常被用作确定一个特定进程是否存在。若检查的进程并不存在,则kill返回-1,errno被设置为ESRCH。即使kill返回0,也不能保证一定存在该进程。
10.10 alarm 和 pause 函数
#include
unsigned int alarm(unsigned int seconds);
10.11 信号集
#include
int sigemptyset(sigset_t *set);
int sigfillset(siget_t *set);
int sigaddset(siget_t *set,int signo);
int sigdelset(siget_t *set,int signo);
若成功返回0;出错返回-1
int sigismember(const siget_t *set,int signo);
真返回1,假返回0
假如用int类型来实现
#define sigemptyset(ptr) (*(ptr) = 0)
#define sigfillset(ptr) (*(ptr) = ~(sigset_t)0,0)
#include
#include
#define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG)
int sigaddset(sigset_t *set, int signo)
{
if(SIGBAD(signo)){
errno = EINVAL;
return(-1);
}
*set |= 1 << (signo - 1);
return(0);
}
10.12 sigprocmask函数
#include
int sigprocmask(int how,const sigset_t * restrict set,sigset_t *restrict oset);
成功返回0;错误返回-1
- 首先,oset如果是非空指针,那么进程的当前信号屏蔽字通过oset返回
- 其次,如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
|how|说明|
|:---:|:---:|
|SIG_BLOCK|该进程新的信号屏蔽字是当前信号屏蔽字和set指向的信号集的并集。于是set包含了希望阻塞的附加信号|
|SIG_UNBLOCK|该进程新的信号屏蔽字是当前信号屏蔽字和set所指向的信号集补集的交集。于是set包含了希望解除阻塞的信号|
|SIG_SETMASK|该进程新的信号屏蔽字是set所指向的值| - 如果set是空指针,则不改变该进程的信号屏蔽字,how也随之没有意义
10.13 sigpending函数
#include
int sigpending(sigset_t *set);//set返回被阻塞不能递送的信号集
成功返回0;出错返回-1