一种动态分配内存错误的解决办法
1、项目背景
一款2年前开发的无线网络通信软件在最近的使用过程中出现网络中传感器离线的问题,此软件之前已经使用的几年了,基本功能还算稳定。这次为什么出了问题。
先派工程师去现场调试一下,初步的结果是网络信号弱,并且有个别主干网络设备离线不工作。这次网络的中的传感器数量与比前比相差不多,但是传感器数据的上报间隔较短。
2、临时办法
网络信号弱导致的通信时有中断,需要通过加装主干网络设备增加信号覆盖解决。同时调整传感器数据的上报间隔变长,网络中设备离线的情况不再出现,看来是数据量上报大有关了。
3、产品软件问题
虽然说现场问题临时解决了,传感器都上线了。但是这里面还是说明网络设备的软件有问题,这个问题是只有在大数量传输时会出现。那么就在公司搭建网络环境,发送大数量测试,经过长达3周的测试,复现出现场的问题。网络设备会出现内部MCU硬件定时器不工作和内存分配异常导致的无线网络发送空数据包两种问题。
3.1 定时器不工作
在某种特定的网络情况下,网络中的设备内部的MCU硬件定时器会被连续调用两次启动,正常的情况下是调用1次启动,1次停止。可能由于数据量大,调用停止的操作未成功,就出现了连续两次调用启动的情况。经过分析定时器的驱动代码和实际跟踪发生问题时的情况,当第二次调用定时器启动时,HAL_TIM_Base_Start(&htim2);函数是返回失败,跟踪代码后发现定时器内部的状态是工作状态是BUSY状态,返回错误,因为定时还在运行状态。
分析下面的代码,发现当调用StartWork函数-》InitTimer函数,此函数会通过寄存器操作把定时器关闭,之后再调用HAL_TIM_Base_Start函数启动,由于寄存器操作把定时器并不修改定时器的状态变量,导致再次调用启动会失败,正确的操作逻辑基于对外设备定时器的操作应统一方式,不能API函数调用和定时器操作混合使用,会导致异常情况驱动内部的状态不正常。
/*==========================================================================
brief : 硬件定时器初始化
param : None
retval : None
//==========================================================================*/
void InitTimer(TimeIsUpCb_t callback)
{HTimer2Timing.Status = eSltTiming_Idle;HTimer2Timing.CurSltIdx = 0;HTimer2Timing.StartCnt = 0;HTimer2Timing.EndtCnt = myAttributes.TotalSltSize_us - myAttributes.SltSize_us - 1;HTimer2Timing.NextSltTCnt = 0;HTimer2Timing.CallBackWhenTimeIsUp = callback;寄存器操作停止了定时器,不能修改定时器内部的状态变量__HAL_TIM_DISABLE(&htim2); //停止timier2计数htim2.Instance->CNT = 0;htim2.Instance->ARR = myAttributes.TotalSltSize_us - 1;__HAL_TIM_CLEAR_IT(&htim2, (TIM_IT_UPDATE | TIM_IT_CC1));__HAL_TIM_ENABLE_IT(&htim2, (TIM_IT_UPDATE | TIM_IT_CC1));
}/*==========================================================================
brief : 开始工作命令处理函数,模块开始广播BCH,等待设备接入
param : puartpkt: 串口帧数据指针,包含串口handle(用于说明串口数据是从哪个串口来的)
retval : None
//==========================================================================*/
void InitEachBuf(void);
void StartWork(UartFrame_t* puartpkt)
{//计算一次发送时不同时隙与最大传输字节数据对应表CalMaxBytesInOnePktBySlt(myAttributes.LoRaCfg.ModulationIndex, (myAttributes.SltSize_us),(myAttributes.BCHInfo.GP_Dphy * 100));InitAllTheadPt();InitEachBuf();BCHFrame_Init();InitTimer(Send_DL_Frame);调用定时器通过API函数调用 HAL_TIM_Base_Start(&htim2);myAttributes.WorkStatus = eModuleWorkSt_InNet;
}/*==========================================================================
brief : 停止工作命令处理函数
param : puartpkt: 串口帧数据指针,包含串口handle(用于说明串口数据是从哪个串口来的)
retval : None
//==========================================================================*/
void StopWork(UartFrame_t* puartpkt)
{myAttributes.WorkStatus = eModuleWorkSt_Idle;InitRadio();HAL_TIM_Base_Stop(&htim2);InitEachBuf();DevSltList_Init();DevRegList_Init();DevDRxList_Init();
}
找到问题,解决办法很简单了,重新整理定时器的调用逻辑,全部通过API接口来调用定时器启动和停止 ,这样连续两次调用启动定时器也没有问题。
3.2 内存分配异常
内存分配异常很难解决,前前后后增加了很多调试代码,一步一步跟踪分析,确定是内存分配导致的异常。
typedef struct
{uint8_t PktNum : 7;uint8_t FlagRewind : 1;uint16_t Size;uint16_t Head;uint16_t Tail;uint16_t DataTail;//有效的数据结尾uint8_t* DataArr;
}StreamBuffer;内存申请代码:
//申请一个保留空间给外部使用,不支持多线程、多次调用
uint8_t* ApplyMemByStreamBuffer(StreamBuffer *ptStreamBuffer, uint16_t size)
{uint8_t* Result = NULL;do{//如果申请空间大于空间大小则放弃if(size > ptStreamBuffer->Size)break;if(ptStreamBuffer->PktNum == 0){ptStreamBuffer->Head = 0;ptStreamBuffer->Tail = 0;ptStreamBuffer->DataTail = 0;ptStreamBuffer->FlagRewind = 0;}uint16_t newtail = ptStreamBuffer->Tail + size;if(newtail <= ptStreamBuffer->Size){if(ptStreamBuffer->FlagRewind){ //第2种情况,Tail超过Head产生数据覆盖,不能分配出内存if(newtail > ptStreamBuffer->Head)break;}Result = (ptStreamBuffer->DataArr + ptStreamBuffer->Tail);}else //尾部空间浪费一些,不用了,这样处理简单{ptStreamBuffer->Tail = 0;ptStreamBuffer->FlagRewind = 1;if(ptStreamBuffer->PktNum){ //第2种情况,Tail超过Head产生数据覆盖,不能分配出内存if((ptStreamBuffer->Tail + size) > ptStreamBuffer->Head)break;}//从缓存区头部开始使用Result = ptStreamBuffer->DataArr;}}while(0);return(Result);
}
存在的问题:当可用内存为10240长度时,每次申请10个字节长度时,可申请出1024块内存,使用PktNum 7位表不了这么多的内存,当申请的1024块时,PktNum只能到127,无法表达内部存储了多少块?释放内存时,只能释放127块。申请0长度时,能返回可以申请,需要处理一下。
根据以上内存代码,做了初步的代码逻辑分析,发现一些问题,但实际测试是这些问题点全部未发生,做了分配的原理分析图:内存分配基本逻辑正常。
后面经过多次测试,发现一个共同的现场,当内部出问题时,内存块分配出去的计数PktNum与内存中已存储的有效数据不一样多。如下图,计数中有8块分配,根据内存指针来看已经没有有效数据了。
此种问题多次复现,并且出现时PktNum都是8,很有规律,当时内存中申请的数据块长度为1225字节,整个总内存的长度为10240长度,计算下来正好能存储8*1225,即8块。说明内存在用完了后发生第二申请覆盖,所以只有计数增加了。
跟踪内存分析的数据长度,发现如果内存中先分配8块1225长度块,再分配1个小块147长度,最后释放掉8块1225长度的块,再申请8块1225长度的块后,内存就满了,再申请1块1225长度的内存,内存出现的反转,开始占用已经申请出去的内存。解决办法,不允许此种情况下再分配出新的内存了。
uint8_t* ApplyMemByStreamBuffer(StreamBuffer *ptStreamBuffer, uint16_t size)
{uint8_t* Result = NULL;do{//如果申请空间大于空间大小则放弃/* 如果申请的空间是0或是内部存储的PktNum已经最大,申请的数量已超总内存大小,返回失败 zhaoshimin 20250429*/if((size > ptStreamBuffer->Size) || (size == 0) || (STREAM_BUFFER_PKTNUM_MAX == ptStreamBuffer->PktNum)){break;} if(ptStreamBuffer->PktNum == 0){ptStreamBuffer->Head = 0;ptStreamBuffer->Tail = 0;ptStreamBuffer->DataTail = 0;ptStreamBuffer->FlagRewind = 0;}uint16_t newtail = ptStreamBuffer->Tail + size;if(newtail <= ptStreamBuffer->Size){if(ptStreamBuffer->FlagRewind){if(newtail > ptStreamBuffer->Head)break;}Result = (ptStreamBuffer->DataArr + ptStreamBuffer->Tail);}else //尾部空间浪费一些,不用了,这样处理简单{/*当内存中先放入1225的8包数据再放入147长度的包后,
释放掉8包数据后,再申请放入1225长度的8包数据后,内存满了,
发生第二次产生了反转 20250429*/if(ptStreamBuffer->FlagRewind){break;}else{ptStreamBuffer->Tail = 0;ptStreamBuffer->FlagRewind = 1;if(ptStreamBuffer->PktNum){if((ptStreamBuffer->Tail + size) > ptStreamBuffer->Head)break;}} Result = ptStreamBuffer->DataArr;}}while(0);return(Result);
}