Linux-浅析可重入函数与不可重入函数

背景

首先函数我们都不陌生,学过任何一门编程语言的都知道函数。那么什么是可重入函数?

我们都知道,函数是一段被加载到内存中的代码。函数的代码可短可长,执行的长度也不能确定。在多线程中,线程之间是可以切换的。函数是一段写好的既定的代码,属于程序共有的代码段。一个进程中有多个线程,每一个线程都有可能调用这段函数去执行。然而,多线程环境中,你也不知道下一秒是那个线程在运行,随时随地都是在变化的。所以,既然我们无法掌控进程的调度,线程的切换,那么就要做到一件事情就是,不论你的线程怎么切换,我的代码执行的结果都要一致。
那么如何才能保证线程切换但是函数执行是一致的。 线程是随时随地可以被切换的,而函数代码又是线程共有的,所以有可能第一个线程执行了该函数后,切换到另一个线程又会执行一次该函数,那么两次函数执行的结果是否一致呢,两次执行的值是否又会互相影响。这些都是不确定的。
所以就引出了重入的概念:所谓的重入,就是同一段代码被重叠多次执行。

可重入函数

可重入的概念只针对于函数本身的设计,与多线程单线程无关。我们所写的函数,都应该满足可重入函数的特性,这样让代码更可靠。而不可重入函数则是被中断嵌套执行后,之前执行的函数或者说被嵌套执行的函数的结构都不正确或者一些不正确了。这样的结果不是我们想要的。因为函数重叠执行,会导致每一层的函数相互影响,结果都不对。这样的函数是不安全的。我们要避免这个情况的发生。因此可重入函数就是必须重视的了。只要做到函数被嵌套后,每一层函数执行的结果都能正确,那就是可重入函数了。

可重入函数(即可以被中断的函数)可以被一个以上的任务调用,而不担心数据破坏。可重入函数在任何时候都可以被中断,而一段时间之后又可以恢复运行,而相应的数据不会破坏或者丢失。

可重入函数使用的变量有两种情况:
1.使用局部变量,变量保存在CPU寄存器中或者堆栈中;
2.使用全局变量,但是这时候要注意保护全局变量(防止任务中断后被其它任务改变变量)。
举一个例子:

void strcpy(char* dest,char* src)
{
    {
        ;
    }
    *dest = NULL;
}

上面这个例子我们都不陌生是用来实现库函数strcpy的函数,这个函数就是可重入函数,原因是因为,该函数的参数均在堆栈中,在多线程切换的情况下,仍然能够保证数据的完整性,不会破环指针的指向。

常用的可重入函数的方法有:

  1. 不要使用全局变量,防止别的代码覆盖这些变量的值。
  2. 调用这类函数之前先关掉中断,调用完之后马上打开中断。防止函数执行期间被中断进入别的任务执行。
  3. 使用信号量(互斥条件)。
    总之:要保证中断是安全的

不可重入函数

先看一个例子:

 int temp;
 void swap(int *ex1,int *ex2)
{
    temp = *ex1; 
    *ex1 = *ex2;
    *ex2 = temp;
}

这个函数是不可重入的函数,原因是因为定义了全局变量temp,当任务1在执行该代码段时,执行了一半后被切出去,另一个任务2也来执行该函数,等到再由任务2切换回任务1时,数据已经不是原来的数据了,这样就产生了数据的二义性。

但是可能会有一些疑惑。我们再学习进程的描述时,在进程的PCB中有一个东西叫做上下文数据,目的就是为了保护进程切换时,上下文的数据,防止进程切换时导致数据的不一致。中断结束会恢复上下文。
怎么会出现函数所依赖的环境发生改变了呢?

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

满足下面条件之一的多数是不可重入函数:
(1)使用了静态数据结构;
(2)调用了malloc或free;
(3)调用了标准I/O函数;
(4)进行了浮点运算.

一些规则

我们在写代码时,应尽量避免写出不可重入的函数,那么如何写出可重入的函数呢?

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

如何将一个不可重入的函数改写成可重入的函数?

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

  1. 不要使用全局变量。因为别的代码很可能覆盖这些变量值。
  2. 在和硬件发生交互的时候,切记执行类似disinterrupt()之类的操作,就是关闭硬件中断。完成交互记得打开中断,在有些系列上,这叫做“进入/退出核心”。
  3. 不能调用其它任何不可重入的函数。
  4. 谨慎使用堆栈。

    参考文章:
    什么是可重入函数与不可重入函数
    linux的可重入函数与不可重入函数

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值