目录
4.1. 创建一个动态链接库用于挂钩 malloc 和 free
1. 朋友,了解一下Linux的内存工作原理吧!
朋友,你有没有遇到过这样的情况?你的Linux程序运行得挺顺利,但突然间发现内存占用飙升,系统变得卡顿,甚至有时候还会崩溃?别担心,今天我们就来聊聊Linux内存的工作原理,以及如何管理和监控内存,防止那些讨厌的内存泄漏。听起来有点复杂?别急,我会用最简单的方式,帮你把这些概念搞清楚。
1.1. 这张图展示的是一个Linux进程的虚拟内存结构
首先,让我们看看一个Linux进程的虚拟内存结构。内存被划分为多个区域,每个区域都有不同的用途。从下到上依次是:
-
代码段 (.text):
- 这里保存了程序的可执行代码。当程序加载时,代码从磁盘读入并开始执行。
- 所有进程共享相同的代码,通常是只读的,确保了代码的安全性和一致性。
-
已初始化数据段 (.data):
- 这个区域存放已初始化的全局变量和静态变量。
- 比如说,你在程序中定义的全局数组或静态计数器,就会被存放在这里。
-
未初始化数据段 (.bss):
- 这里保存未初始化的全局变量和静态变量,初始值为0。
- 运行时会动态分配内存给这些变量,确保它们在使用前被正确初始化。
-
运行时堆 (heap):
- 堆区是用来动态分配内存的地方。每当你调用
malloc
、calloc
或realloc
时,内存就会从这里分配出来。 brk
指向堆的顶部,随着堆的扩展而增长。- 内存泄漏问题通常发生在这里。如果程序分配了内存却没有正确释放,堆区会不断增大,导致内存资源浪费。
- 堆区是用来动态分配内存的地方。每当你调用
-
共享库的内存映射区域:
- 这个区域用来存放共享库(比如
.so
文件)的内存映射。 - 多个进程可以共享相同的内存区域,节省了系统资源。
- 这个区域用来存放共享库(比如
-
用户栈 (stack):
- 栈区用来存储函数调用时的局部变量和函数参数。
- 每次函数调用都会在栈中压入一个新的帧,函数返回时弹出这个帧。
- 栈是从高地址向低地址增长的,内存分配是自动的,由系统管理。
-
物理内存、内核代码和数据:
- 这是系统保留的区域,包括物理内存和与进程相关的数据结构(如页表、任务描述符等)。
- 这些区域对用户进程是不可见的,确保了系统的稳定性和安全性。
-
内核虚拟内存:
- 这个区域保存了与内核相关的虚拟内存,供系统操作使用。
- 它与用户空间是分离的,保证了内核操作的高效和安全。
2. 内存分配与回收:让你的程序跑得更稳健
内存分配与回收是程序运行过程中至关重要的部分。合理的内存管理不仅能提升程序的性能,还能防止那些讨厌的内存泄漏问题。让我们来看看内存分配与回收的基本流程吧!
2.1. 内存分配与内存泄漏
堆区是内存分配的核心。程序员可以通过调用 malloc
、calloc
或 realloc
等函数,动态地分配和释放内存。然而,问题就出在这里——如果分配了内存却没有释放,或者在程序退出时忘记释放内存,这就会导致内存泄漏。
内存泄漏的危害:
- 内存堆逐渐增大:未释放的内存会让堆区不断膨胀,系统的可用内存减少。
- 性能下降:内存资源被浪费,程序运行效率降低。
- 系统崩溃:长时间运行的程序可能耗尽系统内存,导致程序或整个系统崩溃。
相比之下,栈区的内存分配是自动的,由系统管理。每次函数调用时分配内存,函数返回时自动释放。因此,栈区不会出现内存泄漏的问题。但要注意,栈溢出也是一个潜在的问题,尤其是在递归调用过多的情况下。
3. 内存泄漏检测代码分析
要确保你的程序不会出现内存泄漏,检测和监控内存的分配与释放是必不可少的。代码实现了两种内存监控方式,帮助我们检测内存泄漏:
- 预处理宏替换(通过宏定义替换
malloc
和free
) - 动态链接库挂钩(通过
dlsym
挂钩malloc
和free
)
让我们一起来看看这两种方法是如何工作的,以及它们各自的优缺点。
3.1. 预处理宏替换方法
实现原理:
通过预处理宏,将代码中的 malloc
和 free
函数替换为自定义的 nMalloc
和 nFree
函数。这种方法在编译阶段完成替换,所有对 malloc
和 free
的调用都会被替换为带有额外参数(如文件名、函数名、行号)的自定义函数。
代码片段:
#if 0
void *nMalloc(size_t size, const char *filename, const char *funcname, int line) {
void *ptr = malloc(size);
char buff[128] = {0};
snprintf(buff, 128, "./block/%p.mem", ptr);
FILE* fp = fopen(buff, "w");
if (!fp) {
free(ptr);
return NULL;
}
fprintf(fp, "[+][%s:%s:%d] %p: %ld malloc\n", filename, funcname, line, ptr, size);
fflush(fp);
fclose(fp);
return ptr;
}
void nFree(void *ptr, const char *filename, const char *funcname, int line) {
char buff[128] = {0};
snprintf(buff, 128, "./block/%p.mem", ptr);
if (unlink(buff) < 0) { // 文件不存在
printf("double free: %p at %s:%s:%d\n", ptr, filename, funcname, line);
return;
}
return free(ptr);
}
#define malloc(size) nMalloc(size, __FILE__, __func__, __LINE__)
#define