为什么不能在中断中使用printf--不可重入函数

前言

前两天在笔试的时候遇到了关于能否在中断中使用printf的问题,当时有点蒙,笔试结束后查阅了资料,这里进行以下总结

可重入函数和不可重入函数

printf不能在中断中被调用的原因是它是一个不可重入函数,而在中断中要避免调用不可重入函数,首先我们先说说什么是可重入函数,什么是不可重入函数
简单说来,区分一个函数是否可重入就是看这个函数能否在未返回的时候再次被调用。而造成一个函数不可重入的原因往往是使用了全局变量,如果一个函数未返回再执行一次会导致对全局变量的操作是不安全的。就例如我们常用的printf、malloc、free都是不可重入的函数,printf会引用全局变量stdout,malloc,free会引用全局的内存分配表,在多线程的环境下,如果没有很好的处理数据保护和互斥访问,就会发生错误
在unix里面通常都有加上_r后缀的同名可重入函数版本
如果实在没有,不妨在可预见的发生错误的地方尝试加上保护锁同步机制等等

如何写可重入的函数

我们只要遵循几条规则,写出来的函数就是可重入的:

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

总而言之我们就是要保证中断是安全的!

满足下列条件的函数多数是不可重入的:

  • 函数体内使用了静态的数据结构;
  • 函数体内调用了malloc()或者free()函数;
  • 函数体内调用了标准I/O函数。
### 出现 HardFault 的可能原因 在 Keil 环境中,`sprintf` 导致 `HardFault` 的主要原因通常涉及以下几个方面: #### 1. 堆栈溢出 如果任务堆栈空间不足,则调用复杂的 C 库函数(如 `sprintf`)可能会导致堆栈溢出。FreeRTOS 中的任务堆栈大小是由开发者定义的,过小的堆栈可能导致未定义行为[^1]。 #### 2. 浮点单元 (FPU) 配置错误 某些微控制器支持硬件浮点运算,在 FreeRTOS 下使用 FPU 可能会引发异常。这是因为默认情况下,FreeRTOS 不保存/恢复 FPU 上下文。即使不显式使用浮点数,像 `sprintf` 这样的标准库函数也可能间接依赖于 FPU。 #### 3. 启动文件配置不当 启动文件中的 `HardFault_Handler` 如果没有正确实现调试功能,无法捕获具体的错误信息。这使得定位问题变得困难[^2]。 --- ### 解决方案 以下是针对上述问题的具体解决方法: #### 方法一:增加任务堆栈大小 确保分配给任务的堆栈足够大以容纳 `sprintf` 所需的空间。可以通过调整创建任务时传递的参数来增大堆栈尺寸。例如: ```c xTaskCreate(TaskFunction, "TaskName", configMINIMAL_STACK_SIZE * 4, NULL, tskIDLE_PRIORITY, NULL); ``` 这将堆栈设置为最小值的四倍 (`configMINIMAL_STACK_SIZE * 4`) 来减少潜在的堆栈溢出风险。 #### 方法二:启用并初始化 FPU 支持 对于具有硬件浮点单元的支持芯片(如 GD32F30x),需要确认以下几点: - **编译器选项**: 在 Keil MDK 中打开 `-mfpu=fpv4-sp-d16` `-mfloat-abi=hard` 或者软浮点模式 `-mfloat-abi=softfp`。 - **上下文切换处理**: 修改 FreeRTOS 提供的 port 文件夹下的汇编代码片段,加入对 FPU 寄存器组的保存与恢复逻辑。 #### 方法三:改进 HardFault 处理程序以便获取更多信息 通过自定义 `HardFault_Handler` 实现更详细的崩溃报告机制可以帮助快速找到根本原因。下面是一个简单的例子展示如何打印寄存器状态到串口或其他输出设备上: ```assembly HardFault_Handler: PUSH {r0-r4,lr} ; Save registers. LDR R0, =hard_fault ; Load address of handler function into R0. BX R0 ; Branch to it. hard_fault: B hard_fault_infinite_loop hard_fault_infinite_loop: B . ``` 随后可以在对应的 C/C++ 源码进一步扩展此处理器的行为,比如记录日志或者触发断点中断等操作。 --- ### 总结 当遇到因 `sprintf` 调用而产生的 `HardFault` 错误时,应优先考虑是否存在资源耗尽情况(特别是内存),其次是检查体系结构特定的功能是否被适当地启用了。最后借助完善的异常捕捉手段可以有效提升诊断效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值