记录一次linux应用内存调试过程

文章描述了一位开发者在监控基于嵌入式Linux的数据采集器时,发现内存不断增长,疑似内存泄漏。通过valgrind等工具定位到问题可能与MQTT连接及on_log回调函数有关。在特定条件下(多个MQTT连接,5秒心跳),只有注册了on_log时才会出现内存泄漏。作者创建了一个最小复现系统,但未能在纯净环境中重现问题,推测可能是内存碎片或应用环境导致。尽管未找到确切原因,但提出了问题解决的一般性步骤和后续调查方向。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近一直在监测之前开发的一款数据采集器的性能,基于嵌入式linux系统开发,发现应用在运行过程中,虚拟内存(使用top监测)和物理内存(使用free监测)会一直增长,大约一个小时分别增长1M左右。

下面回顾一下对于该问题的分析过程。

凭经验,应该发生了内存泄漏,经过多次对比测试之后,基本确定导致该问题的功能模块。但是,经过分析代码,一直找不到导致问题的关键点,无奈,只能求助于工具,首先想到的就是赫赫有名的valgrind,该工具的一项重要功能就是监测内存泄漏。

由于linux的rootfs是通过buildroot构建出来的,所以,增加valgrind十分的简单。之后,通过valgrind长时间测试了应用的内存使用情况,结果让我大失所望,valgrind确实监测到了一些所谓的“内存泄漏(主要是一些已经申请,就不会释放的内存块)”,但是,没有看到导致开头所讲的内存泄漏的原因。

之后,又经过几天的对比测试,进一步锁定了导致该问题的功能部分。具体是这样的,该应用涉及到了MQTT协议通信需求,基于mosquitto开发MQTT相关的功能,多次测试结果表示:只开启一路mqtt连接,并且MQTT和服务端MQTT Broker之间的心跳为5s时,大约经过一个小时就会有1M内存的泄漏!

问题的关键点找到了,下一步就是定位,解决问题了,下面是具体的执行步骤:

  1. 快速复现问题
    因为之前的测试结果是,一个MQTT连接,5s心跳,大约1小时,内存泄漏1M。为了能够快速的复现问题,缩短调试时间,我同时建立了50个连接,每个连接的心跳为5s,这样基本上大约1分钟,内存就会泄漏1M,结果和猜想的一致,大约55s,内存就会泄漏1M,经过进一步的分析发现是,如果向mosquitto注册了on_log回调函数,就会出问题,反之,这不会出现。好了,问题可以快速复现了,这极大的提高了调试效率
  2. 实现参照模型,编写最小复现系统
    由于产品功能比较复杂,为了排除其他功能模块干扰,最重要的是确定是否是mosquitto惹的祸,我实现了类似于上一步的最小测试模块,作为参照模型,该单元只有一个功能,就是建立50个连接,每个连接心跳5s,没有其他任何功能。不过,可惜的是,没有出现内存泄漏问题,心情无比崩溃 (: 。不过,虽然这个参照模型没有成功,对于实际问题的分析,这一步是必不可少的!
  3. 确定问题原因
    参照模型没有成功,但在应用使用环境下,问题确实存在,问题原因若隐若现。应用环境下,问题确实存在,参照模型下不存在,看来需要分析mosquitto的源码了。上面提到在注册on_log之后,会出现内存泄漏,那这个on_log的工作原理是怎样的呢,下面是简单的介绍:
    如果应用向mosquitto注册了on_log,那么在mosquitto运行过程中会将一些调试信息通过该回调函数进行输出,用于调试。而这里的心跳包会在注册了on_log回调的情况下,执行如下的路径:
  • send mqtt ping request packet->on_log
  • recv mqtt ping response packet->on_log
    调用on_log的代码如下:
int log__printf(struct mosquitto *mosq, unsigned int priority, const char *fmt, ...)
{
	va_list va;
	char *s;
	size_t len;
	pthread_mutex_lock(&mosq->log_callback_mutex);
	if(mosq->on_log){
		len = strlen(fmt) + 500;
		s = mosquitto__malloc(len*sizeof(char));
		if(!s){
			pthread_mutex_unlock(&mosq->log_callback_mutex);
			return MOSQ_ERR_NOMEM;
		}
		va_start(va, fmt);
		vsnprintf(s, len, fmt, va);
		va_end(va);
		s[len-1] = '\0'; /* Ensure string is null terminated. */
		mosq->on_log(mosq, mosq->userdata, (int)priority, s);
		mosquitto__free(s);
	}
	pthread_mutex_unlock(&mosq->log_callback_mutex);
	return MOSQ_ERR_SUCCESS;
}

这里可以看到,如果on_log有效就会将日志输出,也可以看到这里调用了mosquitto__malloc进行了缓冲区分配,在on_log返回之后,调用mosquitto__free释放了之前的缓存。

这里的代码没啥问题,内存都正常释放了,那怎么会出现内存泄漏呢? (:

  1. 解决问题
    由于没有找到问题的原因,解决问题也就无从谈起,但是,正常情况下应该有这个步骤 😉😃 下面做些一些猜测:
  • 由于log__printf日志输出缓冲区大小不定len = strlen(fmt) + 500,详见log_printf的具体实现,这样申请的内存容易造成内存碎片,可能导致系统无法回收这些内存,最终内存泄漏。
  • 应用本身运行环境导致mosquitto出现该问题… 😉
  • 革命尚未成功,同志仍需努力!

虽然,这次调试过程,最后也没有找到问题的原因,但是,上面提到的**4个分析步骤,**对于一般的问题解决都是通用的,后面可以加以借鉴,至于问题原因还需要进一步努力寻找了,静候佳音吧 😉

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值