一.信号与消息
linux里边的信号和win下边的的消息有基本相同的同能,都有各种各样的信号(消息)及其相应的信号(消息)处理函数。
1. 信号处理函数添加。
signal函数。
下边是从高级编程上拷贝来。平平无奇,一看就明白了。
定义:
typedef void Sigfunc(int);
Sigfunc *signal(int, Sigfunc *);
将一个信号和他的处理函数绑定在一起。非常简单的东西。 在wind下,用了很多遍了把?
需要注意的是他的返回值:
#define SIG_ERR (void (*)())-1
#define SIG_DFL (void (*)()) 0
#define SIG_IGN (void (*)()) 1
以前曾经讲过,一定要注意返回值的处理。一定要判断函数的返回值。
2. 所有的wind message都是可以截获的,但是Linux下,SIGSTOP,SIGKILL信号是不能被截获的。保留这两个信号不被截获是为了让系统,用户,可以直接kill掉任何的进程(当然是有权限杀的进程).
3. 系统函数是可以被信号打断的。
高级编程这样写道:
进程捕捉到信号并继续执行时,它首先执行该信号处理程序中的指令。如果从信号处理程序返回(例如没有调用e x i t或l o n g j m p),则继续执行在捕捉到信号时进程正在执行的正常指令序列(这类似于硬件中断发生时所做的)。但在信号处理程序中,不能判断捕捉到信号时进程执行到何处。
也就是说,系统不给你保留当时的栈环境,直接跳出正在执行的系统函数。这与对硬中断的处理(中断环境保存,执行中断代码,中断环境恢复)是不同的。
比如说,sleep(60)的过程中,进程收到一个信号。这个信号会打断这个函数的执行,也就是没有睡够,睡了20秒,30秒就出来了。这个造成的后果还小一点,你想想,要是在写文件的过程中,写了一半就被打断,那会出现什么后果?
我们常用的对这个事情的处理办法:
1. 屏蔽掉不需要的或者所有的的信号.
sigset_t sets;//一个空的sigset集合。
sigfillset(&sets);//填充所有的信号。
sigprocmask(SIG_BLOCK, &sets, 0);
顺便介绍一下sigprocmask的一个参数。需要注意,进程有当前的阻塞信号的集合。对集合的操作。
SIG_BLOCK:
The resulting set shall be the union of the current set and the signal set pointed to by set.
SIG_SETMASK:
The resulting set shall be the signal set pointed to by set.
SIG_UNBLOCK
The resulting set shall be the intersection of the current set and the complement of the signal set pointed to by set.
所有的信号都屏蔽了,那么如何用信号让程序正常退出那? linux可不比 windows还有一个关闭按钮让你按。大多数程序得用到信号让程序正常退出,而且不能把程序直接kill掉,因正常退出的过程中通常需要做一部份(比如清理等)工作。这时,你可以让程序阻塞等待一个被屏蔽掉的信号上,通常是,SIGTERM.有点相当于windows里边的close消息。
while( sigwaitinfo(&sets, 0) != SIGTERM );
SIGTERM信号来了,程序准备退出。呵呵!
第二个参数是sig的info,不用的话设置为NULL. 用的话,自己查帮助。
sigtimedwait只不过加了一个时间参数。不过,linux下的超时和win下边的不太一样。他的超时是与现在的时间作对比的。下边是我写的一个函数。用来获得超时的时间struct timespec结构。
int getTimeOut(int sec,long nsec,struct timespec& abstimeout)
{
struct timeval now;
gettimeofday(&now, 0);
abstimeout.tv_sec = now.tv_sec + sec;
abstimeout.tv_nsec = (now.tv_usec * 1000) + nsec;
if (abstimeout.tv_nsec >= 1000000000)
{
abstimeout.tv_sec++;
abstimeout.tv_nsec -= 1000000000;
}
return 0;
}
一般有这这种需要的程序,都是在主线程里边屏蔽掉所有信号,然后,在另外的线程里边阻塞等待一个信号。那么,其他线程的函数继承主线程里边的信号屏蔽的办法,屏蔽掉所有信号!不过,用gdb调试的时候,gdb产生的信号,用这些办法是拦不住的。这种办法只能拦住系统发来的信号。
2. 对返回值进行处理。 "手工" 支持这种软中断。
对可能中断的函数做应用级别的处理,例如:
while( ::close(fd) == -1 && errno == EINTR );
sleep被中断,会返回剩余的秒数.write会返回已经正确写入的字节数目。等等。利用这些信息,可以做一下函数重入,只是写起来比较麻烦。
需要注意的是:在Linux里边,系统函数分成可再入的和不可再入的。区分的原则是这个函数是否更改了全局数据结构。也就是,这个函数在重入以前,要考虑上一次进入的时候,对全局数据结构的更改。也就是需要的话,自己做点环境保存和恢复。比如:
高级编程这么写到:
要了解在信号处理程序中即使调用列于表1 0 - 3中的函数,因为每个进程只有一个e r r n o变量,所以我们可能修改了其原先的值。考虑一个信号处理程序,它恰好在m a i n刚设置e r r n o之后被调用。如果该信号处理程序调用r e a d,则它可能更改e r r n o的值,从而取代了刚由m a i n设置的值。因此,作为一个通用的规则,当在信号处理程序中调用表1 0 - 3中列出的函数时,应当在其前保存,在其后恢复errno。
还需要注意的是,sigaction里边有个参数,SIG_RESTART. 他的意思是,当函数被信号中断以后,系统可以自动重新调用这个函数一遍。而且有的版本还不支持这个参数。
试着想像一下:参数不变的条件下,你的write函数重新调用一遍会出现什么后果?
高级编程里边是这么讲的:
P O S I X . 1允许实现再起动系统调用,但这并不是必需的。系统V的默认工作方式是不再起动系统调用。但是S V R 4使用s i g a c t i o n时(见1 0 . 1 4节),可以指定SA_RESTART选择项以再起动由该信号中断的系统调用。在4 . 3 + B S D中,系统调用的再起动依赖于调用了哪一个函数设置信号处理方式配置。早期的与4 . 3 B S D兼容的s i g v e c函数使被该信号中断的系统调用自动再起动。但是,使用较新的与POSIX . 1兼容的sigaction则不使它们再起动。但如同在S V R 4中一样,在sigaction中可以使用SA_RESTART选择项,使内核再起动由该信号中断的系统调用。
在windows下,你不用担心消息会中断系统调用。不过,也可以认为丧失的是对软中断信号的的快速相应。
二. 几个特殊的信号
1. SIGPIPE:
在linux下写socket的程序的时候,如果尝试send到一个disconnected socket上,就会让底层抛出一个SIGPIPE信号。
这个信号的缺省处理方法是退出进程,大多数时候这都不是我们期望的。因此我们需要重载这个信号的处理方法。调用以下代码,即可安全的屏蔽SIGPIPE:
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigaction( SIGPIPE, &sa, 0 );
把这几行代码,看作是windows下的WSAStartUp()就好了。