2017-08-18

第十章 信号


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系统中,信号是不可靠的。

  1. 在进程每次接到信号对其进行处理时,随即将该信号动作重置为默认值
int sig_int();
...
signal(SIGINT,sig_int);
...
sig_int()
{
    signal(SIGINT,sig_int);
    ...
}
  1. 在进程不希望某种信号发生时,它不能关闭该信号。进程能做的就是忽略该信号。
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的早期处理方式如下:

  1. 当将该信号的配置设置为SIG_DFL时,即不会理会这一个信号,但是没有调用wait/waitpid,产生了僵死进程
  2. 当将该信号的配置设置为SIG_IGN时,子进程在终止时的状态被丢弃,即不会产生僵死进程。但是如果父进程调用wait/waitpid,那么它将阻塞到所有子进程都终止,然后wait/waitpid返回-1,并且将errno设置为ECHILD
  3. 当将该信号的配置设置为信号处理函数的地址,则内核立即检查时候有子进程准备好被等待。注意会产生循环,所以在处理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参数有四种情况:

  1. pid>0 将信号发送给进程ID为pid的进程
  2. pid==0 将信号发送给与发送进程属于同意进程组的所有进程,而且发送进程要具有权限向这些进程发送信号。这里的所有进程不包括系统进程集(内核进程和init(pid==1))
  3. pid<0 将信号发送给进程组ID等于|pid|,而且发送进程要具有权限向这些进程发送信号。这里的所有进程不包括系统进程集(内核进程和init(pid==1))
  4. 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
  1. 首先,oset如果是非空指针,那么进程的当前信号屏蔽字通过oset返回
  2. 其次,如果set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
    |how|说明|
    |:---:|:---:|
    |SIG_BLOCK|该进程新的信号屏蔽字是当前信号屏蔽字和set指向的信号集的并集。于是set包含了希望阻塞的附加信号|
    |SIG_UNBLOCK|该进程新的信号屏蔽字是当前信号屏蔽字和set所指向的信号集补集的交集。于是set包含了希望解除阻塞的信号|
    |SIG_SETMASK|该进程新的信号屏蔽字是set所指向的值|
  3. 如果set是空指针,则不改变该进程的信号屏蔽字,how也随之没有意义

10.13 sigpending函数

#include 
int sigpending(sigset_t *set);//set返回被阻塞不能递送的信号集
成功返回0;出错返回-1

你可能感兴趣的:(2017-08-18)