最近遇到一个由内存映射引起崩溃问题,因此记录一下解决方法:
一,使用gdb加载core文件,bt操作命令查看堆栈崩溃信息(当未发现core文件的时候可以通过dmesg命令工具进行排查):
定位错误函数代码:
bool ErrorMsgManage::WriteErrMsgToFile (unsigned long updateTime)
{
// 加锁
std::lock_guard<std::mutex> lkfile(m_filelock);
// 获取文件指针和map错误队列
ftruncate(fd, sizeof(sErrMsgFile)*m_ErrorMsg_map.size() + TIME_LEN) ;
// 从文件指针的现行位置
lseek(fd,0,SEEK_CUR) ;
// 文件对象映射到进程的地址空间
unsigned char *p_map = (unsigned char*)mmap(Null,sizeof(sErrMsgFile)*m_ErrorMsg_map,size() + TIME_LEN, PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 )
unsigned char* tmp_map = p_map;
// 写入时间
memmove (tmp_map, std::to_string(updateTime).c_str(), TIME_LEN) :
tmp_map += TIME_LEN;
// 取出错误信息,写入
for (auto iter : m_ErrorMsg_map)
{
unsigned char* aa = reinterpret_cast<unsigned char*>(&iter.second) ;
memmove (tmp_map , aa, sizeof(ErrMsgFieldInfol) ;
tmp_map += sizeof(sErrMsgFile) ;
}
// 关闭内存映射
munmap ( p_map , sizeof(sErrMsgFile)*m_ErrorMsg_map.size() + TIME_LEN) ;
return true;
}
崩溃行:
二,memmove函数:主要作用是从源内存块拷贝一定数量的字节到目标内存块,但只有在源地址和目标地址不重叠时才会拷贝数据。如果源地址和目标地址重叠,该函数会从后向前拷贝数据,以避免数据被覆盖。
三,该函数出现异常原因大致有两个:1,拷贝的内存地址非法。2,只有目标内存块的大小小于源内存块的大小,可能会导致数据被截断,造成不可预期的结果。
四,根据1排查:
a>查看内存空间,内存空间充足
b>经过检查引入的数据正常
c>怀疑多个地方对同一个文件进行写操作导致的,但是对文件操作是有加锁的,排除此坏一点。
std::lock_guard<std::mutex> lkfile(m_filelock);
五,根据2排查:
a>地址空间可能冲突
WriteErrMsgToFile函数中有地址空间映射操作
unsigned char *p_map = (unsigned char*)mmap(Null,sizeof(sErrMsgFile)*m_ErrorMsg_map,size() + TIME_LEN, PROT_READ|PROT_WRITE,MAP_SHARED,fd,0 )
因此怀疑读取队列大小,并映射地址空间完成之后,有操作对map队列写入数据,导致读取队列数据集大于映射出来的地址空间。
检查代码,果然发现对错误消息插入队列的时候未调用锁,而直接进行写数据的操作。代码如下:
void ErrorMsgManage::InsertErrorMsgMap(sErrMsgFile* p_ErrMsgFieldMap)
{
sErrMsgFile *p = reinterpret_cast<sErrMsgFile>(p_ErrMsgFieldMap );
auto iter = m_ErrorMsg_map.find(p->ErrorCode);
if ( iter == m_ErrorMsg_map.end())
{
m_ErrorMsg_map.insert({p->ErrorCode,*p});
}
}
六,解决方法:
在调用错误消息插入队列函数InsertErrorMsgMap中,进行枷锁,避免再次出现崩溃。
std::lock_guard<std::mutex> lkfile(m_filelock);
七,总结:1,确定异常代码段(看日志, 查看core文件, 查看系统日志文件syslog)
2,分析引起错误引起原因的可能性,再追一验证排查