1.什么是可重入和不可重入函数?可重入和不可重入函数主要用于多任务环境。一个可重入的函数,简单的说就是一个可以中断的函数,也就是说这个函数执行的时候可以随时中断,可以调到OS调度去执行另外一段代码,返回控制时不会出错;而不可重入函数由于使用了一些系统资源,比如全局变量区、中断向量表等,如果被中断,可能会出现问题。这种类型的功能无法在多任务环境中运行。也可以理解为reentry就是重复进入。首先,这意味着这个函数可以被中断。其次,这意味着它除了在自己的堆栈上使用变量外,不依赖于任何环境(包括静态)。这样的函数是纯代码(Purecode)是可重入的,允许函数的多个副本运行而不会相互干扰,因为它们使用单??独的堆栈。如果确实需要访问全局变量(包括static),一定要注意互斥手段的实现。可重入函数在并行执行环境中非常重要,但访问全局变量通常会有一些性能损失。2、不可重入函数的缺点在编写可重入函数时,如果使用了全局变量,应该通过关闭中断和信号量(即P、V操作)的方式进行保护。解释:如果使用的全局变量没有被保护,这个函数将不会是可重入的,即当多个进程调用这个函数时,很有可能相关的全局变量变得不可知。例子:假设Exam是一个int类型的全局变量,函数Squre_Exam返回Exam的平方值。那么下面的函数是不可重入的。unsignedintexample(intpara){unsignedinttemp;Exam=para;//(**)temp=Square_Exam();returntemp;}如果这个函数被多个进程调用,结果可能是未知的,因为当(**)语句只是执行后可能会激活另一个使用这个函数的进程,那么当新激活的进程执行这个函数时,Exam会被赋予不同的para值,所以当控制返回到temp=Square_Exam()时,计算出的temp可能不是预期结果。这个函数应该改进如下:unsignedintexample(intpara){unsignedinttemp;[申请信号量操作]//(1)Exam=para;temp=Square_Exam();[释放信号量操作]returntemp;}如果不能申请“semaphore”,表示另一个进程正在给Exam赋值并计算其平方(即使用这个信号),而这个进程必须等待信号被释放才能继续执行。如果应用了该信号,它可以继续执行,但其他进程必须等待该进程释放信号量后才能使用该信号。3、保证函数可重入的方法写函数时尽量使用局部变量(如寄存器、栈中的变量),保护要使用的全局变量(如关闭中断、信号量等),这样构成的函数一定是可重入函数。在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成一个不可重入的函数,那么不同的任务在调用这个函数的时候可能会修改其他调用这个函数的任务的数据,造成不可预知的后果。不可重入函数在实时系统设计中被认为是不安全的函数。大多数满足以下条件的函数都是不可重入的:函数体中使用了静态数据结构;malloc()或free()函数在函数体中被调用;在函数体中调用标准I/O函数。下面举例说明。A.可重入函数voidstrcpy(char*lpszDest,char*lpszSrc){while(*lpszDest++=*lpszSrc++);*dest=0;}B.不可重入函数1charcTemp;//全局变量voidSwapChar1(char*lpcX,char*lpcY){cTemp=*lpcX;*lpcX=*lpcY;lpcY=cTemp;//访问的全局变量}C.不可重入函数2voidSwapChar2(char*lpcX,char*lpcY){staticcharcTemp;//静态局部变量cTemp=*lpcX;*lpcX=*lpcY;lpcY=cTemp;//使用静态局部变量}问题一:可重入函数怎么写?答:不要在函数体中访问那些全局变量,不要使用静态局部变量变量,坚持只使用局部变量,你写的函数是可重入的。如果必须访问全局变量,请记住使用互斥信号量保护全局变量。问题2:如何将不可重入函数改写为可重入函数?答案:使不可重入函数可重入的唯一方法是用可重入规则重写它。其实很简单,只要遵循几条通俗易懂的规则,那么写出来的函数就是可重入的。不要使用全局变量。因为其他代码很可能会覆盖这些变量值。在和硬件交互的时候,记得执行disinterrupt()这样的操作,就是关闭硬件中断。记住在完成交互后打开中断,在某些家庭中这被称为“进入/退出核心”。不能调用任何其他不可重入函数。谨慎使用堆栈。最好在使用OSENTERKERNAL之前。堆栈操作涉及到内存分配,一不小心就会导致其他任务的数据被覆盖。因此,请谨慎使用堆栈!最好不要用!许多黑客程序就是利用这一点,让系统执行非法代码,轻而易举地获得系统控制权。仍然有一些规则。总之,永远记住一句话:保证中断是安全的!例题:我曾经设计过如下函数,在代码检查时被提示有一个bug,因为这个函数是不可重入的,为什么?unsignedintsum_int(unsignedintbase){unsignedintindex;staticunsignedintsum=0;//注意,是静态类型for(index=1;index<=base;index++)sum+=index;returnsum;}分析:所谓函数是可重入的(也可以说是可预测的),即只要输入数据相同,就应该产生相同的输出。这个函数之所以不可预测,是因为函数中使用了静态变量。由于静态变量的特性,这样的函数被称为:具有“内存”功能的函数。因此,如果需要可重入的函数,必须避免在函数中使用静态变量。在这类函数中使用静态变量的原则是能用多少就用多少。把上面的函数修改成可重入函数,只要去掉声明的sum变量中的static关键字,变量sum就变成auto类型变量,函数就变成可重入函数了。当然,有时候,函数中必须使用静态变量。例如,当函数的返回值是指针类型时,必须使用静态局部变量的地址作为返回值。如果是auto类型,返回值为false。指针。这种情况发生在多任务系统中。当在任务执行期间捕获并处理信号时,进程正在执行的指令序列会暂时被信号处理程序中断。如果它从信号处理程序返回,它会继续在进程的断点处执行正常的指令序列。从断点恢复到重新执行,函数所依赖的环境没有改变,所以说函数是可重入的。否则,它是不可重入的。4、最后,众所周知,在进程中断时,系统会保存和恢复进程的上下文。但是,恢复的上下文仅限于返回地址、cpu寄存器等少数上下文,全局或静态变量、缓冲区等函数不受保护,所以如果这些值在函数被中断,当函数在断点处继续执行时,结果将不可预测。比如malloc,如果此时一个进程正在执行malloc分配堆空间,此时程序捕获到一个signal被中断,而执行signalhandler中恰好有一个malloc,会造成损坏进程环境,因为malloc通常会为它分配的存储区维护一个链表。当信号处理函数被插入执行时,进程可能正在对这张表进行操作,而信号处理函数的调用正好覆盖了进程的操作,从而导致错误。大多数满足以下条件之一的函数都是不可重入函数:使用静态数据结构;调用malloc或free;调用标准I/O函数;标准io库的许多实现都以不可重入的方式使用全局数据结构。执行浮点运算。在许多处理器/编译器中,浮点数通常是不可重入的(浮点运算大多使用协处理器或软件仿真实现)。
