linux下PageHeap

linux下PageHeap

 问题

   最近游戏开始技术封测了,不过刚刚上线3个小时,Server就挂了,挂在框架代码里,一个不可能挂的地方。
从CallStack看,是在获取数据时发送请求包的时候挂的,由于框架部分是其他部门的同事开发的,所以查问题的时候就拉上他们了,
大家折腾了2天,没有实质性的进展,服务器还是基本上每3个小时宕机一次。由于上层逻辑大部分都在我那,所以压力比较大,宕机的直接原因是hashtable的一个桶的指针异常,
这个hashtable是框架代码的一个内部成员,按道理我们是无从破坏的,只有可能是多线程环境下迭代器损坏导致的。
但是框架代码在这个地方确实无懈可击,所以真正的原因应该还是上层代码破坏了堆内存,很可能是一个memcpy越界导致的。这毕竟是个猜想,如何找到证据呢,这是个问题。
把所有代码里的memcpy浏览了一遍,没有发现明显问题。

猜测

  一般游戏中比较容易出现但是不好查的问题很多时候都是脚本(lua)导致的,我们的脚本部分是一个同事几年前写的,在几个产品中都使用过,按道理没这么脆弱,不过老大还是和最初开发这个模块的部门沟通了下,
还真发现问题了,赶紧拿了新的版本更新上去。经过一天的观察,服务器没有宕机了,OK,问题碰巧解决了,背了这么久的黑锅,终于放下来了。

PageHeap

   假如没有碰巧解决了这个问题,正常的思路该如何解决这个问题呢,这个时候我怀念windows了,在windows下有PageHeap来解决这类写越界的问题。基本思路就是每次分配内存的时候,都将内存的结尾放在页的边缘,紧接着这块内存分配一块不能写的内存,这样,一旦写越界,就会写异常,导致宕机。linux下没有现成的工具,但是linux提供了mmap功能,我们可以自己实现这样一个功能,当然,这一切都不用自己动手了,tcmalloc已经包含了
这个功能了,不过在文档里基本没有介绍,我也是在阅读tcmalloc代码时看到的,这个功能默认是关闭的,打开这个开关需要改写代码:

这个代码在debugallocation.cc里:

DEFINE_bool(malloc_page_fence,
            EnvToBool("TCMALLOC_PAGE_FENCE", false),
            "Enables putting of memory allocations at page boundaries "
            "with a guard page following the allocation (to catch buffer "
            "overruns right when they happen).");
把false改成true就可以了。
想要在项目里加入PageHeap功能,只需要链接的时候加上 -ltcmalloc_debug即可。把它加入项目中,试着运行下,直接挂了,
仔细一看,原来是项目中很多成员变量没有初始化导致的,tcmalloc_debug会自动将new 和malloc出来的内存初始化为指定值,这样,一旦变量没有初始化,很容易就暴露了。
修改完这个问题后,编译,再运行,还是挂,这个是mprotect的时候挂的,错误是内存不够,这怎么可能呢,其实是达到了资源限制了。
echo 128000 > /proc/sys/vm/max_map_count
把map数量限制加大,再运行,OK了!
 
  但是游戏Server启动后,发现一个问题,CPU长期处于100%,导致登陆一个玩家都很困难,gdb中断后,info thread,发现大部分的操作都在mmap和mprotect,最开始
怀疑我的linux版本有问题,导致这2个AP慢,写了测试程序试了下,发现其实API不慢,估计是频繁调用导致的。
所以得换种思路优化下才可以,其实大部分情况下,我们free的时候,无需将页面munmap掉,可以先cache进来,下次分配的时候,如果有,直接拿来用就可以了。
最简单的cache算法就是定义一个void* s_pageCache[50000]数组,页面数相同的内存组成一个链表,挂在一个数组项下,这个很像STL的小内存处理,我们可以将mmap出来的内存的
前面几个字节(一个指针大小)用于索引下一个freePage。当然这个过程需要加锁,不能用pthread的锁(因为他们会调用malloc等内存分配函数),必须用spinlock,从linux源码里直接抄一个过来即可。
static void*   s_pagePool[MAX_PAGE_ALLOC]={0};

malloc的时候,先从pagePool里面获取:
// 先从pagePool找
 void* pFreePage = NULL;
 spin_lock(&s_pageHeapLock);
 assert(nPageNum < MAX_PAGE_ALLOC);
 if(s_pagePool[nPageNum])
 {
   pFreePage = s_pagePool[nPageNum];
   void* pNextFreePage = *((void**)pFreePage);
   s_pagePool[nPageNum] = pNextFreePage;
 }
 spin_unlock(&s_pageHeapLock);

free内存的时候,直接放到pagePoll里:
spin_lock(&s_pageHeapLock);
 assert(nPageNum < MAX_PAGE_ALLOC);
 void* pNextFree = s_pagePool[nPageNum];
 *(void**)pAddress = pNextFree;
 s_pagePool[nPageNum] = pAddress;
 
 spin_unlock(&s_pageHeapLock);

编译、运行,OK了,CPU迅速降下来了,空载的时候不到1%,而且也能达到检测写溢出的问题。

你可能感兴趣的:(linux下PageHeap)