UNIX环境高级编程(十)信号

10.2信号概念

产生信号的条件:

1.当用户按某些终端键时,引发终端产生信号。

2.硬件异常产生信号:除数为0,无效的内存引用。

3.进程调用kill函数将信号发给里一个进程或进程组。限制:接收信号进程和发送信号进程所有者必须相同,或者发送信号的所有者是超级用户

4.当检测到某种软件条件已经发生,并将其通知有关进程时也产生信号。


可以要求内核在信号出现时按照下面三种方式处理,我们称之为信号处理或者与信号相关的动作。

1.忽略此信号。但有两种信号不能忽略,它们是SIGKILL和SIGSTOP。不能忽略的原因是:他们向超级用户提供了使进程终止或停止的可靠方法。

2.捕捉信号。为了做到这一点,要通知内核在某种信号发生时调用一个用户函数。用户不能捕捉SIGKILL和SIGSTOP信号

3.执行系统默认动作。针对大多数信号的默认动作是终止进程。


下面介绍几个信号:
SIGABRT:调用abort函数时产生此信号。进程异常终止

SIGINT:当用户按中断键时,终端驱动程序产生此信号并送至前台进程组中的每一个进程。

SIGKILL:这是两个不能被捕捉和忽略的信号之一。它向系统管理员提供了一种可以杀死任一进程的可靠方法。

SIGQUIT:当用户在终端上按退出键时产生此信号,并送至进程组的所有进程

SIGSEGV:当引用了一次无效内存时产生此信号

SIGSTOP:用于停止一个进程

SIGTERM:这是由kill命令发送的系统默认终止信号


10.signal函数

#include<signal.h>
void (*signal(int sigo,void(*func)(int)))(int);
//返回值:若成功返回以前的处理配置,若出错返回SIG_ERR

sigo参数是信号名。func的值是常量SIG_IGN,常量SIG_DFL或当接到此信号后要调用的函数地址。如果指定SIG_IGN,则向内核表示忽略此信号;如果指定SIG_DFL,则表示接到此信号后的动作是系统默认动作。当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为“捕捉”该信号。称此函数为信号处理程序或信号捕捉函数。

signal函数原型太复杂,如果使用typedef,则更简单一些。

typedef void  Sigfunc(int);

然后signal函数原型写成

Sigfunc *signal(int,Sigfunc *);

1.程序启动

当执行一个程序时,所有信号的状态都是系统默认或忽略.通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。exec函数将原先设置为要捕捉的信号都设置为它们的默认动作,(对于一个进程原先要捕捉的信号,当其要执行一个新程序后,就自然不能再捕捉它了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已经没有意义)。

signal函数的限制:不调用signal函数就不能知道当前的处理方式。

2.进城创建

当一个进程调用fork时,起子进程继承父进程信号处理方式。因为子进程在开始时复制了父进程的内存映像,所以信号捕捉函数的地址在子进程中是有意义的


10.4不可靠信号

信号不可靠:这里指的是,信号可能丢失;一个信号发生了,但进程却有可能一直不知道这一点。同时,进程对信号的控制能力也差,它能捕捉信号或忽略它。有时用户希望通知内核阻塞一个信号:不要忽略该信号在其发生时记住它,然后在进程准备好时再通知它。

10.5中断的系统调用

如果进程是在执行一个低速系统调用而阻塞期间捕捉到一个信号,则该系统调用就被中断不再继续执行。该系统调用返回出错,其errno被设置为EINTR.

为了支持这种特性,将系统调用分为两类:低速系统调用和其他系统调用。低速系统调用是有可能使进程永远阻塞的一类系统调用,它们包括:

1.在读某些类型的文件(管道,终端设备以及网络设备)时,如果数据不存在有可能使调用者永远阻塞

2.在写这些类型的文件时,如果不能立即接受这些数据,有可能使调用者永远阻塞

3.打开某些类型的文件,在某些条件发生之前也可能使调用者永远阻塞

4.pause和wait函数

5.某些ioctl操作

6.某些进程间通信函数

在这些低速系统调用中,一个值得注意的意外是与磁盘IO有关的系统调用。虽然读,写一个磁盘文件可能暂时阻塞调用者,但是除非发生硬件错误,IO操作总会很快返回,并使调用者不再阻塞


10.6 可重入函数

进程捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断,它首先执行信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉信号时进程在何处执行。

不是可重入函数的原因:

1.已知它们使用静态数据结构

2.它们使用malloc或free

3.它们是标准IO函数


10.8可靠信号术语和语义

首先,当引发信号的事件发生时,为进程产生一个信号(或向进程发送一个信号)。事件可以是硬件异常(例如,除以0),软件条件(alarm计时器超时),终端产生信号或调用kill函数。在产生信号时,内核通过在进程表中设置一个某种形式的标志。

当对信号采取了这种动作时,我们说向进程递送(delivery)了一个信号

在进程产生和递送的时间间隔内,称信号是未决的(pending)

进程可以选用信号递送阻塞。如果进程产生了一个选择为阻塞的信号,而且该信号的处理动作是系统默认动作或捕捉该信号,则为该进程保持此信号为未决状态,直到该进程

1.对该信号解除阻塞

2.对此信号的动作改为忽略

