函数是否安全?
在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。
那么什么是可重入函数呢?所谓可重入是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。可重入函数简单来说,就是可以被打断的函数。就是说,你可以在这个函数执行的任何时候打断他的运行,在任务调度下去执行另外一段代码而不会出现什么错误。也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。
而不可重入的函数由于使用了一些系统资源,比如全局(静态)变量存储区、中断向量表等等,所以其如果被中断的话,可能出现问题,所以不可重入函数是不能运行在多任务环境下的。
不可重入函数的特点
基本上符合以下特点之一的函数都是不可重入的:
① 函数体内使用了静态的数据结构;
② 函数体内调用了标准I/O函数(如printf);
③ 函数体内调用了malloc()或者free()函数。
假设Exam是int型全局变量,函数Squre_Exam返回Exam平方值。那么如下函数不具有可重入性。
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
Exam = para; // (**)
temp = Square_Exam( );
return temp;
}
此函数若被多个进程调用的话,其结果可能是未知的,因为当(**)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使 Exam 赋与另一个不同的 para 值,所以当控制重新回到 “temp = Square_Exam()” 后,计算出的temp很可能不是预想中的结果。此函数应如下改进。
int Exam = 0;
unsigned int example( int para )
{
unsigned int temp;
[申请互斥信号量操作] //(1) 加锁
Exam = para;
temp = Square_Exam( );
[释放互斥信号量操作] // 解锁
return temp;
}
如果本任务申请不到信号量,说明别的的任务正处于给 Exam 赋值并计算其平方过程中(即正在使用此全局变量),本任务必须等待其释放信号量后,才可继续执行。若申请到信号量,则可继续执行,但其它任务必须等待本任务释放信号量后,才能再使用本全局变量。
可重入函数的编写规则
把一个不可重入函数变成可重入的唯一方法是用可重入规则来重塑它。
其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的:
① 不要使用全局(静态)存储区的变量。因为别的代码很可能覆盖这些变量值。
② 如果使用全局变量或是和硬件发生交互的时候,务必执行类似DisInterrupt()之类的操作,就是关闭硬件中断。完成交互后记得立即打开中断,在有些系统上,这叫做“进入/退出核心”或者用OS_ENTER_KERNAL/OS_EXIT_KERNAL(临界区保护)来描述。
③ 函数内部不能调用任何不可重入的函数。
④ 谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者当使用全局变量时则务必要对全局变量予以保护。
总之,时刻记住一句话:保证函数中的变量是安全的!
欢迎大家关注我的公众号: