可重入和不可重入

AI权益加码!Claude Code、Cursor等20+工具免费用! 购周边限时加赠Coding Plan Lite,畅享主流AI工具!学习进阶更高效! 阅读详情
 

可重入和不可重入

Posted on 2009-07-05 12:00 Prayer 阅读(326) 评论(0)   编辑  收藏 引用 所属分类: C/C++LINUX/UNIX/AIX <!---->
这种情况出现在多任务系统当中,在任务执行期间捕捉到信号并对其进行处理时,进程正在执行的指令序列就被信号处理程序临时中断。如果从信号处理程序返回,则继续执行进程断点处的正常指令序列,从重新恢复到断点重新执行的过程中,函数所依赖的环境没有发生改变,就说这个函数是可重入的,反之就是不可重入的。
众所周知, 在进程中断期间,系统会保存和恢复进程的上下文,然而恢复的上下文仅限于返回地址,cpu寄存器等之类的少量上下文,而函数内部使用的诸如全局或静态变量,buffer等并不在保护之列,所以如果这些值在函数被中断期间发生了改变,那么当函数回到断点继续执行时,其结果就不可预料了。打个比方,比如malloc,将如一个进程此时正在执行malloc分配堆空间,此时程序捕捉到信号发生中断,执行信号处理程序中恰好也有一个malloc,这样就会对进程的环境造成破坏,因为malloc通常为它所分配的存储区维护一个链接表,插入执行信号处理函数时,进程可能正在对这张表进行操作,而信号处理函数的调用刚好覆盖了进程的操作,造成错误。

满足下面条件之一的多数是不可重入函数:
(1)使用了静态数据结构;
(2)调用了malloc或free;
(3)调用了标准I/O函数;标准io库很多实现都以不可重入的方式使用全局数据结构。
(4)进行了浮点运算.许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多使用协处理器或者软件模拟来实现。

 

1) 信号处理程序A内外都调用了同一个不可重入函数B;B在执行期间被信号打断,进入A (A中调用了B),完事之后返回B被中断点继续执行,这时B函数的环境可能改变,其结果就不可预料了。
2) 多线程共享进程内部的资源,如果两个线程A,B调用同一个不可重入函数F,A线程进入F后,线程调度,切换到B,B也执行了F,那么当再次切换到线程A时,其调用F的结果也是不可预料的。
在信号处理程序中即使调用可重入函数也有问题要注意。作为一个通用的规则,当在信号处理程序中调用可重入函数时,应当在其前保存errno,并在其后恢复errno。(因为每个线程只有一个errno变量,信号处理函数可能会修改其值,要了解经常被捕捉到的信号是SIGCHLD,其信号处理程序通常要调用一种wait函数,而各种wait函数都能改变errno。)

 

可重入函数列表:

_exit()、 access()、alarm()、cfgetispeed()、cfgetospeed()、cfsetispeed()、cfsetospeed ()、chdir()、chmod()、chown()、close()、creat()、dup()、dup2()、execle()、 execve()、fcntl()、fork()、fpathconf ()、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()。

 

书上关于信号处理程序中调用不可重入函数的例子:

#include <stdlib.h>
#include <stdio.h>
#include <pwd.h>

static void func(int signo)
{
    struct passwd *rootptr;
    if( ( rootptr = getpwnam( "root" ) ) == NULL )
    {
        err_sys( "getpwnam error" );
    }
    signal(SIGALRM,func);
    alarm(1);
}

int main(int argc, char** argv)
{
    signal(SIGALRM,func);
    alarm(1);
    for(;;)
    {
        if( ( ptr = getpwnam("sar") ) == NULL )
        {
            err_sys( "getpwnam error" );
        }
    }
    return 0;
}

signal了一个SIGALRM,而后设置一个定时器,在for函数运行期间的某个时刻,也许就是在getpwnam函数运行期间,相应信号发生中断,进入信号处理函数func,在运行func期间又收到alarm发出的信号,getpwnam可能再次中断,这样就很容易发生不可预料的问题。

可重入函数与不可重入函数

主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任务环境下的。

也可以这样理解,重入即表示重复进入,首先它意味着这个函数可以被中断,其次意味着它除了使用自己栈上的变量以外不依赖于任何环境(包括static),这样的函数就是purecode(纯代码)可重入,可以允许有该函数的多个副本在运行,由于它们使用的是分离的栈,所以不会互相干扰。如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。

编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。

说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。


示例:假设Exam是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很可能不是预想中的结果。此函数应如下改进。

     unsigned int example( int para ) {
         unsigned int temp;
         [申请信号量操作] //(1)
         Exam = para;
         temp = Square_Exam( );
         [释放信号量操作]
         return temp;
     }
     (1)若申请不到“信号量”,说明另外的进程正处于给Exam赋值并计算其平方过程中(即正在使用此信号),本进程必须等待其释放信号后,才可继续执行。若申请到信号,则可继续执行,但其它进程必须等待本进程释放信号量后,才能再使用本信号。

    保证函数的可重入性的方法:
    在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量),对于要使用的全局变量要加以保护(如采取关中断、信号量等方法),这样构成的函数就一定是一个可重入的函数。
     VxWorks中采取的可重入的技术有:
     * 动态堆栈变量(各子函数有自己独立的堆栈空间)
     * 受保护的全局变量和静态变量
     * 任务变量


--------------------------------------------------
    在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。那么什么是可重入函数呢?
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。不可重入函数在实时系统设计中被视为不安全函数。

满足下列条件的函数多数是不可重入的:
     1) 函数体内使用了静态的数据结构;
     2) 函数体内调用了malloc()或者free()函数;
     3) 函数体内调用了标准I/O函数。

    下面举例加以说明。
     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;//使用了静态局部变量
     }

    问题1,如何编写可重入的函数?
    答:在函数体内不访问那些全局变量,不使用静态局部变量,坚持只使用局部变量,写出的函数就将是可重入的。如果必须访问全局变量,记住利用互斥信号量来保护全局变量。

    问题2,如何将一个不可重入的函数改写成可重入的函数?
    答:把一个不可重入函数变成可重入的唯一方法是用可重入规则来重写它。其实很简单,只要遵守了几条很容易理解的规则,那么写出来的函数就是可重入的。
     1) 不要使用全局变量。因为别的代码很可能覆盖这些变量值。
     2) 在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”。
     3) 不能调用其它任何不可重入的函数。
     4) 谨慎使用堆栈。最好先在使用前先OS_ENTER_KERNAL。

    堆栈操作涉及内存分配,稍不留神就会造成益出导致覆盖其他任务的数据,所以,请谨慎使用堆栈!最好别用!很多黑客程序就利用了这一点以便系统执行非法代码从而轻松获得系统控制权。还有一些规则,总之,时刻记住一句话:保证中断是安全的!

    实例问题:曾经设计过如下一个函数,在代码检视的时候被提醒有bug,因为这个函数是不可重入的,为什么?
     unsigned int sum_int( unsigned int base ) {
         unsigned int index;
         static unsigned int sum = 0; // 注意,是static类型
         for (index = 1; index <= base; index++)
             sum += index;
         return sum;
     }


    分析:所谓的函数是可重入的(也可以说是可预测的),即只要输入数据相同就应产生相同的输出。这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果需要一个可重入的函数,一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。
    将上面的函数修改为可重入的函数,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto类型的变量,函数即变为一个可重入的函数。
    当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。


一些关于线程的可重入不可重入的知识 线程安全:简单来说线程安全就是多个线程并发同一段代码时,不会出现不同的结果,我们就可以说该线程是安全的;线程不安全:说完了线程安全,线程不安全的问题就很好解释,如果多线程并发执行时会产生不同的结果,则该线程就是不安全的。线程安全产生的原因:大多是因为对全局变量静态变量的操作。 阅读详情

相关推荐

可重入函数不可重入函数

