基本定义

可重入函数可以做这样的基本定义:重入意味着这个函数可以重复进入,可以被并行调用,可以被中断,它只使用自身栈上的数据变量,它不依赖于任务环境,在多任务调度过程中,它是安全的,不必担心数据出错。

不可重入函数基本上有相反的定义:不可重入,意味着不可被并行调度,否则会产生不可预料的结果,这些函数提内一般使用了静态(static)的数据结构,使用了malloc()或者free()函数,使用了标准I/O函数等等。

可重入函数

  可重入函数主要用于多任务环境中,简单来说就是可以被中断的函数,即在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,返回控制时不会出现什么错误;也意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是 purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。

  可重入函数使用的变量有两种情况:

  1. 使用局部变量,变量保存在CPU寄存器中或者堆栈中;

  2. 使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

```c
void swap2(int* x, int* y) {
    int tmp;
    tmp=*x;
    *x=*y;
    *y=tmp;
}
```

该函数功能是计算不同篮子里的苹果数,函数体内没有访问全局变量,不使用静态局部变量,只使用局部变量,所以这个函数具有可重入的,如果必须使用全局变量,那么为了保证函数的安全,必须利用互斥信号量或者中断机制来保护全局变量。

1
2
3
4
5
6
7
8
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
 temp = Square_Exam( );
return temp;
}

假设Exam是 int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。

此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam赋与另一个不同的para值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp很可能不是预想中的结果。此函数应如下改进。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
[申请信号量操作] //(1)  加锁
Exam = para;
temp = Square_Exam( );
[释放信号量操作] // 解锁
return temp;
}

不可重入函数

  在多任务系统下,中断可能在任务执行的任何时间发生;不可重入的函数由于使用了一些系统资源,比如全局变量区、中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

  在中断前后不都要保存和恢复上下文吗,怎么会出现函数所依赖的环境发生改变了呢?

  我们知道中断时确实保存一些上下文,但是仅限于返回地址,cpu寄存器等之类的少量上下文,而函数内部使用的诸如全局或静态变量,buffer等并不在保护之列,所以如果这些值在函数被中断期间发生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。

  满足下面条件之一的多数是不可重入函数:

  (1)使用了静态数据结构;

  (2)调用了malloc或free;

  (3)调用了标准I/O函数;

  (4)进行了浮点运算.

malloc/free是不可重入的,它们使用了全局变量来指向空闲区;比如malloc,假如一个进程此时正在执行malloc分配堆空间,此时程序捕捉到信号发生中断,执行信号处理程序中恰好也有一个malloc,这样就会对进程的环境造成破坏,因为malloc通常为它所分配的存储区维护一个链接表,插入执行信号处理函数时,进程可能正在对这张表进行操作,而信号处理函数的调用刚好覆盖了进程的操作,造成错误。

标准io库很多实现都以不可重入的方式使用全局数据结构

许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现)。

1
2
3
4
5
6
7
8
static int sum = 0;
int cout_pear(int *package,int n)
{
   int i;
   for(i = 0; i < n; i++)
  sum += *(package ++); //(1)
   return sum;
}

这个函数由于使用了静态全局变量,对sum的并行性操作结果是未知的,是不安全的操做。若此函数被多个进程调用的话,结果是未知的。因为,但语句(1)执行完一次或者几次后,另外使用这个sum的函数可能正好被调度,并得到运行机会,那么这个新运行的函数将使sum变成了另外的值,所以当(1)重新获得运行机会时,sum的值已经变成了另外的值,这是不可预料的结果。

不可重入的函数改写成可重入函数

把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的:

1、不要使用全局变量。因为别的代码很可能覆盖这些变量值。

2、在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交 互记得打开中断,在有些系列上,这叫做“进入/ 退出核心”。

3、不能调用其它任何不可重入的函数。

4、谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。