内核在递送一个原来阻塞的信号该进程时(而不是产生该信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可以改变对该信号的动作。

每一个进程都有一个信号屏蔽字(signal mask),它规定了当前要阻塞递送到该进程的信号集


10.9 kill和raise函数

kill函数将信号发给进程或进程组。raise允许进程向自身发送信号。

#include<signal.h>
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==-1 将信号发送给发送进程有权限向它们发送信号的系统上的所有进程。如上所述,所有进程不包括系统进程。

超级用户可以将信号发送给任一进程。发送者的实际或有效用户ID必须等于接受者的实际或有效用户ID

如果signo参数是0,kill函数仍执行正常的错误检查,但不发送信号。如果向一个不存在的进程发送信号,则kill返回-1.

如果调用kill为调用进程产生信号,而且该信号是不被阻塞的,那么在kill返回之前就会将signo或者其它某个未决的非阻塞信号传送至该进程


10.10 alarm和pause函数

使用alarm函数可以设置一个计时器,在将来某个时间计时器会超时,产生SIGALRM信号。若果不捕捉或忽略该信号,则其默认动作是终止调用该alarm函数的进程

#include<unistd.h>

unsigned int alarm(unsigned int seconds);
//返回值:0或以前设置的闹钟时间的余留秒数

其中seconds的值是秒数,经过了指定的seconds秒后会产生SIGALRM,信号由内核产生,由于进程调度的延迟,所以进程得到控制从而处理信号还需一些时间

每个进程只能有一个闹钟时钟。如果在调用alarm时,以前已经为该进程设置了一个闹钟时钟,而且它还没有超时,那么该闹钟时钟的余留值作为本次alarm函数调用的值得返回,以前登记的闹钟时钟被新值取代。

如果有以前为进程登记的闹钟时钟没有超时,而且本次调用的seconds值为0,则取消以前的闹钟时钟,其余留值作为alarm函数的返回值

如果我们想捕捉SIGALRM信号,必须在调用alarm函数前设置该信号的处理程序,如果我们先调用alarm,然后在我们能够设置SIGALRM信号处理程序之前已接受到该信号,那么进程终止。

pause函数使进程终止,直到捕捉到一个信号。

#include<unistd.h>

int pause(void);
//返回值:-1,并将errno设置为EINTR

只有执行了一个信号处理函数并从中返回时,pause才返回,并返回-1,将errno值设置为EINTR。

10.11信号集

我们需要一个能表示多个信号--------信号集的数据类型。

用数据类型sigset_t以表示一个信号集,并且定义了5个处理信号集的函数。

#include<signal.h>

int sigemptyset(sigset_t *set);
int sigaddset(sigset_t *set,int signo);
int sigdelset(sigset_t*set,int signo);
//返回值:若成功返回0,若出错返回-1
int sigismember(const sigset_t *set,int signo);
//返回值:若真返回1,若假返回0,若出错返回-1

10.12 sigprocmask函数

一个进程的信号屏蔽字规定了当前阻塞而不能递送给进程的信号集。调用sigprocmask函数可以检测或更改信号屏蔽字

#include<signal.h>

int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
//返回值:若成功返回0,若出错返回-1

首先,若oset是一个非空指针,那么进程的当前信号屏蔽字通过oset返回

若set是非空指针,则参数how指示如何修改当前信号屏蔽字。

how选项:

SIG_BLOCK :该进程新的信号屏蔽字是当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。

SIG_UNBLOCK:该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集补集的交集。set包含了我们希望解除阻塞的信号

SIG_SETMASK:该进程新的信号屏蔽字将被set所指向的信号集的值取代

在调用sigprocmask后如果有任何未决的、不再阻塞的信号,则在sigprocmask返回前,至少会将其中一个信号递送给该进程

10.13 sigpending函数

sigpending函数返回信号集,之中的各个信号对于调用进程是阻塞的而不能递送,因而也一定是未决的。

#inlcude<signal.h>

int sigpending(sigset_t *set);
//返回值:若成功返回0,出错返回-1

10.14  sigaction函数

sigaction函数是检查或修改与指定信号相关联的处理动作

#include<signal.h>
int sigaction(int signo,const struct sigaction *act,struct sigaction *oact);
//返回值:若成功返回0,出错返回-1

其中signo是要检测或修改其具体动作的信号编号。若act为非空,则要修改其动作。若果oact为非空,则系统由oact返回该信号的上一个动作。

struct sigaction
{
void (*sa_handler)(int);//addr of signal handler
sigset_t sa_mask;//additional signals to block
int sa_flags;//signal options
void (sa_sigaction)(int,siginfo_t*,void*);//alternate handler
};
当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数地址(与常量SIG_IGN SIG_DFL相对),则sa_mask字段说明了一个信号集,在调用信号捕捉函数之前,这一信号集要加入到信号屏蔽字中去。仅当从信号捕捉函数返回时再将进城的信号屏蔽字复位为原先值。在信号处理程序调用时,操作系统建立的新信号屏蔽字包括正在被递送的信号。因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。

第三个参数指定对信号处理的各个选项,其意义如下:

这些选项可以使用或进行组织。第四个成员sa_sigaction是一个替代的信号捕获函数,当sa_flags成员中包含SA_SIGINFO标识时,该函数被使用
对除了SIG_ALRM以外的所有信号,我们的有意尝试设置SA_RESTART标志,于是被这些信号中断的系统调用都能够重启动。不希望重启动由SIG_ALRM信号中断的系统调用的原因是:我们希望对IO操作可以设置事件限制。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值