UNIX环境高级编程——信号

引言

今天读完《UNIX环境高级编程》第10章信号,这一章对UNIX系统的信号相关的函数进行了解释。

信号

信号是用来中断进程的执行(与底层的CPU级别的中断相似,但一个是中断CPU的执行,一个是中断进程的执行)。

信号机制

信号的使用

在使用一个信号时,我们首先需要知道该信号是如何产生的,是由一些异常产生的(如除0),还是手工产生的(如kill, alarm等)。

在知道了信号如何产生后,我们就需要为进程注册该信号的处理函数,一般会使用signal或者sigaction函数注册。(sigaction比singal功能多,并且在一些UNIX实现中signal是使用sigaction实现的。)

信号屏蔽字

有时在进程执行的过程,需要防止被一些信号中断,此时就需要使用信号屏蔽字。

信号屏蔽字是进程的一个属性。一个信号被屏蔽后,还是会发送给进程,但不会调用相应的处理函数,直到信号被取消屏蔽后,才会被处理。

中断系统调用

如果一个进程由于执行系统调用,而进入阻塞状态,但偏偏此时还发生了某种不重要信号,那么就可能会导致系统调用失败并返回,如执行sleep过程中,如果发生SIGINT,就会让sleep返回。

通过对信号处理函数做一些特殊的设置,就可以使系统调用重新执行,从而避免这种情况。

数据访问冲突

当被中断的程序和信号处理程序会访问同一块内存时,就会出现数据访问冲突,进而导致一些意想不到的错误。

上图是一个数据访问冲突的例子,最终的结果不是x+1后的值,而是0,显然这不是我们想要的结果。所以在使用信号时,要注意数据访问冲突(数据竞争)

使用模式

  1. 设置信号处理函数和信号屏蔽字
  2. 业务逻辑
  3. 还原信号处理函数和信号屏蔽字

上述是一般流程,有时在处理完业务后,需要等待某个信号,此时就会用到sigsuspend,这个函数会阻塞进程,直到某个信号的产生,但这个函数的参数不是要等待的信号,而是要屏蔽的信号,剩下的信号就是要等待的信号。这个函数的逻辑上等价于先设置信号屏蔽字然后调用pause函数,但是由于后者不是原子的,所以有Bug。因为在调用pause之前,信号可能已经产生了,进而导致pause一直阻塞,所以UNIX提供了sigsuspend函数来解决这个问题。

sigsuspend会将进程的信号屏蔽字设置为传入的参数,然后阻塞进程,直到某个没有被阻塞的信号产生,然处理该信号,最后还原信号屏蔽字并返回。

sleep函数的实现

在信号这一章,作者通过实现sleep函数,讲解了信号的一些用法。

sleep函数的作用就是阻塞进程一段时间。作者通过alarm和pause(或者sigsuspend)来实现了sleep函数。

最简单的实现是:

  1. 设置SIGALRM的处理函数
  2. 调用alarm函数定时
  3. 调用pause函数等待SIGALRM(如果产生其他信号,sleep也返回)
  4. 还原SIGALARM信号处理函数

上述的问题在于在调用pause函数之前,已经产生了SIGALRM信号,那么pause函数就会一直阻塞下去。

解决办法就是在调用alarm函数前,屏蔽SIGALRM信号,然后在pause前,取消屏蔽。但是在取消屏蔽后,调用pause之前这一段时间内,也有可能产生SIGALRM信号,所以这个方法也不行。关键问题在于信号会在pause之前产生,所以sigsuspend就派上用场了。

在第三步,我们不再调用pause,而是使用sigsuspend函数等待SIGALRM信号

具体步骤如下

  1. 设置SIGALRM的处理函数并屏蔽SIGALRM
  2. 调用alarm函数定时
  3. 调用sigsupend函数等待SIGALRM(如果产生其他信号,sleep也返回)
  4. 还原SIGALARM信号处理函数及信号屏蔽字

发布于 2019-07-21 19:48