函数的可重入性分析
问题起源:最近在看Linux环境编程:从应用到内核 第0章基础知识 0.4 背景概念介绍 0.4.5 可重入函数 有一说一,第一次接触这个概念
由于平时做的项目很少用到多线程开发,所以就没关注到这个概念,但是我用过UCOS操作系统,平时写代码居然完全没关注过可重入函数
,简直不可原谅。通过这次经历让我更加感受到没事多读书的重要性了,读书+动手实操才能加深嵌入式软件这门技术的理解
言归正传,敲黑板:
平时写函数时,一定要注意自己写的函数
1、会不会被中断打断之后再在中断处理程序里再次调用你写的函数
2、如果不是裸机编程,那要考虑会不会被多个任务执行你写的函数
如果满足上面两个条件之一,那就要考虑函数的可重入性,。
如果你也是第一次接触这个概念,我相信你看上面的两个条件会有点懵逼,不用担心,下面的可重入函数概念已经准备好了,只要耐心读下去,你就能体会到
----心中自有丘壑,万丈高楼平地起--- 侯捷经常挂在嘴边的话
一、什么是可重入函数
µC/OS是多任务内核,函数可能会被多个任务调用,代码的重入性是保证完成多任务的基础。可重入代码指的是可被多个体任务同时调用,而不会破坏数据的一段代码,或者说代码具有在执行过程中打断后再次被调用的能力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | Swap1函数代码:这是不可重入代码Int temp; void swap1( int *x, int *y) { temp=*x; *x=*y; *y=temp; } Swap2函数代码:<br>这是可重入代码 void swap2( int *x, int *y) { int temp; temp=*x; *x=*y; *y=temp; } ———————————————— 版权声明:本文为CSDN博主「kernel1101」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https: //blog.csdn.net/kernel1101/article/details/47954723 |
二、别人总结的可重入函数详解
一篇文章带你了解C语言函数的可重入性
总结的很不错,防止作者删除文章,我这里再抄一下
一、不可重入函数。
在函数中如果我们使用静态变量了,导致产生中断调用别的函数的 过程中可能还会调用这个函数,于是原来的 静态变量被在这里改变了,然后返回主体函数,用着的那个静态变量就被改变了,导致错误。这类函数我们称为不可重入函数。
在 嵌入式系统的设计中,经常会出现多个任务调用同一个函数的情况。如果这个函数不幸被设计成为不可重入的函数的话,那么不同任务调用这个函数时可能修改其他任 务调用这个函数的数据,从而导致不可预料的后果。
不可重入函数在实时系统设计中被视为不安全函数。
满足下列条件的函数多数是不可重入的:
(1)函数体内使用了静态的数据结构;
(2)函数体内调用了malloc()或者free()函数;
(3)函数体内调用了标准I/O函数。
(4)函数体内调用了不可中断的硬件寄存器,如串口收发寄存器
二、可重入函数。
可重入函数可以被一个以上的任务调用,而不必担心数据被破坏。可重入函数任何时候都可以被中断,一段时间以后又可以运行,而相应的数据不会丢失。
可重入函数或者只使用局部变量,即保存在CPU寄存器中或堆栈中;或者使用全局变量,则要对全局变量予以保护。
若一个函数是可重入的,则该函数必须满足一下必要条件:
1、不能含有静态(全局)非常量数据。
2、不能返回静态(全局)非常量数据的地址。
3、只能处理由调用者提供的数据。作为可重入函数的输入参数,只能由调用者提供,而且所提供的输入数据必须满足上面两点要求
4、不能依赖于单实例模式资源的锁。
5、不能调用不可重入的函数。 函数内部,尽量不能用 malloc 和 free 之类的方法进行内存分配和释放,如果使用,一般情况下会造成该函数的不可重入
三、如何写出可重入的函数
1、在写函数时候尽量使用局部变量(例如寄存器、堆栈中的变量)。不访问那些全局变量,不使用静态局部变量
2、如果确实需要访问全局变量(包括static),一定要注意实施互斥手段。可重入函数在并行运行环境中非常重要,但是一般要为访问全局变量付出一些性能代价。编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
四、函数的可重入性和线程安全的关系
可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有一定的区别。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。
大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个线程同时访问。
线程安全与可重入性是两个不同性质的概念。
可重入是在单线程操作系统背景下,重入的函数或者子程序,按照后进先出的线性序依次执行完毕。多线程执行的函数或子程序,各个线程的执行时机是由操作系统调度,不可预期的,但是该函数的每个执行线程都会不时的获得CPU的时间片,不断向前推进执行进度。
可重入函数未必是线程安全的;线程安全函数未必是可重入的。例如,一个函数打开某个文件并读入数据。这个函数是可重入的,因为它的多个实例同时执行不会造成冲突;但它不是线程安全的,因为在它读入文件时可能有别的线程正在修改该文件,为了线程安全必须对文件加“同步锁”。
函数在它的函数体内部访问共享资源使用了加锁、解锁操作,所以它是线程安全的,但是却不可重入。因为若该函数一个实例运行到已经执行加锁但未执行解锁时被停下来,系统又启动该函数的另外一个实例,则新的实例在加锁处将转入等待。如果该函数是一个中断处理服务,在中断处理时又发生新的中断将导致资源死锁。
五、malloc和printf为什么不可重入
malloc和printf通常使用全局结构,并在内部使用基于锁的同步。这就是为什么它们不可重入。
malloc函数可以是线程安全的,也可以是线程不安全的。两者都不可重入:
malloc在全局堆上操作,同时发生的两个不同的malloc调用可能返回相同的内存块。(第二个malloc调用应该在获取块的地址之前发生,但块没有标记为不可用)。这违反了malloc的后置条件,因此此实现不会重新进入。
printf函数也对全局数据进行操作。任何输出流通常都使用一个附加到资源数据的全局缓冲区(用于终端或文件的缓冲区)。打印过程通常是将数据复制到缓冲区,然后刷新缓冲区的序列。
总结
平时写函数时,一定要注意自己写的函数
1、会不会被中断打断之后再在中断处理程序里再次调用你写的函数
2、如果不是裸机编程,那要考虑会不会被多个任务执行你写的函数
如果满足上面两个条件之一,那就要考虑函数的可重入性,
是不是get到了可重入函数的重要性了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!