Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
Network
使用SAL_AT组件下载文件,运行过程中内存占用太大
发布于 2019-05-29 00:25:48 浏览:3719
订阅该版
现象:使用RTT的SAL_AT组件,利用sim800c, 在阿里物联网平台进行OTA下载过程中,占用内存超大(700K的文件下载,动态内存使用最高超过600K)。下载完毕后内存全部释放。 查找问题: 看了下RTT实现机理是AT组件检查到+RECEIVE标志后,通过URC表调用回调函数,回调函数再执行at_socket.c中的at_recv_notice_cb()回调函数,使用at_recvpkt_put()将接收到的数据写入list链表上,然后使用workqueue唤醒等待接收数据的线程使用at_recvpkt_get()数据读取, 该函数读完后使用at_recvpkt_node_delete()删除链表节点并且释放内存。 在这个过程中内存申请就在urc_recv_func()函数,没接收到一个“+RECEIVE”b标志就会申请内存,因为没有释放,造成动态内存使用超高。 请教: 只要内存足够大,整个下载过程非常完整,但是过程中动态内存申请巨大。同样的应用程序,使用netdev组件转换成以太网,则没有这么大的内存消耗。 所以请教下这个占用内存超大的原理。以下是运行日志: ``` [172703] I/ali-ota: receive 4096 bytes, total recieve: 0 bytes [172944] I/ali-ota: receive 4096 bytes, total recieve: 4096 bytes [174964] I/ali-ota: receive 4096 bytes, total recieve: 8192 bytes [175205] I/ali-ota: receive 4096 bytes, total recieve: 12288 bytes free memheap pool size max used size available size -------- ---------- ------------- -------------- ext-sram 1048576
63012
985564 heap 62816 62804 12 msh />[177267] I/ali-ota: receive 4096 bytes, total recieve: 16384 bytes [177508] I/ali-ota: receive 4096 bytes, total recieve: 20480 bytes [180038] I/ali-ota: receive 4096 bytes, total recieve: 24576 bytes msh />free memheap pool size max used size available size -------- ---------- ------------- -------------- ext-sram 1048576
77200
971376 heap 62816 62804 12 msh />[181498] I/ali-ota: receive 4096 bytes, total recieve: 28672 bytes [181739] I/ali-ota: receive 4096 bytes, total recieve: 32768 bytes msh />free memheap pool size max used size available size -------- ---------- ------------- -------------- ext-sram 1048576
84220
964356 heap 62816 62804 12 msh />[183979] I/ali-ota: receive 4096 bytes, total recieve: 36864 bytes [184220] I/ali-ota: receive 4096 bytes, total recieve: 40960 bytes [186718] I/ali-ota: receive 4096 bytes, total recieve: 45056 bytes [188782] I/ali-ota: receive 4096 bytes, total recieve: 49152 bytes [189024] I/ali-ota: receive 4096 bytes, total recieve: 53248 bytes [191181] I/ali-ota: receive 4096 bytes, total recieve: 57344 bytes [191422] I/ali-ota: receive 4096 bytes, total recieve: 61440 bytes [193701] I/ali-ota: receive 4096 bytes, total recieve: 65536 bytes msh />free memheap pool size max used size available size -------- ---------- ------------- -------------- ext-sram 1048576 126600 921976 heap 62816 62804 12 ``` 可以看见,从固件开始下载后,动态内存的使用往上飙升,直至下载完毕后,所有内存释放得干干净净。
查看更多
24
个回答
默认排序
按发布时间排序
Cheney_Chen
2019-05-29
这家伙很懒,什么也没写!
[i=s] 本帖最后由 Cheney_Chen 于 2019-5-29 11:31 编辑 [/i] 调用 recv 函数接收完一段数据之后,链表中的这段数据数据应该就会被释放,sim800c 这段数据长度应该为 1.5 K 左右,内存占用这么大是一直没有调用 recv 接收数据??
armink
2019-05-29
这家伙很懒,什么也没写!
PS 看下 at client 线程优先级是否比 OTA 接收固件的线程优先级高,感觉像是 client 一直在收,可是收完了没人去取
Spunky
2019-05-29
这家伙很懒,什么也没写!
>PS 看下 at client 线程优先级是否比 OTA 接收固件的线程优先级高,感觉像是 client 一直在收,可是收完了 ... --- 这个是我最先就想到的问题, 默认at_client的现场是9, 后面我把MQTT的现场改为8, 高于at_client线程, 但是现象依然是这样.
Spunky
2019-05-29
这家伙很懒,什么也没写!
>调用 recv 函数接收完一段数据之后,链表中的这段数据数据应该就会被释放,sim800c 这段数据长度应该为 1.5 ... --- 是这样的,关于at_recvpkt_get()和at_recvpkt_put()代码我是看了的. 但仿佛是SIM800的接收超级快, at_recvfrom()来不及接收. 造成at_socket_sim800c.c中的urc_recv_func()一直在申请内存, 造成动态内存超级大. 最开始我使用的内部内SRAM, 经常报警urc_recv_func()内部的内存申请失败. 然后使用外部SRAM后得以解决. 而后又出现resp buffer爆满, 我又增加了AT接口使用的uart驱动fifo的尺寸为8K, 问题也得以解决, 整个下载过程成功. 但是整个下载过程中,动态内存申请消耗太大, 几乎接近于下载文件的大小. 给人的感觉是at_recvpkt_get()未释放内存, 但是这部分代码看上去是没有问题的.
armink
2019-05-30
这家伙很懒,什么也没写!
>这个是我最先就想到的问题, 默认at_client的现场是9, 后面我把MQTT的现场改为8, 高于at_client线程, 但 ... --- 加大 固件接收的频率及包尺寸试试呢
Spunky
2019-05-30
这家伙很懒,什么也没写!
>加大 固件接收的频率及包尺寸试试呢 --- 加大固件的接收频率怎么做?我已经将OTA的线程优先级提到最高, 包的尺寸加大可能会加剧at_client线程的内存块申请数量, 毕竟sim800c一次性传输数据最大也就1.5K左右. 我想连接下RTT的DFS实现poll/select的方式, 按照unix/linux的机制,系统使用的fd数量就这么几个, 轮询效率也应该不会低到哪里去的. 我看wqueue_wake()实现机理是查询socket号,然后直接唤醒持有socket号的线程. 如果一个线程里面有两个或者两个以上socket, 会不会造成延时??
armink
2019-05-30
这家伙很懒,什么也没写!
>加大固件的接收频率怎么做?我已经将OTA的线程优先级提到最高, 包的尺寸加大可能会加剧at_client线程的内 ... --- 每太看懂你的问题。 增加更多的日志再分析下呢,比如:每次 at client 线程收到一个报文,OTA 线程收到一个报文。看下时序关系
Spunky
2019-05-30
这家伙很懒,什么也没写!
>加大 固件接收的频率及包尺寸试试呢 --- 我贴出我的应用代码请帮忙看一下: ``` /* * Copyright (c) 2006-2018 RT-Thread Development Team. All rights reserved. * License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include
#include
#include
#include
#include "iot_import.h" #include "iot_export.h" #include "exports/iot_export_ota.h" #include "rtthread.h" #include "cJSON.h" #include "fal.h" #include "easyflash.h" #include "mqtt-def.h" #define LOG_TAG "ali-ota" #define LOG_LVL LOG_LVL_DBG #include
#define MQTT_OTA_RECV_BUFF_LEN (4097) static void *ota_hd = RT_NULL; static char ota_recv_buff[MQTT_OTA_RECV_BUFF_LEN];
/* 这个TIMER是控制MQTT周期性publish设备状态 */
void mqtt_start_period_timer(void) { rt_timer_t period_timer; period_timer = (rt_timer_t)rt_object_find("ali_timer", RT_Object_Class_Timer); if (period_timer) { rt_timer_start(period_timer); } }
/* 这个TIMER是控制MQTT周期性publish设备状态 */
void mqtt_stop_period_timer(void) { rt_timer_t period_timer; period_timer = (rt_timer_t)rt_object_find("ali_timer", RT_Object_Class_Timer); if (period_timer) { rt_timer_stop(period_timer); } } /* 该函数在MQTT上线后, 循环调用 */ rt_err_t mqtt_ota_init(void *mqtt_ota_hd, char *product_key, char *device_name) { if ((mqtt_ota_hd == RT_NULL) || (product_key == RT_NULL) || (device_name == RT_NULL)) return RT_ERROR; if (ota_hd) return RT_EOK; ota_hd = IOT_OTA_Init(product_key, device_name, mqtt_ota_hd); if (RT_NULL == ota_hd) { LOG_D("initialize OTA failed"); return RT_ERROR; } char firmware_verison[FIRMWARE_VERSION_MAXLEN]; rt_memset(firmware_verison, 0, sizeof(firmware_verison)); if (ef_get_env_blob(MQTT_OTA_FIRMWARE_VERSION, firmware_verison, sizeof(firmware_verison) - 1, RT_NULL) <= 0) { LOG_D("get device firmware version failed"); if (ota_hd) { IOT_OTA_Deinit(ota_hd); ota_hd = RT_NULL; } return RT_ERROR; } if (0 != IOT_OTA_ReportVersion(ota_hd, firmware_verison)) { LOG_D("report OTA version failed"); if (ota_hd) { IOT_OTA_Deinit(ota_hd); ota_hd = RT_NULL; } return RT_ERROR; } return RT_EOK; } void mqtt_ota_deinit(void) { if (ota_hd) { IOT_OTA_Deinit(ota_hd); ota_hd = RT_NULL; } } rt_err_t mqtt_ota(void *mqtt_ota_hd, char *product_key, char *device_name) { rt_err_t result = RT_ERROR; if ((mqtt_ota_hd == RT_NULL) || (product_key == RT_NULL) || (device_name == RT_NULL)) goto __mqtt_ota_exit; if (mqtt_ota_init(mqtt_ota_hd, product_key, device_name) != RT_EOK) goto __mqtt_ota_exit; if (IOT_OTA_IsFetching(ota_hd)) { char fm_ver[FIRMWARE_VERSION_MAXLEN], md5sum[33]; IOT_OTA_Ioctl(ota_hd, IOT_OTAG_VERSION, fm_ver, FIRMWARE_VERSION_MAXLEN); IOT_OTA_Ioctl(ota_hd, IOT_OTAG_MD5SUM, md5sum, 33); char firmware_verison[FIRMWARE_VERSION_MAXLEN]; rt_memset(firmware_verison, 0, sizeof(firmware_verison)); if (ef_get_env_blob(MQTT_OTA_FIRMWARE_VERSION, firmware_verison, sizeof(firmware_verison) - 1, RT_NULL) > 0) { if (!rt_strcasecmp(firmware_verison, fm_ver)) { IOT_OTA_ReportVersion(ota_hd, fm_ver); result = RT_EOK; goto __mqtt_ota_exit; } } const struct fal_partition *dl_partition; dl_partition = fal_partition_find(MQTT_OTA_DOWNLOAD_PARTITION_NAME); if (dl_partition == RT_NULL) { LOG_I("can not find %s partition", MQTT_OTA_DOWNLOAD_PARTITION_NAME); goto __mqtt_ota_exit; } if (fal_partition_erase_all(dl_partition) < 0) { LOG_I("can not erase %s partition", dl_partition->name); goto __mqtt_ota_exit; }
/* 防止at_client接收粘包, 关闭周期性publish设备状态 */
mqtt_stop_period_timer(); int fetch_len; rt_uint32_t last_percent = 0, percent = 0; rt_uint32_t size_of_download = 0, size_of_file; rt_uint32_t content_write_sz; /* 循环条件:未下载完成 */ while (!IOT_OTA_IsFetchFinish(ota_hd)) { fetch_len = IOT_OTA_FetchYield(ota_hd, ota_recv_buff, MQTT_OTA_RECV_BUFF_LEN, 1); if (fetch_len > 0) { content_write_sz = fal_partition_write(dl_partition, size_of_download, (uint8_t *)ota_recv_buff, fetch_len); if (content_write_sz != fetch_len) { LOG_I("Write OTA data to file failed"); IOT_OTA_ReportProgress(ota_hd, IOT_OTAP_BURN_FAILED, RT_NULL); mqtt_ota_deinit(); mqtt_start_period_timer(); goto __mqtt_ota_exit; } else { LOG_I("receive %d bytes, total recieve: %d bytes", size_of_download, size_of_file); } } else { LOG_I("ota fetch failed."); IOT_OTA_ReportProgress(ota_hd, IOT_OTAP_FETCH_FAILED, NULL); if (fetch_len < 0) { mqtt_ota_deinit(); mqtt_start_period_timer(); goto __mqtt_ota_exit; } } /* get OTA information */ IOT_OTA_Ioctl(ota_hd, IOT_OTAG_FETCHED_SIZE, &size_of_download, 4); IOT_OTA_Ioctl(ota_hd, IOT_OTAG_FILE_SIZE, &size_of_file, 4); last_percent = percent; percent = (size_of_download * 100) / size_of_file; if (percent - last_percent > 0) { IOT_OTA_ReportProgress(ota_hd, (IOT_OTA_Progress_t)percent, RT_NULL); IOT_OTA_ReportProgress(ota_hd, (IOT_OTA_Progress_t)percent, "hello"); } IOT_MQTT_Yield(mqtt_ota_hd, 100); } /* end of while */ IOT_OTA_Ioctl(ota_hd, IOT_OTAG_MD5SUM, md5sum, 33); IOT_OTA_Ioctl(ota_hd, IOT_OTAG_VERSION, fm_ver, FIRMWARE_VERSION_MAXLEN); uint32_t firmware_valid; IOT_OTA_Ioctl(ota_hd, IOT_OTAG_CHECK_FIRMWARE, &firmware_valid, 4); if ((firmware_valid) && (size_of_download == size_of_file) && (size_of_file > 0)) { LOG_D("The firmware is valid! Download firmware successfully."); LOG_D("OTA FW version: %s", fm_ver); LOG_D("OTA FW MD5 Sum: %s", md5sum); ef_set_env_blob(MQTT_OTA_FIRMWARE_VERSION, fm_ver, rt_strlen(fm_ver)); IOT_OTA_ReportVersion(ota_hd, fm_ver); result = RT_EOK; } mqtt_ota_deinit(); mqtt_start_period_timer(); } __mqtt_ota_exit:
return result;
}
``` 这上面的代码中, 每次调用IOT_OTA_FetchYield()后, 都会调用一次IOT_MQTT_Yield(mqtt_ota_hd, 100); 这里会不会有问题, 阿里平台OAT流程是: 1. OTA平台通过版本号判断是否下载固件, 这个通道为MQTT通道. 我在每次OAT初始化后发送一次给平台 2. OTA平台下载固件前会通过MQTT通道下发一个升级命令(包含固件的URL地址), 然后设备通过HTTPS主动连接指定的URL. 3. 建立TLS连接后, 平台向设备下发固件数据. 由上面代码所示,我通过IOT_OTA_FetchYield()接收完一帧数据后, 会调用IOT_MQTT_Yield(mqtt_ota_hd, 100), 在给出的官方例程中是这样做的,目的应该在下载过程中更新下MQTT通道,包括reconnect, keepalive, 还有一些订阅和发布的数据处理. 阿里的SDK在这不分是固定将线程挂起100个tick(这个是关键). 实际执行时间是应该会大于100个tick的. 这里就有个问题: 1. 由于MQTT与HTTPS是两个通道(独立socket), HTTPS的下发是不会停的, 就造成数据的堆积. 比如在那100个tick中, OTA下发了两帧的数据, 留在动态内存中. 2. 每执行1次IOT_OTA_FetchYield()只在动态内存中读取1帧固件数据, 然后释放1帧数据, 留了1帧数据在动态内存中下个循环再读取. 3. 应用程序将读取的1帧数据写入FLASH, 在执行1次IOT_MQTT_Yield(mqtt_ota_hd, 100), 又会延迟至少100个tick+写入FLASH的时间, 这个过程中OTA平台可能又下发了2帧数据. 这样动态内存中就有3帧数据了; 4. 下一个循环在动态内存中就缓存了4帧, 再一个循环就是5帧数据, 然后恶性循环下去, 知道动态内存分配失败. 这个推断可以解释两个现象: 1. 以太网下载, 动态内存几乎没有太多增长. 除了以太网的延迟低外, TCP的实现是由LWIP自己控制, 也就是可以控制数据流, 所以不会造成数据堆积. 2. AT_SAL下载, 延迟大且不稳定, 而TCP协议栈的实现是GPRS模块实现, 模块收到数据就会往串口上发,所以MCU不能控制数据流, 一旦MCU的recv有延迟就会造成数据堆积. 按照[https://www.rt-thread.org/qa/space-uid-6104.html](armink提的建议是加大接收块size和减少recv的延迟的确是解决问题的方向, 我后续把)IOT_MQTT_Yield(mqtt_ota_hd, 100)去掉, 将每一次接收数据块的长度加大试试. 为了方便请高手帮我分析下,我把OTA在mqtt实现的部分代码贴上来, 我是在MQTT连上服务器后循环调用mqtt_ota()的. 由于代码太多,贴出关键代码 ``` while (IOT_MQTT_CheckStateNormal(mqtt_device_hd)) { /* handle the MQTT packet received from TCP or SSL connection */ IOT_MQTT_Yield(mqtt_device_hd, 200); /* OTA周期执行 */ extern rt_err_t mqtt_ota(void *mqtt_ota_hd, char *product_key, char *device_name); mqtt_ota(mqtt_device_hd, product_key, device_name); HAL_SleepMs(200); } ```
Spunky
2019-05-30
这家伙很懒,什么也没写!
>每太看懂你的问题。 > >增加更多的日志再分析下呢,比如:每次 at client 线程收到一个报文,OTA 线程收到 ... --- 是的,我觉得问题应该出在我的应用代码上. 刚刚我做了一个假设分析, 后续我增加日志输出和你给出的两个解决问题的方向试试. 我个人觉得AT_SOCKET的数据包收发是否可以增加一种方式,让每一SOCKET挂一个FIFO指针, FIFO的尺寸由使用者根据应用场景自行设定, 在建立socket的时候就可以提前申请内存大小. 而且FIFO的读入和写入的效率比目前AT_SOCKET使用的单向链表+临时内存块申请的效率要高, 减少内存碎片产生, 并且可以节省每个内存块的管理字节. 比较适合嵌入式系统的现状. 目前我的plan B是使用PPPOS方式, 拨号上网, 效费比没有使用AT命令方式高, 但是利用LWIP控制TCP的数据收发, 可以有效的控制数据流, 至少我认为使用内存的大小可以控制. 而不像现在无法控制最大内存的使用范围. 才疏学浅, 以上是一些简单的领悟, 正确与否还请指教.
armink
2019-05-31
这家伙很懒,什么也没写!
>是的,我觉得问题应该出在我的应用代码上. 刚刚我做了一个假设分析, 后续我增加日志输出和你给出的两个解 ... --- 由于 Flash 写入速度比 网络接收数据慢,导致数据积压。 从根本上解决这个问题确实还得从网络接收那边入手。目前想到的办法是在 at_socket 那边配置 TCP 窗口大小(默认可以4k-8k),当窗口已满时,即便收到 URC 也不允许再往窗口对应的 buf 拷贝数据。 等窗口有空间后再重新查询 AT 设备那边的接收 buf ,取回剩余的接收数据。
撰写答案
登录
注册新账号
关注者
2
被浏览
3.7k
关于作者
Spunky
这家伙很懒,什么也没写!
提问
19
回答
98
被采纳
0
关注TA
发私信
相关问题
1
lwip1.4.1连接经常会断开无法连接上,可以ping通
2
LPC1768:RTT+LWIP+webserver用IE刷网页出现硬件中断错误(已经解决)
3
求一些LWIP开发的经验,目前ping一直不稳定。
4
stm32f207+dp83848无法ping通
5
RTT下的LWIP传递机制
6
rtt内lwip的socket是否是线程安全?
7
Lwip+enc28j60无法ping通
8
坑爹的rtconfig.h lwip关掉了checksum
9
花了一个晚上,把RT2.0的LWIP、网卡驱动、文件系统整合起来了,发现一点小问题
10
lwip例程中udp发送时如何指定源端口发送到指定目的地端口
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
CherryUSB的bootuf2配置
2
在用clangd开发RTT吗,快来试试如何简单获得清晰干净的工作区
3
GD32F450 片内 flash驱动适配
4
STM32H7R7运行CherryUSB
5
RT-Smart首次线下培训,锁定2024 RT-Thread开发者大会!
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
freemodbus
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
flashDB
GD32
socket
中断
编译报错
Debug
SFUD
rt_mq_消息队列_msg_queue
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
a1012112796
10
个答案
1
次被采纳
踩姑娘的小蘑菇
4
个答案
1
次被采纳
红枫
4
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
Ryan_CW
4
个答案
1
次被采纳
本月文章贡献
catcatbing
3
篇文章
6
次点赞
YZRD
2
篇文章
5
次点赞
lizimu
2
篇文章
5
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部