第七章 linux信号机制
信号机制是linux 学习的一个难点,不像windows 消息那么简单。
(1) 信号的概念与产生
信号就是系统发给进程的命令, 有人叫软中断。我认为这是不确切的,因为中断是中止进程立刻进入中断指令。 而信号的执行要看系统内核的调度。
1> 终端键盘,I/O设备 eg: 键盘上CTRL+C
2> 调用系统函数发送信号 eg: kill SIGSEGV 7000 或者 raise SIGSEGV
3> 软件条件 eg:alarm 函数
linux 系统定义信号如下,具体各个信号含义查阅相关文档。
1)SIGHUP 2)SIGINT 3)SIGQUIT 4)SIGILL 5)SIGTRAP 6)SIGABRT 7)SIGBUS 8)SIGFPE 9)SIGKILL 10)SIGUSR1 11)SIGSEGV 12)SIGUSR2 13)SIGPIPE 14)SIGALRM 15)SIGTERM 16)SIGSTKFLT 17)SIGCHLD 18)SIGCONT 19)SIGSTOP 20)SIGTSTP 21)SIGTTIN 22)SIGTTOU 23)SIGURG 24)SIGXCPU 25)SIGXFSZ 26)SIGVTALRM 27)SIGPROF 28)SIGWINCH 29)SIGIO 30)SIGPWR 31)SIGSYS 34)SIGRTMIN 35)SIGRTMIN+1 36)SIGRTMIN+2 37)SIGRTMIN+3 38)SIGRTMIN+4...
信号分为实时信号与非实时信号。 编号34以后的称为实时信号,以前的非实时信号。
实时信号在进程阻塞该信号的时间内,发给该进程的所有相同的实时信号会排队,而非实时信号则会合并为一个信号。
(2) 信号机制
1> 信号机制采用的是回掉机制,每个进程有一个信号队列,当向进程发送信号的时候,内核会将信号插入该进程的信号队列(注意不是简单插入最后,又优先级的,可不关注)。
2> 进程向系统注册要各种信号的处理方式(阻塞,默认处理,丢弃)。
3> 系统遍历进程信号队列,中止正在运行的进程,调用进程的信号处理函数处理信号,继续运行进程。
(3) 信号相关函数介绍
//该函数主要用来注册新号的回掉函数 #include <signal.h> void (*signal(int signum, void (*handler)(int) ))(int); typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); //系统调用kill用来向进程发送一个信号 int kill(pid_t pid, int sig); //系统调用alarm设置一个定时器,当定时器计时到达时,将发出一个信号给进程 #include <unistd.h> unsigned int alarm(unsigned int seconds); //setitimer调用来设置定时器,用getitimer来得到定时器的状态 #include <sys/time.h> int getitimer(int which, struct itimerval *value); int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue);
注意:忽略一个信号:signal(SIGINT,oldptr)
(4)信号阻塞
执行信号的处理动作称为信号递达,信号从产生到递达之间的状态,称为信号未决。进程可以选择阻塞某个信号。被阻塞的信号产生时将保持在未决状
态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。每个信
号都有两个标志位分别表示阻塞和未决,还有一个函数指针表示处理动作。
sigset_t 信号屏蔽字,表示每个信号的有效无效状态。
#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); //删除信号屏蔽字 int sigismember(const sigset_t *set, int signo); //判断信号屏蔽子中是否有该信号 int sigprocmask(int how, const sigset_t *set, sigset_t *oset);//设置进程信号屏蔽字,返回原先的。
(5)一个信号处理的例子
转自博客http://kenby.iteye.com/blog/1173862
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <error.h> #include <string.h> void sig_handler(int signum) { printf("catch SIGINT\n"); } int main(int argc, char **argv) { sigset_t block; struct sigaction action, old_action; /* 安装信号 */ action.sa_handler = sig_handler; sigemptyset(&action.sa_mask); action.sa_flags = 0; sigaction(SIGINT, NULL, &old_action); if (old_action.sa_handler != SIG_IGN) { sigaction(SIGINT, &action, NULL); } /* 屏蔽信号 */ sigemptyset(&block); sigaddset(&block, SIGINT); printf("block SIGINT\n"); sigprocmask(SIG_BLOCK, &block, NULL); printf("--> send SIGINT -->\n"); kill(getpid(), SIGINT); printf("--> send SIGINT -->\n"); kill(getpid(), SIGINT); sleep(1); /* 解除信号后, 之前触发的信号将被递送, * 但SIGINT是非可靠信号, 只会递送一次 */ printf("unblock SIGINT\n"); sigprocmask(SIG_UNBLOCK, &block, NULL); sleep(2); return 0; }
(6) 系统调用setjmp()和longjmp()
有时候,当接收到一个信号时,希望能跳回程序中以前的一个位置执行。例如,在有的程序内,当用户按了中断键,则程序跳回到显示主菜单执行。我们可以用库系统调用setjmp()和longjmp()来完成这项工作。
setjmp()能保存程序中的当前位置(是通过保存堆栈环境实现的),longjmp()能把控制转回到被保存的位置。在某种意义上,longjmp()是远程跳转,而不是局部区域内的跳转。我们必须注意到,由于堆栈已经
回到被保存位置这一点,所以longjmp()从来不返回。然而,与其对应的setjmp()是要返回的。
#include <stdio.h> #include <setjmp.h> #include <signal.h> jmp_buf position; void main() { int goback(); /* 保存当前的堆栈环境 */ setjmp(position); signal(SIGINT,goback); } void goback(int singel) { fprintf(stderr,”\nInterrupted\n”); /* 跳转回被保存的断点 */ longjmp(position,1); }
(7)注意
进程中的信号处理是异步的,并且信号不带参数,我们不知道信号会在何时到来 。然而信号处理函数的实现,有着许多的限制;比如有一些函数不能在信号处理
函数中调用;再比如一些函数read、recv等调用时会被异步的信号给中断(interrupt),因此我们必须对在这些函数在调用时因为信号而中断的情况进行处理(判断函
数返回时 enno 是否等于 EINTR)。