可重入函数不可重入函数 作为一个编程人员,一定要有可重入不可重入概念,但工作中发现的现象让我大跌眼镜——很多人没听说过!!!亡羊补牢,为时不晚,现在在脑子中种下这颗概念种子也不迟。为什么说一定要有可重入不可重入的概念呢,因为我们的程序肯定不只在main()函数中简单的跑一个while(1)循环,一般都会涉及到中断操作系统。这时,就必须要考虑一个函数是不是可重入的以及如果不可重入对程序设计会...

guoweilkd的博客 571

可重入的解释

声明:本文转载自维基百科http://zh.wikipedia.org/zh/%E5%8F%AF%E9%87%8D%E5%85%A5 可重入的概念          若一个程序或子程序可以「安全的被并行执行(Parallel computing)」,则称其为可重入(reentrant或re-entrant)的。即当该子程序正在运行时,可以再次进入并执行它(并行执行时,个别的执行结果,都

lsrmake的专栏 1849

可重入程序与不可重入程序的区分

可重入性的英文关键词为Reentrancy,这里首先要区分可抢占这一概念。在计算机里面,程序的运行可以说是一堆机器指令被放入CPU进行顺序执行,但是操作系统为了更好地管理程序,就出现了各式各样的载体概念,比如说进程、线程、任务,究其本质,都是相对于“调度”这个操作,它们只是调度的

betterlord的专栏 5078

什么是可重入(Reentrant)?

一、什么是重入? 函数在执行时,由于外部原因或内部调用,又一次进入该函数执行。 二、重入发生场景 多个线程同时执行该函数 函数自身调用自身 三、什么是可重入 在相同的输入下,函数每次被调用产生的结果相同。 栗子: int add(int a, int b) { return a + b; } 每次调用add(1, 2),得到的结果都是3. 四、可重入的函数必须具备哪些特点 不使用任何静态或全局的非const对象 不返回任何静态或全局的非const对象的指针 只依赖于调用方提

xunye的博客 9470

详解可重入函数与不可重入函数

详解函数的可重入性;详解可重入函数不可重入函数;如何设计可重入的函数。

wangyx1234的博客 7605

可重入不可重入锁的区别

不可重入锁示例(同一个线程不可重入上锁后的代码段) 如下是一个不可重入锁的逻辑过程,会发现执行main方法控制台会打印执行doJob方法前,然后就会一直线程阻塞,不会打印执行doJob方法过程中,原因在于第一次上锁后,由于没有释放锁,因此执行第一次lock后isLocked = true,这个时候调用doJob()内部又一次调用了lock()由于上个线程将isLocked = true,导致再次进入的时候就进入死循环。导致线程无法执行System.out.println("执行doJob方法过程中");这

诗水人间 8万+

可重入不可重入锁的理解

可重入锁:ReentrantLock 在学JUC的时候,听到可重入锁这个词,不理解它的概念,网上搜索一番,还是有点迷糊,所以自己再来做一下笔记,理一理思路。 一、锁是什么? 我们这里提到的锁,是指把所需要的代码块,资源,或数据锁上,在操作访问他们的时候只允许一个线程去做操作。最终结果是为了保证cpu计算结果的正确性。 二、可重入锁与不可重入锁的区别? 1、不可重入锁: 只判断这个锁有没有被锁上,只要被锁上,申请锁的线程都会被要求等待。实现简单 2、可重入锁: 不仅判断锁有没有被锁上,还会判断锁是哪个线程锁

qq_37975919的博客 5735

可重入不可重入

如果把证件/卡看作是同步锁,把关卡看作是同步域(即同步代码块/方法或显式锁锁定的代码),那么可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。Reentrant = Re + entrant,Re是重复、又、再的意思,entrant是enter的名词或者形容词形式,翻译为进入者或者可进入的,所以Reentrant翻译为可重复进入的、可再次进入的,因此ReentrantLock翻译为重入锁或者再入锁。

Dream It Possible 3400

可重入不可重入锁的理解,他们的区别及实现原理解析。

