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

771 lines
28 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 课前准备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 <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` 立即达到 3GB`RSS` 保持很小
2. **稀疏访问**:证明大地址空间 + 小物理内存 = 高效
3. **密集访问**:观察 `RSS` 增长 → `VmSwap` 增长 → 可能的 OOM
**配合监控**(另一个终端):
```bash
watch -n 0.3 'cat /proc/<PID>/status | grep -E "VmSize|VmRSS|VmSwap"'
```
---
## 演示环节二进程隔离与内存保护8分钟
### 核心程序(`demo2_isolation.c`
```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写时复制**
```c
// 添加这段代码观察 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`
```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;
}
```
**编译需要**
```bash
gcc -o demo3_sharing demo3_sharing.c -lrt
```
---
## 演示环节四内存保护5分钟
### 核心程序(`demo4_protection.c`
```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`
```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成功且几乎不占用物理内存
>
> **验证**:运行代码,观察 `VmSize` 和 `RSS`
### 实时对比(双终端投影)
**左屏**:运行程序,显示输出
**右屏**:持续运行监控命令
```bash
# 推荐监控组合
watch -n 0.5 'clear; echo "=== 进程 ==="; cat /proc/<PID>/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
}
```