《2022年2022年可重入函数的概念 .pdf》由会员分享,可在线阅读,更多相关《2022年2022年可重入函数的概念 .pdf(12页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、1 可重入函数的概念主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入 OS 调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是 purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,
2、所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V 操作)等手段对其加以保护。说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。示例:假设 Exam 是 int 型全局变量,函数Squre_Exam 返回 Exam 平方值。那么如下函数不具有可重入性。unsigned int example(int para)unsigned int t
3、emp;Exam=para;/(*)temp=Square_Exam();return temp;此函数若被多个进程调用的话,其结果可能是未知的,因为当(*)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使Exam 赋与另一个不同的para 值,所以当控制重新回到“temp=Square_Exam()”后,计算出的 temp 很可能不是预想中的结果。此函数应如下改进。unsigned int example(int para)unsigned int temp;申请信号量操作 /(1)Exam=para;temp=Square_Exam();释放信
4、号量操作 return temp;(1)若申请不到“信号量”,说明另外的进程正处于给Exam 赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 12 页 -2 能再使用本信号。保证函数的可重入性的方法:在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。VxWorks 中采取的可重入的技术有:*动态堆栈变量(各子函数有自己独
5、立的堆栈空间)*受保护的全局变量和静态变量*任务变量二.实时系统中的可重入函数在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。满足下列条件的函数多数是不可重入的:1)函数体内使用了静态的数据结构;2)函数体内调用了malloc()或者 free()函数;3)函数体内调用了标准I/O 函数。下面举例加以
6、说明。A.可重入函数void strcpy(char*lpszDest,char*lpszSrc)while(*lpszDest+=*lpszSrc+);*dest=0;B.不可重入函数 1 charcTemp;/全局变量void SwapChar1(char*lpcX,char*lpcY)cTemp=*lpcX;*lpcX=*lpcY;lpcY=cTemp;/访问了全局变量 C.不可重入函数 2 void SwapChar2(char*lpcX,char*lpcY)static char cTemp;/静态局部变量cTemp=*lpcX;*lpcX=*lpcY;lpcY=cTemp;/使用了
7、静态局部变量 名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 12 页 -3 三.办法问题 1,如何编写可重入的函数?答:在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。问题 2,如何将一个不可重入的函数改写成可重入的函数?答:把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。1)不要使用全局变量。因为别的代码很可能覆盖这些变量值。2)在和硬件发生交互的时候,切记执行类似disinterrup
8、t()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”。3)不能调用其它任何不可重入的函数。4)谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。堆栈操作涉及内存分配,稍不留神就会造成益出导致覆盖其他任务的数据,所以,请谨慎使用堆栈!最好别用!很多黑客程序就利用了这一点以便系统执行非法代码从而轻松获得系统控制权。还有一些规则,总之,时刻记住一句话:保证中断是安全的!四.实例实例问题:曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?unsigned int sum_int(unsigned int b
9、ase)unsigned int index;static unsigned int sum=0;/注意,是 static类型for(index=1;index=base;index+)sum+=index;return sum;分析:所谓的函数是可重入的(也可以说是可预测的),即只要输入数据相同就应产生相同的输出。这个函数之所以是不可预测的,就是因为函数中使用了 static变量,因为 static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果需要一个可重入的函数,一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。将上面的函
10、数修改为可重入的函数,只要将声明sum 变量中的 static 关键字去掉,变量 sum即变为一个 auto类型的变量,函数即变为一个可重入的函数。当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是 static的局部变量的地址作为返回值,若为 auto名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 12 页 -4 类型,则返回为错指针。可重入函数和不可重入函数以及区别在 实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数
11、据,从而导致不可预料的后果。那么什么是可重入性,可重入函数呢?什么是可重入性?可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入 函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。可重入函数:(1)不为连续的调用持有静态数据。(2)不返回指向静态数据的指针;所有数据都由函数的调用者提供。(3)使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数
12、据。(4)如果必须访问全局变量,记住利用互斥信号量来保护全局变量。(5)绝不调用任何不可重入函数。不可重入函数:(1)函数中使用了静态变量,无论是全局静态变量还是局部静态变量。(2)函数返回静态变量。(3)函数中调用了不可重入函数。(4)函数体内使用了静态的数据结构;(5)函数体内调用了malloc()或者 free()函数;(6)函数体内调用了其他标准I/O 函数。(7)函数是singleton 中的成员函数而且使用了不使用线程独立存储的成员变量。总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。示例在多线程条件下,函数应当是线程安全的,进一步,更强的条件是可
13、重入的。可重入函数保证了在多线程条件下,函数的状态不会出现错误。以下分别是一个不可重入和可重入函数的示例:/c code static int tmp;void func1(int*x,int*y)tmp=*x;*x=*y;名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 12 页 -5*y=tmp;void func2(int*x,int*y)int tmp;tmp=*x;*x=*y;*y=tmp;func1 是不可重入的,func2 是可重入的。因为在多线程条件下,操作系统会在func1 还没有执行完的情况下,切换到另一个线程中,那个线程可能再次调用func1,这样状态就错了。
14、函数编写规范1:对所调用函数的错误返回码要仔细、全面地处理2:明确函数功能,精确(而不是近似)地实现函数设计3:编写可重入函数时,应注意局部变量的使用(如编写C/C+语言的可重入函数时,应使用auto 即缺省态局部变量或寄存器变量)说明:编写 C/C+语言的可重入函数时,不应使用 static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。4:编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V 操作)等手段对其加以保护说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。示例:假设 Exam 是
15、int 型全局变量,函数Squre_Exam返回 Exam 平方值。那么如下函数不具有可重入性。unsigned int example(int para)unsigned int temp;Exam=para;/(*)temp=Square_Exam();return temp;此函数若被多个进程调用的话,其结果可能是未知的,因为当(*)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此 函数时,将使Exam 赋与另一个不同的para 值,所以当控制重新回到“temp=Square_Exam()”后,计算出的 temp很可能不是预想中的结果。此函数应如下改进。
16、unsigned int example(int para)unsigned int temp;名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 12 页 -6 申请信号量操作 /若申请不到“信号量”,说明另外的进程正处于Exam=para;/给 Exam 赋值并计算其平方过程中(即正在使用此temp=Square_Exam();/信号),本进程必须等待其释放信号后,才可继释放信号量操作 /续执行。若申请到信号,则可继续执行,但其/它进程必须等待本进程释放信号量后,才能再使/用本信号。return temp;5:在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还
17、是由接口函数本身负责,缺省是由函数调用者负责说明:对于模块间接口函数的参数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。6:防止将函数的参数作为工作变量说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。示例:如下函数的实现就不太好。void sum_data(unsigned int num,int
18、*data,int*sum)unsigned int count;*sum=0;for(count=0;count num;count+)*sum+=datacount;/sum成了工作变量,不太好。若改为如下,则更好些。void sum_data(unsigned int num,int*data,int*sum)unsigned int count;int sum_temp;sum_temp=0;名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 12 页 -7 for(count=0;count b)?a:b;改为如下就很清晰了。int max(int a,int b)retu
19、rn(a b)?a:b);value=max(a,b);或改为如下。#define MAX(a,b)(a)(b)?(a):(b)value=MAX(a,b);10:不要设计多用途面面俱到的函数说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。11:函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出说明:带有内部“存储器”的函数的功能可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在 C/C+语言中,函数的static局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值
20、为指针类型时,则必须是 STATIC 的局部变量的地址作为返回值,若为AUTO 类,则返回为错针。示例:如下函数,其返回值(即功能)是不可预测的。名师资料总结-精品资料欢迎下载-名师精心整理-第 7 页,共 12 页 -8 unsigned int integer_sum(unsigned int base)unsigned int index;static unsigned int sum=0;/注意,是 static类型的。/若改为 auto 类型,则函数即变为可预测。for(index=1;index B-C-A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调
21、用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。30:仔细分析模块的功能及性能需求,并进一步细分,同时若有必要画出有关数据流图,据此来进行模块的函数划分与组织说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结构,关系到模块的最终效率和可维护性、可测性等。根据模块的功能图或/及数据流图映射出函数结构是常用方法之一。31:改进模块中函数的结构,降低函数间的耦合度,并提高函数的独立性以及代码可读性、效率和可维护性优化函数结构时,要遵守以下原则:(1)不能影响模块功能的实现。(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。名师资
22、料总结-精品资料欢迎下载-名师精心整理-第 11 页,共 12 页 -12(3)通过分解或合并函数来改进软件结构。(4)考查函数的规模,过大的要进行分解。(5)降低函数间接口的复杂度。(6)不同层次的函数调用要有较合理的扇入、扇出。(7)函数功能应可预测。(8)提高函数内聚。(单一功能的函数内聚最高)说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。32:在多任务操作系统的环境下编程,要注意函数可重入性的构造说明:可重入性是指函数可以被多个任务进程调用。在多任务操作系统中,函数是否具有可重入性是非常重要的,因为这是多个进程可以共用此函数的必要条件。另外,编译器是否提供可重入函数库,与
23、它所服务的操作系统有关,只有操作系统是多任务时,编译器才有可能提供可重入函数库。如DOS 下 BC 和MSC 等就不具备可重入函数库,因为DOS 是单用户单任务操作系统。33:避免使用 BOOL 参数说明:原因有二,其一是BOOL 参数值无意义,TURE/FALSE 的含义是非常模糊的,在调用时很难知道该参数到底传达的是什么意思;其二是BOOL 参数值不利于扩充。还有NULL 也是一个无意义的单词。34:对于提供了返回值的函数,在引用时最好使用其返回值35:当一个过程(函数)中对较长变量(一般是结构的成员)有较多引用时,可以用一个意义相当的宏代替说明:这样可以增加编程效率和程序的可读性。示例:
24、在某过程中较多引用TheReceiveBufferFirstSocket.byDataPtr,则可以通过以下宏定义来代替:#define pSOCKDATA TheReceiveBufferFirstScoket.byDataPtr 可重入函数列表:_exit()、access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、execle()、execve()、fcntl()、fork()、fpathcon
25、f()、fstat()、fsync()、getegid()、geteuid()、getgid()、getgroups()、getpgrp()、getpid()、getppid()、getuid()、kill()、link()、lseek()、mkdir()、mkfifo()、open()、pathconf()、pause()、pipe()、raise()、read()、rename()、rmdir()、setgid()、setpgid()、setsid()、setuid()、sigaction()、sigaddset()、sigdelset()、sigemptyset()、sigfillset()、sigismember()、signal()、sigpending()、sigprocmask()、sigsuspend()、sleep()、stat()、sysconf()、tcdrain()、tcflow()、tcflush()、tcgetattr()、tcgetpgrp()、tcsendbreak()、tcsetattr()、tcsetpgrp()、time()、times()、umask()、uname()、unlink()、utime()、wait()、waitpid()、write()。名师资料总结-精品资料欢迎下载-名师精心整理-第 12 页,共 12 页 -
限制150内