危险:中断不可重入的函数

作者: lesca 分类: Concept,FAQ,Ubuntu 发布时间: 2011-03-13 20:22

如果中断一个不可重入的函数,将会引发不可预料的问题。本文举例阐述一个不可重入的函数,将其在不恰当的时候中断,以说明其危害。

lesca原创,转载请注明出处:http://lesca.me/
什么是不可重入的函数?

我们来看一个例子:
[cpp]
#include "apue.h"

static char buf[MAXLINE];

static void
my_alarm(int signo)
{
signal(SIGALRM, my_alarm);
printf("in signal handler\n");
strcpy(buf, "in signal handler");
if(strcmp(buf, "in signal handler") != 0)
printf("handler buf = %s\n", buf);

alarm(1);
}

int
main()
{
signal(SIGALRM, my_alarm);
alarm(1);
while(1){
strcpy(buf, "in main function");
if(strcmp(buf, "in main function") != 0)
printf("main buf = %s\n", buf);
}
}
[/cpp]
这个程序具有全局静态变量buf,主程序先注册一个SIGALRM,以等待1s后执行my_alarm()中断服务程序。等待期间,执行while中的语句,它们包括:1)复制字符串;2)比较字符串;3)数值比较(是否等于零);4)打印输出到标准输出。
这四个过程之间以及执行期间都有可能被中断。也就是说,其输出是不可预测的。
被中断后,执行my_alarm()中断服务程序。它所做的事情与while中的操作差不多。但不同的是,my_alarm()中的代码不会被自己中断。
接着我们看一下可能的运行结果:

$ ./a.out 
in signal handler
in signal handler
main buf = in signafunction
in signal handler
main buf = in signal handler
in signal handler
main buf = in signal handler
in signal handler
in signal handler
^C

分析

我们可以看到:1)每次进入中断服务后,都没有出现字符串被篡改的情况;2)main中的字符串比较函数屡次发现字符串被篡改;3)不仅如此,其中有一次(第一次)出现了一个奇怪的字符串。
那么为什么会这样呢?
我们已经知道了这个程序在执行过程中,能打断的只有main函数,而且通常情况下可以毫无悬念的确定是在while循环内被中断(因为有1s的延时),这解释了现象1和2。
那么现象3是怎么发生的呢?这是由于strcpy()执行到中途被打断引起的。让我们来思考这样的情况:strcpy(buf, "in main function");复制到一半,被中断,my_alarm()修改了buf缓冲,然后my_alarm()返回,使得strcpy(buf, "in main function");得以继续运行。可是buf已经不是原来的buf了,它的内容是in signal handler,而此时strcpy()还得接着刚刚间断的工作,比如可能复制到字母“f”之前,从而它会复制完f之后的字符串,并返回。
总结
中断一个不可重入的函数是危险的,它会引发一个不可预料的错误,并且这些错误在通常情况下不会被察觉。因此,我们有必要在使用信号中断的时候检查信号处理函数中是否引用了不可中断的函数。

怎样判定一个函数是否可重入?

主要看三点:

  1. 它们是否使用静态数据结构(如全局变量)
  2. 是否调用malloc()或者free()
  3. 标准I/O函数

满足以上三点之一的函数就可以被认为是不可重入的。

版权声明

本文出自 Lesca 技术宅,转载时请注明出处及相应链接。

本文永久链接: https://www.lesca.cn/archives/not-reentrant-code.html

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!