信号处理
一、信号的捕捉和处理
1、signal函数
signal函数用来设置进程在接收到信号时的动作
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
signal根据参数signum指定的信号编号来设置该信号的处理函数,当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针,则必须时常数SIG_IGN(忽略该信号)或SIG_DFL(对该信号执行默认操作)。handler是一个函数指针,它所指向的函数的类型时sighandler_t,即它所指向的函数有一个int型参数,且返回值的类型为void。
signal函数执行成功时返回以前的信号处理函数指针,当有错误发生时返回SIG_ERR(即 -1)。
2、sigaction函数
sigaction函数可以用来检查或设置进程在接收到信号时的动作
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction根据参数signum指定的信号编号来设置该信号的处理函数。参数signum可以是SIGKILL和SIGSTOP以外的任何信号。如果参数act不是空指针,则为signum 设置新的信号处理函数;如果oldact不是空指针,则旧的信号处理函数将被存储在oldact中。
struct sigaction的定义如下:
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
sigaction函数执行成功返回0,当有错误发生时返回-1
3、pause函数
pause函数使调用进程挂起直至捕捉到一个信号。
#include <unistd.h>
int pause(void);
pause函数会令目前的进程暂停(进入睡眠状态),直到被信号(signal)所中断,该函数只返回-1,并将errno设置为EINTR
二、信号处理函数的返回
信号处理函数可以正常返回,也可以调用其他函数返回到程序的主函数中,而不是从该处理程序返回。
1、setjmp/longjmp
使用longjmp可以跳转到setjmp设置的位置
#include <setjmp.h>
int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);
参数env时一个特殊类型的jmp_buf的变量。这一数据类型是某种形式的数组,其中存放的时在调用longjmp时能用来恢复栈状态的所有信息。一般来说env是个全局变量,因为需从另一个函数中引用它。可以在希望返回的位置使用setjmp,直接调用setjmp时返回0;当从longjmp返回时,setjmp的返回值时longjmp的第二个参数的值,可以利用这一点使多个longjmp返回到一个setjmp处。
2、sigsetjmp/siglongjmp
由于信号处理期间自动屏蔽另外正在被处理的信号,而使用setjmp/longjmp跳出信号处理程序时又不会自动将信号屏蔽码修改回原来的屏蔽码,从而引起该信号被永久屏蔽。可以使用sigsetjmp/siglongjmp来解决这一问题。
#include <setjmp.h>
int sigsetjmp(sigjmp_buf env, int savesigs);
void siglongjmp(sigjmp_buf env, int val);
这两个函数与setjmp/longjmp的唯一区别就是sigsetjmp多了一个参数savesigs,如果savesigs非0;则sigsetjmp在env中保存进程的当前信号屏蔽字,在调用siglongjmp时会从其中恢复保存的信号屏蔽字。
三、信号的发送
1、kill函数
kill函数用来发送信号给指定的进程
#include <sys/types.h>
#include <signal.h>
int kill (pid_t pid, int sig);
该函数的行为与第一个参数pid的取值有关,第二个参数sig表示信号编号
- 如果pid是正数,则发送信号sig给进程号为pid的进程
- 如果pid为0,则发送信号sig给当前进程所属进程组里的所有进程
- 如果pid为-1,则把信号sig广播至系统内除1号进程和自身以外的所有进程
- 如果pid是比-1还小的负数,则发送信号sig给属于进程组-pid的所有进程
- 如果参数sig是0,则kill()仍执行正常的错误检查,但不发送信号
函数执行成功返回0 ,当有错误发生时返回-1
2、raise函数
raise函数是ANSI C而非POSIX标准定义的,用来给调用他的进程发送信号
#include <signal.h>
int raise(int sig);
参数sig表示要发送的信号,成功返回0,失败返回非0值。
3、sigqueue函数
sigqueue函数是一个比较新的发送信号的函数,它支持信号带有参数,从而可以与函数sigaction配合使用。
#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
sigqueue用来发送信号sig给进程pid .与kill系统调用不同的是,sigqueue在发送信号的同时还支持信号携带参数,另一个不同点时sigqueue不能给一组进程发送信号。参数value是一个共用体,其定义如下:
union sigval
{
int sival_int;
void *sival_ptr;
};
信号携带的参数要么是一个整型值,要么是一个void型指针。当接收进程的信号处理函数是由sigaction函数设置的并设置了SA_SIGINFO标志时,接收进程可以从siginfo_t结构的si_value域取得信号发送时携带的数据。
函数执行成功时返回0,表明信号被成功发送到目标进程,当有错误发送时则返回-1,错误代码存入errno中。
4、alarm函数
alarm函数可以用来设置定时器,定时器超时将产生SIGALRM信号给调用进程。
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
参数seconds表示设定的秒数,经过seconds后,内核将给调用该函数的进程发送SIGALRM信号。如果seconds为0,则不再发送SIGALRM信号。最新一次调用alarm函数将取消之前一次的设定。
如果之前已经调用过alarm,则返回之前设置的定时器剩余时间;否则如果之前没有设置过定时器,则返回0。
5、getitimer/setitimer
与alarm函数一样,setitimer函数也是用来设置定时器的,且alarm和setitimer使用同一个定时器,因此会相互影响。setitimer具有更多的功能。
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerva *value, struct itimerval *ovalue);
第一个参数which用来指定使用哪一个定时器,根据参数which可单独设定每个定时器,定时器的种类如下:
- ITIMER_REAL:按实际时间计时,计时到达时讲给进程发送SIGALRM信号,相当于高精度的alarm函数。
- ITIMER_VIRTUAL:仅当进程执行时才进行计时。计时到达时将给进程发送SIGTALRM信号。
- ITMER_PROF:进程执行的时间以及内核因本进程而消耗的时间都计时。
参数value用来指定定时器的时间,结构struct itimerval的定义如下:
struct itimerval
{
struct timeval it_interval;
struct timeval it_value;
};
其中结构struct timeval的定义如下:
struct timeval
{
long tv_sec; //秒数
long tv_usec; //微秒
};
对于函数getitimer,如果存在由which指定的定时器,则将剩余时间保存在it_value中,该定时器的初始值保存在it_interval中;如果不存在指定类型的定时器,则将value设置为0返回。执行成功返回0,当有错误发生返回-1.
对于函数setitimer,参数ovalue如果不是空指针,则将在其中保存上次设定的定时器的值。定时器从value递减为0时,产生一个信号,并将it_value的值设为it_interval,然后重新开始计时,如此周而复始。仅当it_value的值为0或者计时到达而it_interval的值为0时,停止计时。执行成功返回0,当有错误发生时返回-1.
6、abort函数
abort函数用来向进程发送SIGABRT信号
#include <stdlib.h>
void abort(void);
如果进程设置信号处理函数以扑捉SIGABRT信号,且信号处理函数不返回(如使用longjmp),则abort()不能终止进程。Abort()终止进程时,所有打开的流均会被刷新和关闭。如果进程设置了SIGABRT被阻塞或忽略,abort()将覆盖这种设置。
四、信号的屏蔽
1、信号集
#include <signal.h>
int sigemptyset(sigset_t *set); //用来初始化一个信号集,使其不包括任何信号
int sigfillset(sigset_t *set); //用来初始化一个信号集,使其包括所有信号
int sigaddset(sigset_t *set, int signum); //用来向set指定的信号集中添加由signum指定的信号
int sigdelset(sigset_t *set, int signum); //用来从set指定的信号集中删除由signum指定的信号
int sigismember(const sigset_t *set, int signum); //用来测试信号signum是否包括在set指定的信号集中
函数sigemptyset,sigfillse,sigaddset,sigdelset执行成功时返回0,失败返回-1。函数sigismember返回1表示测试的信号在信号集中,返回0表示测试的信号不在信号集中,出错返回-1.
2、信号屏蔽
信号屏蔽又称为信号阻塞
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
int sigpending(sigset_t *set);
int sigsuspend(const sigset_t *mask);
- sigprocmask函数
每个进程都有一个信号屏蔽码,它规定了当前阻塞而不能递送给该进程的信号集。调用函数sigprocmask可以检测或更改进程的信号屏蔽码。如果参数oldset是非空指针,则该进程之前的信号屏蔽码通过oldset返回;如果参数set是非空指针,则该函数将根据参数how来修改信号当前屏蔽码,how的取值如下:
SIG_BLOCK:将进程新的信号屏蔽码设置为当前信号屏蔽码和set指向信号集的并集
SIG_UNBLOC:将进程新的屏蔽码设置为当前信号屏蔽码中,删除set所指向信号集,即set包含了我们希望解除阻塞的信号。即使对当前信号屏蔽码中不存在的信号使用SIG_U NBLOCK也是合法操作。
SIG_SETMASK:将进程新的信号屏蔽码设置为set指向的值。
函数执行成功返回0,当有错误发生时返回-1.
sigpending函数
函数sigpending用来获取调用进程因被阻塞而不能递送和当前未决的信号集。该信号集通过参数set返回。
函数执行成功返回0,当有错误发生时返回-1.sigsuspend函数
函数sigsuspend将进程的信号屏蔽码设置为mask,然后与pause函数一样等待信号的发生并执行完信号处理函数。信号处理函数执行完之后再把进程的信号屏蔽码设置为原来的屏蔽字,然后sigsuspend函数才返回。sigsuspend函数保证改变进程的屏蔽码和将进程挂起等待信号是原子操作。
sigsuspend函数总是返回-1,并将errno置为EINTR。