This repository has been archived on 2026-04-18. You can view files and clone it, but cannot push or open issues or pull requests.
Virtual-Memory-Demo/doc/process.md

28 KiB
Raw Permalink Blame History

课前准备5分钟

1. 环境检查脚本(check_env.sh

#!/bin/bash
echo "========== WSL2 虚拟存储器演示环境 =========="
echo ""

echo "系统架构与地址空间"
echo "CPU 架构: $(uname -m)"
echo "虚拟地址位数: $(getconf LONG_BIT) bit"
echo "用户空间地址范围: 0x0000000000000000 ~ 0x00007FFFFFFFFFFF (128TB)"
echo "理论可寻址空间: $((2**47 / 1024**4)) TB"

echo ""
echo "物理资源限制"
echo "物理内存:"
free -h | grep Mem
echo ""
echo "Swap 空间:"
free -h | grep Swap
echo ""

echo "内核内存管理参数"
echo "Overcommit 策略: $(cat /proc/sys/vm/overcommit_memory) (0=启发式, 1=总是允许, 2=严格)"
echo "Overcommit 比例: $(cat /proc/sys/vm/overcommit_ratio)%"
echo "页大小: $(getconf PAGE_SIZE) bytes ($(($(getconf PAGE_SIZE)/1024)) KB)"

echo ""
echo "WSL2 特有配置"
echo "WSL 版本:"
wsl.exe --status 2>/dev/null || echo "请在 Windows PowerShell 运行: wsl --status"
echo ""
echo "当前进程限制:"
ulimit -v  # 虚拟内存限制
echo ""

echo "准备演示程序"
ls -lh *.c 2>/dev/null || echo "请确保已编译所有演示程序"

运行

chmod +x check_env.sh
./check_env.sh

演示环节一突破物理内存限制10分钟

核心程序(demo1_overcommit.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <signal.h>

#define GB (1024ULL * 1024 * 1024)

volatile int stop = 0;
void handler(int sig) { stop = 1; }

void print_status(const char* label) {
    printf("\n┌─ %s ──────────────────────┐\n", label);
    char cmd[512];
    snprintf(cmd, sizeof(cmd),
        "echo \"│ 虚拟内存(VmSize):\" && cat /proc/%d/status | grep VmSize | awk '{print \"\" $2 \" \" $3}' && "
        "echo \"│ 物理驻留(RSS):\" && cat /proc/%d/status | grep VmRSS | awk '{print \"\" $2 \" \" $3}' && "
        "echo \"│ 交换空间(VmSwap):\" && cat /proc/%d/status | grep VmSwap | awk '{print \"\" $2 \" \" $3}'",
        getpid(), getpid(), getpid());
    system(cmd);
    printf("└────────────────────────────────┘\n");
}

int main() {
    signal(SIGINT, handler);
    printf("【演示1】突破物理内存限制\n");
    printf("PID: %d | 按 Ctrl+C 随时暂停\n\n", getpid());
    
    // 获取物理内存
    long pages = sysconf(_SC_PHYS_PAGES);
    long page_size = sysconf(_SC_PAGE_SIZE);
    double phys_gb = (double)pages * page_size / GB;
    printf("► 系统物理内存: %.1f GB\n", phys_gb);
    
    // 分配 3倍物理内存的虚拟空间
    size_t alloc_size = (size_t)(phys_gb * 3 * GB);
    printf("► 尝试分配: %.1f GB 虚拟内存 (3倍物理内存)\n", alloc_size / (double)GB);
    printf("  按回车继续...");
    getchar();
    
    void *mem = mmap(NULL, alloc_size, PROT_READ | PROT_WRITE,
                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0);
    if (mem == MAP_FAILED) {
        perror("mmap 失败");
        return 1;
    }
    
    printf("✓ 分配成功! 虚拟地址: %p\n", mem);
    print_status("分配后(未访问)");
    printf("  → 注意: VmSize ≈ %.1f GB, RSS ≈ 0\n", alloc_size/(double)GB);
    
    printf("\n► 开始稀疏访问(只使用 10%% 空间)...\n");
    printf("  按回车继续...");
    getchar();
    
    // 稀疏访问每100页访问1页
    long page_count = alloc_size / page_size;
    for (int i = 0; i < page_count / 100 && !stop; i++) {
        ((char*)mem)[i * 100 * page_size] = 'A';
        if (i % 1000 == 0) {
            printf("  已标记 %d 页 (%.2f MB)\r", i, i*100*page_size/(1024.0*1024));
            fflush(stdout);
        }
    }
    
    print_status("稀疏访问后");
    printf("  → RSS 应该很小(仅实际访问的页)\n");
    
    printf("\n► 现在密集访问,直到触发 swap 或 OOM...\n");
    printf("  按回车继续Ctrl+C 停止)...");
    getchar();
    
    size_t accessed = 0;
    while (!stop && accessed < alloc_size) {
        memset((char*)mem + accessed, 'X', 10 * 1024 * 1024); // 10MB 每步
        accessed += 10 * 1024 * 1024;
        if (accessed % (256 * 1024 * 1024) == 0) {
            printf("  已访问 %.0f MB\n", accessed/(1024.0*1024));
            print_status("密集访问中");
        }
    }
    
    print_status("最终状态");
    munmap(mem, alloc_size);
    return 0;
}

演示要点

  1. 对比VmSize 立即达到 3GBRSS 保持很小
  2. 稀疏访问:证明大地址空间 + 小物理内存 = 高效
  3. 密集访问:观察 RSS 增长 → VmSwap 增长 → 可能的 OOM

配合监控(另一个终端):

watch -n 0.3 'cat /proc/<PID>/status | grep -E "VmSize|VmRSS|VmSwap"'

演示环节二进程隔离与内存保护8分钟

核心程序(demo2_isolation.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int global = 100;  // 数据段

void print_maps(const char* who) {
    printf("\n【%s的内存映射】\n", who);
    char cmd[256];
    snprintf(cmd, sizeof(cmd), 
        "cat /proc/%d/maps | grep -E 'stack|heap|global' | head -5", getpid());
    system(cmd);
}

void print_vars(const char* who, int* local, int* dynamic) {
    printf("  %s: global=%d @ %p, local=%d @ %p, dynamic=%d @ %p\n",
           who, global, &global, *local, local, *dynamic, dynamic);
}

int main() {
    printf("【演示2】进程隔离与内存保护\n");
    printf("PID: %d\n\n", getpid());
    
    int local = 200;  // 栈
    int *dynamic = malloc(sizeof(int));
    *dynamic = 300;   // 堆
    
    printf("► 父进程初始状态:\n");
    print_vars("父进程", &local, dynamic);
    print_maps("父进程");
    
    printf("\n► fork() 创建子进程...\n");
    printf("  (子进程将复制父进程的虚拟地址空间,但物理页共享)\n");
    printf("  按回车继续...");
    getchar();
    
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        printf("\n┌─ 子进程 (PID: %d) ─┐\n", getpid());
        print_vars("子进程", &local, dynamic);
        
        printf("\n► 子进程修改所有变量...\n");
        global = 1000;
        local = 2000;
        *dynamic = 3000;
        print_vars("修改后", &local, dynamic);
        
        printf("\n► 查看 /proc/%d/maps与父进程独立\n", getpid());
        char cmd[256];
        snprintf(cmd, sizeof(cmd), "cat /proc/%d/maps | head -10", getpid());
        system(cmd);
        
        printf("\n  注意: 虚拟地址相同,但物理页已复制(COW)\n");
        exit(0);
    } else {
        wait(NULL);
        printf("\n└─ 父进程 (PID: %d) ─┘\n", getpid());
        print_vars("父进程(子进程已退出)", &local, dynamic);
        printf("  → 父进程变量未被修改!进程完全隔离\n");
    }
    
    free(dynamic);
    return 0;
}

进阶:验证 COW写时复制

// 添加这段代码观察 COW 前后的页状态
#include <sys/resource.h>

void print_cow_stats(const char* label) {
    struct rusage usage;
    getrusage(RUSAGE_SELF, &usage);
    printf("  %s: 次要缺页=%ld, 主要缺页=%ld\n", 
           label, usage.ru_minflt, usage.ru_majflt);
}

演示环节三内存共享机制8分钟

核心程序(demo3_sharing.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <fcntl.h>

#define SIZE (100 * 1024 * 1024)  // 100MB

int main() {
    printf("【演示3】内存共享机制\n\n");
    
    // 创建共享内存对象
    int fd = shm_open("/demo_shm", O_CREAT | O_RDWR, 0666);
    ftruncate(fd, SIZE);
    
    printf("► 创建共享内存: /demo_shm (%.1f MB)\n", SIZE/(1024.0*1024));
    
    // 映射到虚拟地址空间
    void *addr = mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    printf("► 映射到虚拟地址: %p\n", addr);
    
    // 填充数据
    strcpy((char*)addr, "Hello from shared memory!");
    
    printf("\n► fork() 创建子进程...\n");
    printf("  父子进程共享同一段物理内存!\n");
    
    pid_t pid = fork();
    if (pid == 0) {
        printf("\n┌─ 子进程 ─┐\n");
        printf("  读取共享内存: %s\n", (char*)addr);
        printf("  修改共享内存...");
        strcpy((char*)addr, "Modified by child!");
        printf("完成\n");
        exit(0);
    } else {
        wait(NULL);
        printf("\n└─ 父进程 ─┘\n");
        printf("  读取共享内存: %s\n", (char*)addr);
        printf("  → 子进程的修改立即可见!\n");
    }
    
    // 对比:普通内存 vs 共享内存
    printf("\n► 对比:普通堆内存 vs 共享内存\n");
    char *heap_mem = malloc(1024);
    strcpy(heap_mem, "Heap data");
    
    printf("  堆内存地址: %p (仅本进程可见)\n", (void*)heap_mem);
    printf("  共享内存地址: %p (多进程共享)\n", addr);
    
    // 查看映射
    printf("\n► 查看 /proc/%d/maps 中的共享内存\n", getpid());
    char cmd[256];
    snprintf(cmd, "cat /proc/%d/maps | grep demo_shm", getpid());
    system(cmd);
    
    munmap(addr, SIZE);
    shm_unlink("/demo_shm");
    free(heap_mem);
    return 0;
}

编译需要

gcc -o demo3_sharing demo3_sharing.c -lrt

演示环节四内存保护5分钟

核心程序(demo4_protection.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

sigjmp_buf env;

void segfault_handler(int sig) {
    printf("✓ 捕获到段错误 (SIGSEGV) - 内存保护生效!\n");
    siglongjmp(env, 1);
}

int main() {
    printf("【演示4】内存保护机制\n\n");
    
    signal(SIGSEGV, segfault_handler);
    
    // 分配内存,设置不同权限
    size_t size = 4096 * 3;
    char *mem = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    
    printf("► 分配 3 页内存,初始权限: PROT_NONE完全不可访问\n");
    printf("  虚拟地址: %p\n", (void*)mem);
    
    if (sigsetjmp(env, 1) == 0) {
        printf("\n► 尝试读取(应失败)...");
        fflush(stdout);
        char x = mem[0];  // 触发 SIGSEGV
        printf("错误:未触发保护!\n");
    }
    
    // 修改第一页为只读
    printf("\n► 修改第一页为 PROT_READ只读\n");
    mprotect(mem, 4096, PROT_READ);
    
    if (sigsetjmp(env, 1) == 0) {
        printf("  尝试读取: %d ✓\n", mem[0]);
        printf("  尝试写入...");
        fflush(stdout);
        mem[0] = 'A';  // 应失败
        printf("错误:未触发保护!\n");
    }
    
    // 修改第二页为可读写
    printf("\n► 修改第二页为 PROT_READ | PROT_WRITE读写\n");
    mprotect(mem + 4096, 4096, PROT_READ | PROT_WRITE);
    mem[4096] = 'X';
    printf("  读写成功: %c ✓\n", mem[4096]);
    
    // 第三页保持 PROT_NONE
    
    printf("\n► 查看权限设置:\n");
    printf("  页1 (%p): 只读\n", (void*)mem);
    printf("  页2 (%p): 读写\n", (void*)(mem + 4096));
    printf("  页3 (%p): 不可访问\n", (void*)(mem + 8192));
    
    printf("\n► 验证 /proc/%d/maps:\n", getpid());
    char cmd[256];
    snprintf(cmd, "cat /proc/%d/maps | grep %p", getpid(), (void*)mem);
    system(cmd);
    
    munmap(mem, size);
    return 0;
}

演示环节五简化内存管理与屏蔽底层差异4分钟

快速演示(demo5_management.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 展示虚拟内存如何简化编程:连续大数组,无需关心物理碎片
int main() {
    printf("【演示5】简化内存管理 & 屏蔽底层差异\n\n");
    
    printf("► 程序员视角:申请 1GB 连续数组\n");
    size_t size = 1024 * 1024 * 1024;
    int *big_array = malloc(size);
    
    if (!big_array) {
        printf("  分配失败\n");
        return 1;
    }
    
    printf("  ✓ 成功: big_array[0] @ %p\n", (void*)&big_array[0]);
    printf("  ✓ 成功: big_array[262144] @ %p (中间)\n", 
           (void*)&big_array[262144]);
    printf("  ✓ 成功: big_array[268435455] @ %p (末尾)\n",
           (void*)&big_array[268435455]);
    
    printf("\n► 物理现实(由内核处理):\n");
    printf("  - 物理内存可能是碎片化的\n");
    printf("  - 通过页表映射为'连续'的虚拟地址\n");
    printf("  - 程序员无需关心物理地址、NUMA 节点、内存 bank\n");
    
    printf("\n► 查看实际物理页分布:\n");
    system("cat /proc/self/pagemap 2>/dev/null | head -5 || echo '需要 root 权限读取 pagemap'");
    
    printf("\n► 跨平台一致性:\n");
    printf("  这段代码在以下环境表现一致:\n");
    printf("  - WSL2 (Hyper-V 虚拟化)\n");
    printf("  - 原生 Linux (x86_64)\n");
    printf("  - 云服务器 (KVM/Xen)\n");
    printf("  - 嵌入式 ARM\n");
    printf("  虚拟存储器屏蔽了硬件差异!\n");
    
    free(big_array);
    return 0;
}

完整演示流程表

时间 环节 操作 可视化重点
0-5' 环境检查 运行 check_env.sh 地址位数、物理内存、overcommit 策略
5-15' 突破物理限制 运行 demo1 + watch 监控 VmSize vs RSS 巨大差异swap 增长
15-23' 进程隔离 运行 demo2,对比父子进程 相同虚拟地址独立物理空间COW 机制
23-31' 内存共享 运行 demo3,对比 mmap 共享 vs 堆内存 多进程看到相同数据,映射名称可见
31-36' 内存保护 运行 demo4,触发段错误 mprotect 权限变化,maps 中 r/w/x 标志
36-40' 简化管理 运行 demo5,总结 连续虚拟地址 vs 物理碎片,跨平台一致性

课堂互动设计

预测-验证环节

在每个演示前,先让学生预测结果:

"我要分配 3 倍物理内存malloc 会成功吗?"

  • 预测 A失败内存不够
  • 预测 B成功但会立即占用大量物理内存
  • 预测 C成功且几乎不占用物理内存

验证:运行代码,观察 VmSizeRSS

实时对比(双终端投影)

左屏:运行程序,显示输出
右屏:持续运行监控命令

# 推荐监控组合
watch -n 0.5 'clear; echo "=== 进程 ==="; cat /proc/<PID>/status | grep -E "Vm|Rss|Swap"; echo ""; echo "=== 系统 ==="; free -h'

关键结论板书

┌─────────────────────────────────────────┐
│  虚拟存储器 = 物理内存的"魔术师"        │
├─────────────────────────────────────────┤
│  1. 空间魔法: 小物理 → 大虚拟地址空间   │
│  2. 隔离魔法: 多进程互不干扰             │
│  3. 共享魔法: 相同物理页多视角映射      │
│  4. 保护魔法: 硬件级权限控制             │
│  5. 简化魔法: 连续地址屏蔽物理复杂性      │
└─────────────────────────────────────────┘
#!/bin/bash
# 进程内存隔离实时监控 - 纯外部版本 (Plan B)
# 通过 /proc 文件系统直接分析,无需修改目标程序

TARGET_PID=""
REFRESH_INTERVAL=0.5

# 清屏
clear_screen() {
    printf "\033[2J\033[H"
}

# 绘制标题
draw_header() {
    local title=$1
    printf "════════════════════════════════════════════════════════════════\n"
    printf "      %s\n" "$title"
    printf "════════════════════════════════════════════════════════════════\n\n"
}

# 获取进程基本信息
get_proc_basic() {
    local pid=$1
    if [ ! -d "/proc/$pid" ]; then
        echo "进程不存在"
        return 1
    fi
    
    local name=$(cat /proc/$pid/comm 2>/dev/null)
    local ppid=$(grep PPid /proc/$pid/status 2>/dev/null | awk '{print $2}')
    local state=$(grep State /proc/$pid/status 2>/dev/null | awk '{print $2}')
    printf "进程名: %-15s | PPID: %-6s | 状态: %s\n" "$name" "${ppid:-0}" "$state"
}

# 获取内存统计
get_mem_stats() {
    local pid=$1
    if [ ! -f "/proc/$pid/status" ]; then
        return 1
    fi
    
    local vmsize=$(grep VmSize /proc/$pid/status 2>/dev/null | awk '{print $2}')
    local vmrss=$(grep VmRSS /proc/$pid/status 2>/dev/null | awk '{print $2}')
    local vmdata=$(grep VmData /proc/$pid/status 2>/dev/null | awk '{print $2}')
    local vmstk=$(grep VmStk /proc/$pid/status 2>/dev/null | awk '{print $2}')
    
    printf "  虚拟内存(VmSize): %8s KB (%6.2f MB)\n" "${vmsize:-0}" $(echo "scale=2; ${vmsize:-0}/1024" | bc 2>/dev/null || echo "0")
    printf "  物理驻留(VmRSS):  %8s KB (%6.2f MB)\n" "${vmrss:-0}" $(echo "scale=2; ${vmrss:-0}/1024" | bc 2>/dev/null || echo "0")
    printf "  数据段(VmData):   %8s KB\n" "${vmdata:-0}"
    printf "  栈段(VmStk):      %8s KB\n" "${vmstk:-0}"
}

# 获取关键内存映射(堆、栈、全局数据)
get_key_mappings() {
    local pid=$1
    if [ ! -f "/proc/$pid/maps" ]; then
        return 1
    fi
    
    printf "  %-20s %-18s %-10s %s\n" "区域" "虚拟地址范围" "权限" "大小"
    printf "  %-20s %-18s %-10s %s\n" "────────────────────" "──────────────────" "──────────" "────"
    
    # 堆
    local heap=$(grep "\\[heap\\]" /proc/$pid/maps 2>/dev/null | head -1)
    if [ -n "$heap" ]; then
        local range=$(echo "$heap" | awk '{print $1}')
        local perm=$(echo "$heap" | awk '{print $2}')
        local size_kb=$(echo "$heap" | awk '{print $3}')
        printf "  %-20s %-18s %-10s %s\n" "[堆] heap" "$range" "$perm" "${size_kb:-动态增长}"
    fi
    
    # 栈
    local stack=$(grep "\\[stack\\]" /proc/$pid/maps 2>/dev/null | head -1)
    if [ -n "$stack" ]; then
        local range=$(echo "$stack" | awk '{print $1}')
        local perm=$(echo "$stack" | awk '{print $2}')
        printf "  %-20s %-18s %-10s %s\n" "[栈] stack" "$range" "$perm" "动态扩展"
    fi
    
    # 全局数据段(通过 maps 中的 data 段或程序段识别)
    local data_seg=$(grep -E "\.data|rw-p.*[0-9a-f]+ [0-9a-f]+ [0-9a-f]+ .*$" /proc/$pid/maps 2>/dev/null | grep -v heap | head -1)
    if [ -n "$data_seg" ]; then
        local range=$(echo "$data_seg" | awk '{print $1}')
        local perm=$(echo "$data_seg" | awk '{print $2}')
        printf "  %-20s %-18s %-10s %s\n" "[数据段] data" "$range" "$perm" "全局变量"
    fi
}

# 获取 COW 相关信息(通过 smaps
get_cow_info() {
    local pid=$1
    if [ ! -f "/proc/$pid/smaps_rollup" ]; then
        printf "  (内核不支持 smaps_rollup跳过 COW 分析)\n"
        return 1
    fi
    
    local shared_clean=$(grep "Shared_Clean" /proc/$pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    local shared_dirty=$(grep "Shared_Dirty" /proc/$pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    local private_clean=$(grep "Private_Clean" /proc/$pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    local private_dirty=$(grep "Private_Dirty" /proc/$pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    
    printf "  共享内存(Shared):   %8s KB (Clean: %s, Dirty: %s)\n" \
        "$((${shared_clean:-0} + ${shared_dirty:-0}))" "${shared_clean:-0}" "${shared_dirty:-0}"
    printf "  私有内存(Private):  %8s KB (Clean: %s, Dirty: %s)\n" \
        "$((${private_clean:-0} + ${private_dirty:-0}))" "${private_clean:-0}" "${private_dirty:-0}"
    
    # COW 判断Private_Dirty 增加表示发生了写时复制
    if [ "${private_dirty:-0}" -gt 0 ]; then
        printf "  → Private_Dirty: %s KB (可能发生 COW 或写入操作)\n" "$private_dirty"
    fi
}

# 单进程监控视图
monitor_single() {
    local pid=$1
    local label=$2
    
    clear_screen
    draw_header "进程内存监控 - $label (PID: $pid)"
    
    printf "【基本信息】\n"
    get_proc_basic $pid
    
    printf "\n【内存统计】\n"
    get_mem_stats $pid
    
    printf "\n【关键映射】\n"
    get_key_mappings $pid
    
    printf "\n【COW / 共享状态】\n"
    get_cow_info $pid
    
    printf "\n────────────────────────────────────────────────────────────────\n"
    printf "提示: 按 Ctrl+C 退出 | 检测到 fork 将自动切换对比模式\n"
}

# 双进程对比视图
monitor_compare() {
    local parent_pid=$1
    local child_pid=$2
    
    clear_screen
    draw_header "父子进程内存隔离对比"
    
    # 检查进程是否存在
    if [ ! -d "/proc/$parent_pid" ] || [ ! -d "/proc/$child_pid" ]; then
        printf "错误: 父进程或子进程已退出\n"
        return 1
    fi
    
    # 获取数据
    local parent_vmrss=$(grep VmRSS /proc/$parent_pid/status 2>/dev/null | awk '{print $2}')
    local child_vmrss=$(grep VmRSS /proc/$child_pid/status 2>/dev/null | awk '{print $2}')
    local parent_private=$(grep Private_Dirty /proc/$parent_pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    local child_private=$(grep Private_Dirty /proc/$child_pid/smaps_rollup 2>/dev/null | awk '{print $2}')
    
    # 计算指标
    local total_rss=$(( (${parent_vmrss:-0} + ${child_vmrss:-0}) ))
    local rss_diff=$(( ${child_vmrss:-0} - ${parent_vmrss:-0} ))
    
    printf "┌────────────────────────────┬────────────────────────────┐\n"
    printf "│  父进程 (PID: %-11s)│  子进程 (PID: %-11s)│\n" "$parent_pid" "$child_pid"
    printf "├────────────────────────────┼────────────────────────────┤\n"
    
    # 基本信息
    local parent_name=$(cat /proc/$parent_pid/comm 2>/dev/null)
    local child_name=$(cat /proc/$child_pid/comm 2>/dev/null)
    printf "│ 名称: %-20s │ 名称: %-20s │\n" "$parent_name" "$child_name"
    
    printf "├────────────────────────────┼────────────────────────────┤\n"
    
    # 内存对比
    printf "│【虚拟内存(VmSize)】        │【虚拟内存(VmSize)】        │\n"
    local parent_vm=$(grep VmSize /proc/$parent_pid/status 2>/dev/null | awk '{print $2}')
    local child_vm=$(grep VmSize /proc/$child_pid/status 2>/dev/null | awk '{print $2}')
    printf "│ %8s KB (%6.2f MB)     │ %8s KB (%6.2f MB)     │\n" \
        "${parent_vm:-0}" $(echo "scale=2; ${parent_vm:-0}/1024" | bc 2>/dev/null || echo "0") \
        "${child_vm:-0}" $(echo "scale=2; ${child_vm:-0}/1024" | bc 2>/dev/null || echo "0")
    
    printf "├────────────────────────────┼────────────────────────────┤\n"
    printf "│【物理内存(VmRSS)】         │【物理内存(VmRSS)】         │\n"
    printf "│ %8s KB (%6.2f MB)     │ %8s KB (%6.2f MB)     │\n" \
        "${parent_vmrss:-0}" $(echo "scale=2; ${parent_vmrss:-0}/1024" | bc 2>/dev/null || echo "0") \
        "${child_vmrss:-0}" $(echo "scale=2; ${child_vmrss:-0}/1024" | bc 2>/dev/null || echo "0")
    
    printf "├────────────────────────────┼────────────────────────────┤\n"
    printf "│【私有脏页(Private_Dirty)】 │【私有脏页(Private_Dirty)】 │\n"
    printf "│ %8s KB                │ %8s KB                │\n" "${parent_private:-0}" "${child_private:-0}"
    
    printf "├────────────────────────────┼────────────────────────────┤\n"
    printf "│【虚拟地址空间】            │【虚拟地址空间】            │\n"
    printf "│ 与 fork 前相同             │ 继承父进程(初始相同)     │\n"
    
    printf "├────────────────────────────┼────────────────────────────┤\n"
    printf "│【物理页状态】              │【物理页状态】              │\n"
    if [ "${child_private:-0}" -gt "${parent_private:-0}" ] && [ "${child_private:-0}" -gt 4 ]; then
        printf "│ 部分共享 (COW 可能已触发)  │ 已独立 (COW 已触发)        │\n"
    else
        printf "│ 共享 (只读,等待写入)      │ 共享 (只读,等待写入)      │\n"
    fi
    
    printf "└────────────────────────────┴────────────────────────────┘\n"
    
    # COW 分析总结
    printf "\n【COW (写时复制) 分析】\n"
    printf "  父子 RSS 总和: %d KB (%.2f MB)\n" "$total_rss" $(echo "scale=2; $total_rss/1024" | bc 2>/dev/null || echo "0")
    printf "  子进程 RSS 增量: %d KB\n" "${rss_diff:-0}"
    
    if [ "${child_private:-0}" -gt 4 ]; then
        printf "  → 检测到子进程有 %s KB 私有脏页COW 已触发!\n" "${child_private:-0}"
        printf "  → 子进程修改了共享页,内核已复制物理页框\n"
    else
        printf "  → 子进程 Private_Dirty 较低COW 尚未触发或修改较少\n"
    fi
    
    printf "\n────────────────────────────────────────────────────────────────\n"
    printf "提示: 按 Ctrl+C 退出 | 子进程退出将返回单进程视图\n"
}

# 主监控循环
main() {
    if [ $# -lt 1 ]; then
        echo "用法: $0 <目标进程PID>"
        echo ""
        echo "功能:"
        echo "  1. 监控单进程内存状态(堆、栈、数据段)"
        echo "  2. 自动检测 fork() 创建的子进程"
        echo "  3. 对比父子进程的 COW (写时复制) 状态"
        echo ""
        echo "示例:"
        echo "  终端1: ./demo2_isolation"
        echo "  终端2: $0 \$(pgrep demo2_isolation | head -1)"
        exit 1
    fi
    
    local parent_pid=$1
    local child_pid=""
    
    # 验证初始进程存在
    if [ ! -d "/proc/$parent_pid" ]; then
        echo "错误: 进程 $parent_pid 不存在"
        exit 1
    fi
    
    trap 'clear_screen; echo "监控已退出"; exit 0' INT
    
    while true; do
        # 检测子进程(通过 PPID 匹配)
        if [ -z "$child_pid" ] || [ ! -d "/proc/$child_pid" ]; then
            # 查找目标进程的子进程
            child_pid=$(pgrep -P $parent_pid 2>/dev/null | head -1)
        fi
        
        # 如果父进程退出,结束监控
        if [ ! -d "/proc/$parent_pid" ]; then
            clear_screen
            echo "目标进程已退出,监控结束"
            exit 0
        fi
        
        # 选择视图
        if [ -n "$child_pid" ] && [ -d "/proc/$child_pid" ]; then
            monitor_compare $parent_pid $child_pid
        else
            monitor_single $parent_pid "父进程"
        fi
        
        sleep $REFRESH_INTERVAL
    done
}