HiHuo
首页
博客
手册
工具
首页
博客
手册
工具
  • 系统底层修炼

    • 操作系统核心知识学习指南
    • CPU调度与上下文切换
    • CFS调度器原理与源码
    • 内存管理与虚拟内存
    • PageCache与内存回收
    • 文件系统与IO优化
    • 零拷贝与Direct I/O
    • 网络子系统架构
    • TCP协议深度解析
    • TCP问题排查实战
    • 网络性能优化
    • epoll与IO多路复用
    • 进程与线程管理
    • Go Runtime调度器GMP模型
    • 系统性能分析方法论
    • DPDK与用户态网络栈
    • eBPF与内核可观测性
    • 综合实战案例

内存管理实验

实验目标

通过实际编程,深入理解虚拟内存、内存映射(mmap)、内存分配等核心概念。

实验列表

实验1:mmap vs 传统IO性能对比

目标: 理解mmap的性能优势

步骤:

  1. 编译程序:
gcc -o mmap_demo mmap_demo.c
  1. 运行程序:
./mmap_demo
  1. 分析结果:
    • 对比传统IO和mmap的性能差异
    • 理解为什么mmap更快(零拷贝、PageCache)

实验2:进程内存布局观测

目标: 理解进程的虚拟内存空间布局

步骤:

  1. 编写测试程序:
// 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;
}
  1. 编译运行:
gcc -o memory_layout memory_layout.c
./memory_layout &

# 在另一个终端
pmap -x $(pgrep memory_layout)
  1. 分析内存布局:
    • 观察各段的地址范围
    • 理解虚拟地址空间的组织

实验3:内存泄漏检测

目标: 学会使用valgrind检测内存泄漏

步骤:

  1. 编写有内存泄漏的程序:
// 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;
}
  1. 编译:
gcc -g -o memory_leak memory_leak.c
  1. 使用valgrind检测:
valgrind --leak-check=full --show-leak-kinds=all ./memory_leak
  1. 分析输出:
    • 查看泄漏的内存量
    • 找到泄漏的代码位置
    • 修复泄漏

实验4:页缺页中断追踪

目标: 理解页缺页中断机制

步骤:

  1. 编写测试程序:
// 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;
}
  1. 编译运行:
gcc -o page_fault_test page_fault_test.c
./page_fault_test &
PID=$!
  1. 观测缺页中断:
# 在另一个终端
# 查看缺页统计
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性能对比

目标: 理解大页对性能的影响

步骤:

  1. 查看当前HugePage配置:
cat /proc/meminfo | grep Huge
  1. 配置HugePage:
# 分配1024个2MB的大页
sudo sysctl -w vm.nr_hugepages=1024
  1. 编写测试程序:
// 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;
}
  1. 编译运行:
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

扩展练习

  1. 共享内存实验:使用mmap创建进程间共享内存
  2. 内存池实验:实现一个简单的内存池
  3. Copy-on-Write实验:观察fork后的内存复制行为
  4. NUMA实验:在NUMA系统上测试内存访问延迟

注意事项

  • 某些实验需要root权限
  • HugePage实验需要足够的可用内存
  • 建议在测试环境中进行
  • 实验结束后清理资源