本章函数都是定义在<signal.h>
1.可靠的信号和不可靠的信号
1.1不可靠的信号
信号可能会丢失,但是进程并不知道此事情,这是早期信号的弊端,并且早期实现对信号的控制也是蛮差的,列如用户希望内核阻塞某个信号,但是不能忽略他,在合适的时候进行释放,当时不具备此种能力
1.2可靠的信号
1.2.1递送和未决:当一个信号产生时,内核通常在进程表中以某种形式设置一个标志,此动作叫做递送,在信号产生和递送的时间间隔内,此时间段内信号是未决的,并且只有在递送了一个信号的时候,才决定进程对信号的处理方式
1.2.2在可靠的信号中,可以对信号进行阻塞,阻塞是把信号阻塞在未决的状态,所以很据1.2.1所述,只要阻塞在未决状态,信号没有进行递送,就可以在次状态中改变信号的处理方式。
1.2.2如果信号在阻塞期间发生多次怎么办
因为是可靠的信号,所以,在这种情况下,应该对信号进行阻塞,但是,当解除阻塞的时候,被阻塞的信号会根据选择,进行递送一次或者多次,如果是进行递送了多次,则说明信号发生了排队,但是此种发生排队的现象,必须支持POSIX.1的实时扩展
2.系统的中断调用
定义:如果进程在执行一个低速系统调用而阻塞期间捕捉到一个信号,则系统调用就被中断不在继续执行,该系统调用返回出错,error被设置成EINTR,这样处理是因为一个信号发生了,进程捕捉到他,这意味着某个重要的事情发生了,在此时,不应该进程阻塞在低速系统调用中,而是应该唤醒阻塞进程,对信号进行处理
2.1关于全局变量errno的理解
经常在调用linux 系统api 的时候会出现一些错误,比方说使用open() write() creat()之类的函数有些时候会返回-1,也就是调用失败,这个时候往往需要知道失败的原因。这个时候使用errno这个全局变量就相当有用了。
在程序代码中包含 #include <errno.h>,然后每次程序调用失败的时候,系统会自动用用错误代码填充errno这个对象(下面介绍)
errno这个全局变量在<errno.h>头文件中声明如下:extern int errno;
errno是一个由POSIX和ISO C标准定义的符号,看(用)起来就好像是一个整形变量。当系统调用或库函数发生错误的时候,比如以只读方式打开一个不存在的文件时,它的值将会被改变,根 据errno值的不同,我们就可以知道自己的程序发生了什么错误,然后进行相应的处理。
为什么,要强调errno看起来好像是一个整形变量呢?因为有的标准(如ISO C)只规定了errno的作用,而没有规定它的实现方式,它可能被定义成一个变量,也有可能被定义成一个宏,这个具体要看编译器自己的实现。早些时 候POSIX.1曾把errno定义成extern int errno这种形式,但现在这种方式比较少见了。因为以这种形式来实现errno,在多线程环境下errno变量是被多个线程共享的,这样可能线程A发生 某些错误改变了errno的值,线程B虽然没有发生任何错误,但是当它检测errno的值的时候,线程B会以为自己发生了错误。所以现在errno在 Linux中被实现成extern int * __errno_location(void): #define errno (*__errno_location()),这样每个线程都有自己的errno,不会再发生混乱了。
2.2低速系统调用
系统中有两种系统调用,一种低速系统调用和其他系统调用,低速系统调用是会让进程永远阻塞的一类系统调用
(1)在读某些类型的文件时(这种系统调用是读操作,读操作,读操作!!!)如果数据并不存在则可能会使
调用者永远阻塞(管道、终端设备以及网络设备)。
(2)在写这些类型(指的是写类型的系统调用)(指的是管道,终端设备,网络设备)的文件时,如果不能立即
接受这些数据,则也可能会使调用者永远阻塞。
(3)打开文件(一些打开文件的系统调用),在某种条件发生之前也可能会使调用者阻塞(例如,打开终端设备,
它要等待直到所连接的调制解调器应答)。
(4)pause(按照定义,它使调用进程睡眠直至捕捉到一个信号 )和wait
(5)某种ioctl操作。
(6)某些进程间通信函数(见第 1 4章)
在这些低速系统调用中,有一个比较值得注意的是与磁盘IO有关的系统调用
虽然读、写一个磁盘文件可能暂时阻塞调用者(在磁盘驱动程序将请求排入队列,然后在适当时间执行请求期间)
,但是除非发生硬件错误, I / O操作总会很快返回,并使调用者不再处于阻塞状态。
2.3对于如何处理read和write系统调用,当被中断的时候处理方案(POSIX 2001)
(1)如果在read函数时发生中断,但是没有收到全部的信息,系统可以认为是失败的,并且将errno设置为
EINTR
(2)系统液可以认为调用是成功的,并且返回已经处理完的数据
2.4当被中断的系统调用出错返回的时候,我们有可能希望他重新启动
![](https://images2015.cnblogs.com/blog/1024552/201609/1024552-20160912091239961-1650401104.png)
为了帮助应用程序使其不必处理被中断的系统调用, 4 . 2 B S D引进了某些被中断的系统调
用的自动再起动。自动再起动的系统调用包括: i o c t l、r e a d、r e a d v、w r i t e、w r i t e v、w a i t和
w a i t p i d。正如前述,其中前五个函数只有对低速设备进行操作时才会被信号中断。而 w a i t和
w a i t p i d在捕捉到信号时总是被中断。某些应用程序并不希望这些函数被中断后再起动,因为这
种自动再起动的处理方式也会带来问题,为此 4 . 3 B S D允许进程在每个信号各别处理的基础上
不使用此功能。
当sigaction指定为SA_RESTART进行中断的系统调用重新启动,signal,被中断的系统调用是默认启动的,
但是在signal中各种平台处理不一样,应该自己定义signal,可以加强可移植性能
3.可重入函数
注意:当进程正在执行执行正常的指令的时候,如果此时有信号发生,则应该首先执行信号处理程序,执行信号处理的过程应该能返回发生信号的地方,然后按照正常的程序执行流程执行
3.1什么叫做可重入:
比如说进程正在执行malloc函数,此时正好发生一个信号,进入信号处理,在信号处理的过程中,再一次执行malloc函数,此时,因为在堆上就会发生错误,这就叫做不可重入的(在执行某一个函数的时候,信号处理程序发生,处理程序中又有执行此函数,但是此函数会对进程产生破坏)
3.2哪些函数是不可重入的
(1)使用了静态数据结构(2)调用了malloc或者free(3)他们是标准的IO函数(因为标准的IO函数使用了全局数据结构)
3.3errno变量
要注意系统调用中的errno,为了保证函数的可重入性,应该在调用信号处理程序的时候保存errno的值,然后在返回的时候发送
4.signal:
函数原型是 void (*signal(int signo,void (*func) (int))) (int);
此函数因为声明过于复杂,所以使用了typedef进行简化typedef void Sigfunc(int);
Sigfunc *signal(int ,Sigfunc*);
在ubuntu中,signal信号默认不阻塞本信号(如果在信号处理程序中,再次发生了本信号,不进行本次信号的阻塞),并且在有本次信号到来的时候恢复系统的默认动作
5.sigaction函数:
int sigaction(int signo,const struct sigaction *restrict act, struct sigaction *restrict oact)
5.1signo是信号的编号
5.2sigaction解释
struct sigaction
{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int,siginfo_t*,void *);
}
5.2.1sa_mask信号屏蔽字,当进程在调用sa_handler之前,sa_mask加入进程的信号屏蔽字中,当从信号处理程序返回的时候,然后恢复原来的信号屏蔽字,注意在信号处理程序中,正在处理的信号被自动加入信号屏蔽字中,若在发生此信号,应该对其进行阻塞,如果一个信号发生多次,一般不将其放入信号队列中,最后,解阻塞的时候,多次相同的信号,只发生一次
5.2.2sa_handler是信号的处理程序,在再次改变信号处理程序的时候,该设置一直有效
5.2.3sa_flags:
SA_NODEFER:
当捕捉到此信号时,在执行其信号捕捉函数时,系统不自动阻塞此信号。注意,此种类型的操作对应于早期的不可
靠信号
SA_RESETHAND:
如果加入此标志,在进入信号处理程序的时候将信号的处理方式设置为SIG_DFL,并且清除SA_SIGINFO,并且此种标志对应以前不可靠的信号!!!对于SIGILL和SIGTRAP信号,此设置是无效的
SA_INTERRUPT:
由此信号中断的系统调用不自动重启动
SA_RESTART:
由此信号中断的系统调用自动重启动
SA_SIGINFO:
由此选项对信号处理程序提供了附加信息,一个指向siginfo结构的指针以及一个指向程序上下文标识符的指针
当设置了此标志,调用以下信号处理程序
void handler(int signo,siginfo_t *info,void *context)
6.sigsuspend函数
1)头文件:#include <signal.h>
2)一个保护临界区代码的错误实例:(sigprocmask()和pause()实现)
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)
{
}
int main()
{
}
上面实例的问题是:本来期望pause()之后,来SIGINT信号,可以结束程序;可是,如果当“取消阻塞”和“pause”之间,正好来了SIGINT信号,结果程序因为pause的原因会一直挂起。。。
解决的方式,当然是sigsuspend()函数了。
3)使用sigsuspend()的程序
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void handler(int sig)
{
}
int main()
{
}
sigsuspend的原子操作是:
(1)设置新的mask阻塞当前进程(上面是用wait替换new,即阻塞SIGUSR1信号)
(2)收到SIGUSR1信号,阻塞,程序继续挂起;收到其他信号,继续运行sigsupsend。
(3)调用该进程设置的信号处理函数(程序中如果先来SIGUSR1信号,然后过来SIGINT信号,则信号处理函数会调用两次,打印不同的内容。第一次打印SIGINT,第二次打印SIGUSR1,因为SIGUSR1是前面阻塞的)
(4)待信号处理函数返回,sigsuspend返回了。(sigsuspend将捕捉信号和信号处理函数集成到一起了)恢复原先的mask(即包含SIGINT信号的)