1、信号概念
- 信号是软件中断,提供了一种处理异步事件的方法。
- 在头文件《signal.h>中,信号名都被定义为正整数常量(信号编号)。
- 不存在编号为0的信号,kill函数对信号编号0由特殊的应用。
在某个信号出现时,可以告诉内核按下列3中方式之一进行处理,我们称之为信号的处理或与信号相关的动作。
(1)忽略此信号。但SIGKILL和SIGSTOP这两种信号决不能被忽略。
(2)捕捉信号。
(3)执行系统默认动作。
2、函数signal
#inlcued <signal.h>
void (*signal(int signo,void (*func)(int)))(int);//返回值是一个函数地址,该函数有一个整形参数,指向在此之前的信号处理程序的指针。
若成功,返回以前的信号处理配置;若出错,返回SIG_ERR
func的值是常量SIG_IGN、常量SIG_DEL或当接到此信号后要调用的函数的地址。如果指定SIG_IGN,则向内核表示忽略此信号(记住有两个信号SIGKILL和SIGSTOP不能忽略)。如果执行SIG_DFL,则表示接到此信号后的动作时系统默认动作。当指定函数地址时,则在信号发生时,调用该函数,我们称这种处理为捕捉该信号。
1、程序启动
当执行一个程序时,所有信号的状态都是系统默认或忽略。通常所有信号都被设置为它们的默认动作,除非调用exec的进程忽略该信号。确切地将,exec函数将原先设置为要捕捉的信号都更改为默认动作,其他信号的状态则不变(一个进程原先要捕捉的信号,当其执行一个新程序后,就不能再捕捉了,因为信号捕捉函数的地址很可能在所执行的新程序文件中已无意义)。
2、进程创建
当一个进程调用fork时,其子进程继承父进程的信号处理方式。
注:进入信号处理程序后,首先要调用signal函数以重新设置此信号处理程序。
3、可重入函数
进程捕捉到信号并对其进行处理时,进程正在执行的正常指令序列就被信号处理程序临时中断,它首先执行该信号处理程序中的指令。如果从信号处理程序返回,则继续执行在捕捉到信号时进程正在执行的正常指令序列。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。
在信号处理程序中保证调用安全的函数是可重入的并被称为是异步信号安全的。
不可重入函数:
(1)已知它们使用静态数据结构
(2)它们调用malloc或free(在信号处理程序调用free而主程序也在调用free时,malloc和free维护的数据结构就出现了损坏)
(3)它们是标准I/O函数(标准I/O库的很多实现都以不可重入方式使用全局数据结构)
4、SIGCLD语义
SIGCHLD:在一个进程终止或停止时,SIGCHLD信号被送给其父进程。
对于SIGCLD的早期处理方式是:
(1)如果进程明确地将该信号的配置设置为SIG_IGN,则调用进程的子进程将不产生僵死进程。
(2)如果将SIGCLD的配置设置为捕捉,则内核立即检查是否有子进程准备好被等待,如果说会这样,则调用SIGCLD处理程序。
5、可靠信号术语和语义
- 当造成信号的事件发生时,为进程产生一个信号(或向一个进程发送一个信号)
- 当一个信号产生时,内核通常在进程表中以某种形式设置一个标志。当对信号采取了这种动作时,我们说向进程递送了一个信号。
- 在信号产生和递送之间的时间间隔内,称信号是未决的。
- 进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞的信号,而且对该信号的动作是系统默认动作或捕捉该信号,则为该进程将此信号保持为未决状态,直到该进程对此信号解除了阻塞,或者将此信号的动作更改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生该信号时),才决定对它的处理方式。
- 如果在进程解除对某个信号的阻塞之前,这种信号发生了多次,允许系统递送该信号一次或多次。
- 每个进程都有一个信号屏蔽字,它规定了当前要阻塞递送到该进程的信号集。
6、函数kill和raise
kill函数将信号发送给进程或进程组。raise函数则允许进程向自身发送信号。
#include <signal.h>
int kill(pid_t pid,int signo);
int raise(int signo);
若成功,返回0;若出错,返回-1
klii的pid参数有以下4种不同的情况:
pid>0: 将该信号发送给进程ID为pid的进程。
pid==0:将该信号发送给与发送进程属于同一进程组的所有进程(这些进程的进程组ID等于发送进程的进程组ID),而且发送进程具有权限向这些进程发送信号。
pid<0:将该信号发送给其进程组ID等于pid绝对值,而且发送进程具有权限向其发送信号的所有进程。
pid==-1:将该信号方发送给发送进程有权限向它们发送信号的所有进程。
进程将信号发送给其他进程需要权限。超级用户可将信号发送给任一进程。对于非超级用户,其基本规则是发送者的实际用户ID或有效用户ID必须等于接收者的实际用户ID或有效用户ID。
注:如果被发送的信号是SIGCONT,则进程可将它发送给属于同一会话的任一其他进程。
7、函数alarm和pause
#include <unistd.h>
unsigned int alarm(unsigned int seconds);//当定时器超时时,产生SIGALRM信号。如果忽略或不捕捉此信号,则其默认动作是终止调用该alarm函数的进程。
返回:0或以前设置的闹钟时间的余留秒数
- 每个进程只能有一个闹钟时间。
- 如果我们想捕捉SIGALRM信号,则必须在调用alarm之前安装该信号的处理程序。
pause函数使调用进程挂起直至捕捉到一个信号:
#include <unistd.h>
int pause(void);//只有执行了一个信号处理程序并从其返回时,pause才返回。
返回值:-1,errno设置为EINTR
8、信号集
#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);
若成功,返回0;若出错,返回-1
int sigismember(const sigset_t *set,int signo);
若真,返回1;若假,返回0
函数sigemptyset初始化由set指向的信号集,清除其中所有信号。函数sigfillset初始化由set指向的信号集,使其包括所有信号。
注:所有应用程序在使用信号集前,要对该信号集调用sigemptyset或sigfillset一次。
9、函数sigprocmask
函数sigprocmask可以检测或更改,或同时进行检测和更改进程的信号屏蔽字(一个进程的信号屏蔽字规定了当前阻塞而不能递送给该进程的信号集):
#include <signal.h>
int sigprocmask(int how,const sigset_t *restrict set,sigset_t *restrict oser);
若成功,返回0;若出错,返回-1
- 若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。
- 若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。
- 如果set是个空指针,则不改变该进程的信号屏蔽字,how的值也无意义。
注:不能阻塞SIGKILL和SIGSTOP信号。
10、函数sigpending
函数sigpending返回一信号集,对于调用进程而言,其中的信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过set参数返回。
#include <signal.h>
int sigpending(sigset_t *set);
若成功,返回0;若出错,返回-1
11、函数sigaction
sigaction函数的功能是检查或修改(或检查并修改)与指定信号相关联的处理动作。
#include <signal.h>
int sigaction(int signo,const struct sigaction *restrict act,struct sigaction *restrict oact);
若成功,返回0;若出错,返回-1
其中,参数signo是要检测或修改其具体动作的信号编号。若act指针非空,则要修改其动作。如果oact指针非空,则系统经由oact指针返回该信号的上一个动作。
struct sigaction{
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
void (*sa_sigaction)(int,siginfo_t *,void *);
};
当更改信号动作时,如果sa_handler字段包含一个信号捕捉函数的地址(不是常量SIG_IGN或SIG_DFL),则sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加到进程的信号屏蔽字中。仅当从信号捕捉函数返回时再将进程的信号屏蔽字恢复为原先值。这样,在调用信号处理程序时就能阻塞某些信号。在信号处理程序被调用时,操作系统建立的新信号屏蔽字包括正被递送的信号,因此保证了在处理一个给定的信号时,如果这种信号再次发生,那么它会被阻塞到对前一个信号的处理结束为止。若同一种信号多次发生,通常并不将它们加入队列,所以如果在某种信号被阻塞时,它发生了5次,那么对这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
一旦对给定的信号设置了一个动作,那么在调用sigaction显式地改变它之前,该设置就一直有效。
act结构的sa_flags字段指定对信号进行处理的各个选项。
sa_sigaction字段是一个替代的信号处理程序,在sigaction结构中使用了SA_SIGINFO标志时,使用该信号处理程序。
siginfo结构包含了信号产生原因的有关信息。
struct siginfo{
int si_signo;
int si_errno;
int si_code;
pid_t si_pid;
uid_t si_uid;
void *si_addr;
int si_status;
union sigval si_value;
};
注意:
- 必须用sigemptyset函数初始化act结构的sa_mask成员。
- 对除SIGALRM以外的所有信号,我们都有意尝试设置SA_RESTART标志,于是被这些信号中断的系统调用都能自动重启动。
12、函数sigsetjmp和siglongjmp
调用longjmp有一个问题:当捕捉到一个信号时,进入信号捕捉函数,此时当前信号被自动地假加到进程的信号屏蔽字中。这阻止了后来产生的这种信号中断该信号处理程序。但如果用longjmp跳出信号处理程序,有可能进程的信号屏蔽字不会自动恢复为原先值,该信号被阻塞。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env,int savemask);
若直接调用,返回0;若从siglongjmp调用返回,则返回非0
void siglongjmp(sigjum_buf env,int val);
如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果带非0savemask的sigsetjmp调用已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。
13、函数sigsuspeng
问题:如果在解除阻塞时刻和pause之间发生了信号,一旦在此时间窗口中发生的信号丢失了,就会使得pause永远阻塞。
为了纠正此问题,需要在一个原子操作中先恢复信号屏蔽字,然后使进程休眠:
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
返回:-1,并将errno设置为EINTR
进程的信号屏蔽字设置为由sigmask指向的值。在捕捉到一个信号或发生了一个会终止进程的信号之前,该进程被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。
14、函数abort
#include <stdlib.h>
void abort(void);//使程序异常终止
此函数将SIGABRT信号发送给调用进程(进程不应忽略此信号)。
让进程捕捉SIGABRT的意图是:在进程终止之前由其执行所需的清理操作。
15、函数system
POSIX.1要求system忽略SIGINT和SIGQUIT,阻塞SIGCHLD。
当用system运行另一个程序时,不应使父、子进程两者都捕捉终端产生的两个信号:中断和退出。这两个信号只应发送给正在运行的程序:子程序。因为由system执行的命令可能是交互式命令,以及因为system的调用者在程序执行时放弃了控制,等待该执行程序的结束。
16、函数sleep、nanosleep和clock_nanosleep
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
返回:0或未休眠完的秒数。
此函数使调用进程被挂起知道满足下面两个条件之一:
(1)已经过了seconds所指定的墙上时钟时间。
(2)调用进程捕捉到一个信号并从信号处理程序返回。
nanosleep函数与sleep函数类似,但提供了纳秒级的精度。
#incude <time.h>
int nanosleep(const struct timespec *reqtp,struct timespec *remtp);
若休眠到要求的时间,返回0;若出错,返回-1
这个函数挂起调用进程,直到要求的时间已经超时或者某个信号中断了该函数。reqtp参数用秒和纳秒指定了需要休眠的时间长度。如果某个信号中断了休眠间隔,进程并没有终止,remtp参数指向的timespec结构就会被设置为未休眠完的时间长度。
#include <time.h>
int clock_nanosleep(clockid_t clock_id,int flags,const struct timespec *reqtp,struct timespec *remtp);
若休眠要求的时间,返回0;若出错,返回错误码
clock_id参数指定了计算延迟时间基于的时钟。flags参数用于控制延迟是相对的还是绝对的。flags为0是表示休眠时间是相对的,如果flags值设置为TIMER_ABSTIME,表示休眠时间是绝对的。
17、函数sigqueue
大部分UNIX系统不对信号排队。
使用信号排队必须做以下几个操作:
(1)使用sigaction函数安装信号处理程序时指定SA_SIGINFO标志。
(2)在sigaction结构的sa_sigaction成员中(而不是通常的sa_handler字段)提供信号处理程序。
(3)使用sigqueue函数发送信号。
#include <signal.h>
int sigqueue(pid_t pid,int signo,const union sigcal value);
若成功,返回0;若出错,返回-1
18、作业控制信号
SIGCHLD 子进程已停止或终止。
SIGCONT 如果进程已停止,则使其继续运行
SIGSTOP 停止信号(不能被捕捉或忽略)
SIGTSTP 交互式停止信号
SIGTTIN 后台进程组成员读控制终端
SIGTTOU 后台进程组成员写控制终端
19、信号名和编号
extern char *sys_siglist[ ];
数组下标是信号编号,数组中的元素是指向信号名符串的指针。
可以使用psignal函数可移植地打印与信号编号对应的字符串:
#include <signal.h>
void psignal(int signo,const char *msg);//字符串msg(通常是程序名)输出到标准错误文件,后面跟随一个冒号和一个空格,在后面对该信号的说明,最后是一个换行符。
#include <siganl.h>
void psiginfo(const siginfo_t *info,const char *msg);
#include <stirng.h>
char *strsignal(int signo);
返回:指向描述该信号的字符串的指针
#include <signal.h>
int sig2str(int signo,char *str);
int str2sig(const char *str,int *signop);
若成功,返回0;若出错,返回-1.