## 课前准备(5分钟) ### 1. 环境检查脚本(`check_env.sh`) ```bash #!/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 "请确保已编译所有演示程序" ``` **运行**: ```bash chmod +x check_env.sh ./check_env.sh ``` --- ## 演示环节一:突破物理内存限制(10分钟) ### 核心程序(`demo1_overcommit.c`) ```c #include #include #include #include #include #include #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` 立即达到 3GB,`RSS` 保持很小 2. **稀疏访问**:证明大地址空间 + 小物理内存 = 高效 3. **密集访问**:观察 `RSS` 增长 → `VmSwap` 增长 → 可能的 OOM **配合监控**(另一个终端): ```bash watch -n 0.3 'cat /proc//status | grep -E "VmSize|VmRSS|VmSwap"' ``` --- ## 演示环节二:进程隔离与内存保护(8分钟) ### 核心程序(`demo2_isolation.c`) ```c #include #include #include #include #include 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(写时复制)** ```c // 添加这段代码观察 COW 前后的页状态 #include 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`) ```c #include #include #include #include #include #include #include #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; } ``` **编译需要**: ```bash gcc -o demo3_sharing demo3_sharing.c -lrt ``` --- ## 演示环节四:内存保护(5分钟) ### 核心程序(`demo4_protection.c`) ```c #include #include #include #include #include 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`) ```c #include #include #include // 展示虚拟内存如何简化编程:连续大数组,无需关心物理碎片 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:成功,且几乎不占用物理内存 > > **验证**:运行代码,观察 `VmSize` 和 `RSS` ### 实时对比(双终端投影) **左屏**:运行程序,显示输出 **右屏**:持续运行监控命令 ```bash # 推荐监控组合 watch -n 0.5 'clear; echo "=== 进程 ==="; cat /proc//status | grep -E "Vm|Rss|Swap"; echo ""; echo "=== 系统 ==="; free -h' ``` ### 关键结论板书 ```plaintext ┌─────────────────────────────────────────┐ │ 虚拟存储器 = 物理内存的"魔术师" │ ├─────────────────────────────────────────┤ │ 1. 空间魔法: 小物理 → 大虚拟地址空间 │ │ 2. 隔离魔法: 多进程互不干扰 │ │ 3. 共享魔法: 相同物理页多视角映射 │ │ 4. 保护魔法: 硬件级权限控制 │ │ 5. 简化魔法: 连续地址屏蔽物理复杂性 │ └─────────────────────────────────────────┘ ``` ```bash #!/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 } ```