| |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| -> 嵌入式 -> Keil C51的Data Overlaying机制导致的函数重入问题 -> 正文阅读 |
|
|
[嵌入式]Keil C51的Data Overlaying机制导致的函数重入问题 |
|
个人博客原文地址:keil c51的data overlaying机制导致的函数重入问题 IO引脚电平读取引出的问题最近在忙于公司一个使用51单片机的项目时,碰到一个蹊跷的问题。项目在主函数的while循环中使用若干IO引脚获取外部电平状态,同时使用一个ADC在周期性的定时器中断中对外部模拟信号进行采集,在实际运行中,发现IO在获取外部电平时会随机性的获取到错误的电平(比如外部输入为低电平,但是读取IO显示为高电平),问题相关的代码片段大致如下:
进一步查找发现,get_io_status()和adc_get()这两个函数都使用了同一个函数io_get()来获取IO电平,函数实现大概为:
在注释adc_get()函数后执行程序,发现get_io_status()就可以正常获取电平了,后来想了想,可能是由于io_get函数发生重入导致的问题,后续的实验也验证了我的想法。 导致问题的Data Overlaying特性也许有些人会有疑问:在io_get()函数中并未使用全局变量,为什么对io_get()函数进行重入会导致问题呢?确实,对于stm32等基于现代的arm内核的单片机来说,以上代码基本不会导致任何问题,大多数arm内核单片机的编译器(或者说几乎所有C语言编译器)都将函数的参数和局部变量存储在RAM的堆栈中,可以理解为每次调用函数,都会为本次调用分配一块空间用来存储本次调用的参数和函数内的局部变量,因此如果一个函数只使用了局部变量,并且内部调用的其他函数也是可重入的,那么对函数进行重入基本不会导致问题;但是对比较古老的51内核单片机来说,情况就有所不同了:很多51内核单片机的RAM可能只有几百字节,堆栈指针支持的寻址范围更是只有区区256字节,如果依然希望在调用函数时在堆栈上存储参数和局部变量,那么这小小的堆栈很快就会溢出了,而且基于堆栈的函数调用在函数进入与退出时的入栈出栈操作,对于很多低速的51单片机来说,也是一笔不小的开销了。因此,为了解决这个问题,keil C51的编译器在实现C语言函数特性时,使用了一种称为Data Overlaying的特性,这里摘录下手册中的原文:
其实这个特性按大白话解释就是说:按照惯例我们应该和很多其他C编译器一样用堆栈来实现函数调用时的传参和局部变量保存,但是C51的堆栈实在太小,所以经典的这一套东西在51内核单片机上性能不行,而且相对于51单片机可怜的RAM来说,如果每个函数调用都开辟堆栈空间的话再做入栈出栈的话,那51内核单片机那点点RAM和那点点性能实在不够用。那么怎么才能减少堆栈的开销呢,keil C51说我在编译时就分析你程序的函数调用树,直接在RAM上给你每个函数的参数和局部变量的存储的地址提前分配好,这样就减少了函数调用时出入栈的开销;并且对于某些函数的局部变量,我们发现它们如果不需要同时使用时,那么就把它们分配到一个地址,相当于把这个地址的空间分时复用了,这样就减少了RAM的使用,其实就相当于在编译阶段就将堆栈结构给确定下来。 对于RAM空间分时复用这里举个例子:假设我分析发现程序中存在函数A、函数B和函数C,同时函数A中调用了函数B和函数C,那么在分配RAM空间时,函数A的局部变量就要分配到单独的地址,不能与函数B、C的局部变量地址重合,因为函数A的生命周期会与函数B、C的生命周期存在重合,但是函数B和C中的局部变量就可以使用相同的RAM地址进行空间分时复用了,因为它们不存在调用关系,函数生命周期不重合。下图描述函数A、B、C之间的调用关系与执行生命周期:
那么它们的局部变量在内存中的地址分配可能就是这样的:
不过官方文档里也讲了:我们这个东西全自动的,大部分场景很好用,但是在以下两种特殊情况需要格外注意:
如果程序中不得不出现以上两种情况,keil C51也给出了称为OVERLAY Linker Directive解决方法,其实就是我们自己去手动修改编译器生成的函数调用链,这里提供链接:BL51 User’s Guide: OVERLAY Linker Directive (keil.com),不展开描述,文末我会给出一些自己认为可行的规避以上两种情况的方法。 很明显,上面提到的特殊情况2导致了我项目中的IO读取错误问题。 问题复盘再来看一遍io_get()的实现:
我在main函数的主循环和中断中都使用了这个函数,但是keil C51在编译时只给局部变量status分配了一处RAM空间,那么可能会出现这样的场景:
规避方法如果使用keil C51时一个函数需要同时在正常main函数流程与中断中调用,则可以考虑采取以下措施来防止重入问题:
以上方法也许可以解燃眉之急,或许有时间要研究下keil官方给出的 OVERLAY Linker Directive 的解决方法。。。。。。 |
|
|
| 嵌入式 最新文章 |
| 基于高精度单片机开发红外测温仪方案 |
| 89C51单片机与DAC0832 |
| 基于51单片机宠物自动投料喂食器控制系统仿 |
| 《痞子衡嵌入式半月刊》 第 68 期 |
| 多思计组实验实验七 简单模型机实验 |
| CSC7720 |
| 启明智显分享| ESP32学习笔记参考--PWM(脉冲 |
| STM32初探 |
| STM32 总结 |
| 【STM32】CubeMX例程四---定时器中断(附工 |
|
|
| 上一篇文章 下一篇文章 查看所有文章 |
|
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
| 360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年11日历 | -2025/11/29 17:13:14- |
|
| 网站联系: qq:121756557 email:121756557@qq.com IT数码 |