首先我们这里提到的锁,是把所需要的代码块,资源,或数据锁上,在操作他们的时候只允许一个线程去做操作。最终结果是为了保证cpu计算结果的正确性。   对不可重入锁的理解: public class Test{ Lock lock = new Lock(); public void methodA(){ lock.lock(); .......

Anakki的博客 2万+

可重入不可重入锁概念区别

可重入锁就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得该锁。 这种情景,可以是不同的线程分别调用这个两个方法。也可是同一个线程,A方法中调用B方法,这个线程调用A方法。 不可重入锁就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不...

u013452335的博客 1万+

基于锁的占有权是否可重入进行分类——可重入不可重入

递归操作场景文件/目录树处理组织架构树遍历递归数学计算(如斐波那契数列缓存)XML/JSON树解析事务处理场景银行核心系统(转账、清算)交易订单处理库存管理系统计费系统需要高级控制的场景高并发库存管理实时交易系统资源池管理(数据库连接池)任务调度系统框架内部实现Spring事务管理Hibernate会话管理线程池任务处理消息队列消费者。

weixin_58239025的博客 1400

Java中的可重入不可重入

锁的简单应用 用lock来保证原子性(this.count++这段代码称为临界区) 什么是原子性,就是不可分,从头执行到尾,不能被其他线程同时执行。 可通过CAS来实现原子操作 CAS(Compare and Swap): CAS操作需要输入两个数值,一个旧值(期望操作前的值)一个新值,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。 CAS主要通过c...

Apple_Boy的博客 2210

重入不可重入

偶然看到公众号推荐了一篇重入重入锁,结合它写的可重入锁 案例一起探讨一下? 1.重入不可重入锁? 可重入锁:当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。例如synchronized ReentrantLock 都是可重入锁。 不可重入锁:当一个线程获取锁对象之后,必须等待该线程释放锁才可以获取其他对象的锁,否则会所阻塞。例如Lock锁,都是不可重入锁 2.可重入锁意义 防止锁阻塞 3.不可重入ReentrantLock以及Synchronzied

qq_39150329的博客 2280

【Linux系统编程】可重入不可重入函数

00. 目录 文章目录00. 目录01. 不可重入函数02. 可重入函数03. 判断条件04. Linux常用可重入函数05. 附录 01. 不可重入函数 在实时系统的设计中,经常会出现多个任务调用同一个函数的情况。如果有一个函数不幸被设计成为这样:那么不同任务调用这个函数时可能修改其他任务调用这个函数的数据,从而导致不可预料的后果。这样的函数是不安全的函数,也叫不可重入函数。 02. 可重入函数...

C/C++软件工程师、嵌入式软件工程师、物联网研发工程师、C/C++讲师、物联网讲师、嵌入式讲师、鸿蒙讲师---欢迎大家一起交流(私信添加博主微信) 2626

C++可重入函数不可重入函数

C++可重入函数不可重入函数 可重入函数是指能够被多个线程“同时”调用的函数,并且能保证函数结果正确不必担心数据错误的函数。 不可重入函数是指不能运行在多任务环境下,除非能保证互斥的函数。 由于使用了未受保护的系统资源,如全局变量区,中断向量表等。 不可重入函数在实时系统设计中被视为不安全函数。 可重入函数特点: 没有静态数据结构 不返回指向静态数据的指针 所有函数数据由函数的调用者提供 使用auto变量,或通过全局变量的拷贝来保护全局变量 若必须访问全局变量,则利用互斥信号保护 不调用不可重入

Sunrise的博客 3234

<<APUE>> 可重入函数 不可重入

要熄灯断电了,明天考试,明天再修改 可重入函数 可重入函数(reentrant):在任意时刻可以被中断,稍后再继续运行,不会丢失数据,即不用担心数据被其他线程篡改。可重入函数必须满足以下三个条件: 可以在执行的过程中可以被打断; 被打断之后,在该函数一次调用执行完之前,可以再次被调用(或进入,reentered)。 再次调用执行完之后,被打断的上次调用可以继续恢复执行,并正确执行。 不可重入函数 多任务调用某一不可重入函数时,可能修改其他任务调用该函数的数据,从而导致不可预料的后果。 即不可重入函数

jiewaikexue的博客 867
上一篇: linux驱动开发流程
下一篇: 进程与线程的区别(转)
EN_wang
博客等级 码龄15年 36粉丝 32原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值