内存管理实验
实验目标
通过实际编程,深入理解虚拟内存、内存映射(mmap)、内存分配等核心概念。
实验列表
实验1:mmap vs 传统IO性能对比
目标: 理解mmap的性能优势
步骤:
- 编译程序:
gcc -o mmap_demo mmap_demo.c
- 运行程序:
./mmap_demo
- 分析结果:
- 对比传统IO和mmap的性能差异
- 理解为什么mmap更快(零拷贝、PageCache)
实验2:进程内存布局观测
目标: 理解进程的虚拟内存空间布局
步骤:
- 编写测试程序:
// memory_layout.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int global_init = 100; // 数据段
int global_uninit; // BSS段
const int global_const = 200; // 只读数据段
int main() {
int stack_var = 1; // 栈
int *heap_var = malloc(sizeof(int)); // 堆
printf("PID: %d\n", getpid());
printf("代码段: %p\n", (void*)main);
printf("只读数据: %p\n", (void*)&global_const);
printf("数据段: %p\n", (void*)&global_init);
printf("BSS段: %p\n", (void*)&global_uninit);
printf("堆: %p\n", (void*)heap_var);
printf("栈: %p\n", (void*)&stack_var);
printf("\n按回车查看详细内存映射...\n");
getchar();
free(heap_var);
return 0;
}
- 编译运行:
gcc -o memory_layout memory_layout.c
./memory_layout &
# 在另一个终端
pmap -x $(pgrep memory_layout)
- 分析内存布局:
- 观察各段的地址范围
- 理解虚拟地址空间的组织
实验3:内存泄漏检测
目标: 学会使用valgrind检测内存泄漏
步骤:
- 编写有内存泄漏的程序:
// memory_leak.c
#include <stdlib.h>
void leak_memory() {
for (int i = 0; i < 1000; i++) {
char *ptr = malloc(1024);
// 故意不释放,造成泄漏
}
}
int main() {
leak_memory();
return 0;
}
- 编译:
gcc -g -o memory_leak memory_leak.c
- 使用valgrind检测:
valgrind --leak-check=full --show-leak-kinds=all ./memory_leak
- 分析输出:
- 查看泄漏的内存量
- 找到泄漏的代码位置
- 修复泄漏
实验4:页缺页中断追踪
目标: 理解页缺页中断机制
步骤:
- 编写测试程序:
// page_fault_test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#define SIZE (1024 * 1024 * 100) // 100MB
int main() {
printf("PID: %d\n", getpid());
printf("按回车分配内存...\n");
getchar();
// 分配大块内存
char *ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
return 1;
}
printf("内存已分配(但未访问)\n");
printf("按回车访问内存(触发缺页中断)...\n");
getchar();
// 访问内存,触发缺页中断
memset(ptr, 0, SIZE);
printf("内存访问完成\n");
printf("按回车退出...\n");
getchar();
munmap(ptr, SIZE);
return 0;
}
- 编译运行:
gcc -o page_fault_test page_fault_test.c
./page_fault_test &
PID=$!
- 观测缺页中断:
# 在另一个终端
# 查看缺页统计
perf stat -e page-faults,minor-faults,major-faults -p $PID
# 或使用/proc
watch -n 1 "cat /proc/$PID/stat | awk '{print \"Minor faults:\", \$10, \"Major faults:\", \$12}'"
实验5:HugePage性能对比
目标: 理解大页对性能的影响
步骤:
- 查看当前HugePage配置:
cat /proc/meminfo | grep Huge
- 配置HugePage:
# 分配1024个2MB的大页
sudo sysctl -w vm.nr_hugepages=1024
- 编写测试程序:
// hugepage_test.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <time.h>
#define SIZE (1024 * 1024 * 1024) // 1GB
void test_normal_page() {
char *ptr;
clock_t start, end;
printf("=== 普通页测试 ===\n");
start = clock();
ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
// 随机访问
for (int i = 0; i < 100000; i++) {
ptr[rand() % SIZE] = 1;
}
end = clock();
printf("耗时: %.2f秒\n\n", (double)(end - start) / CLOCKS_PER_SEC);
munmap(ptr, SIZE);
}
void test_hugepage() {
char *ptr;
clock_t start, end;
printf("=== 大页测试 ===\n");
start = clock();
ptr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap (可能需要配置HugePage)");
return;
}
// 随机访问
for (int i = 0; i < 100000; i++) {
ptr[rand() % SIZE] = 1;
}
end = clock();
printf("耗时: %.2f秒\n\n", (double)(end - start) / CLOCKS_PER_SEC);
munmap(ptr, SIZE);
}
int main() {
test_normal_page();
test_hugepage();
return 0;
}
- 编译运行:
gcc -o hugepage_test hugepage_test.c
./hugepage_test
工具使用
pmap - 查看进程内存映射
pmap -x <pid> # 详细内存映射
pmap -XX <pid> # 更详细的信息
smem - 精确内存统计
smem -r # 按RSS排序
smem -r -s pss # 按PSS排序
valgrind - 内存调试
valgrind --leak-check=full ./program
valgrind --tool=massif ./program # 堆分析
perf - 性能分析
perf stat -e page-faults,minor-faults,major-faults ./program
清理实验环境
# 清理HugePage配置
sudo sysctl -w vm.nr_hugepages=0
# 删除测试文件
rm -f /tmp/mmap*.dat /tmp/traditional*.dat
扩展练习
- 共享内存实验:使用mmap创建进程间共享内存
- 内存池实验:实现一个简单的内存池
- Copy-on-Write实验:观察fork后的内存复制行为
- NUMA实验:在NUMA系统上测试内存访问延迟
注意事项
- 某些实验需要root权限
- HugePage实验需要足够的可用内存
- 建议在测试环境中进行
- 实验结束后清理资源