Linux 学习笔记(十五):进程的魔法 fork 与 僵尸孤儿

一、 fork 函数的奇迹

在 Linux 中,通过 fork() 系统调用创建子进程。它最神奇的地方在于**"一次调用,两次返回"**:

  • 在父进程中,返回子进程的 PID(>0)。

  • 在子进程中,返回 0。

  • 失败返回 <0。

    因此在代码中,我们通常用 if(ret == 0) { // 子进程 } else { // 父进程 } 来分流。

二、 虚拟地址与写时拷贝

在实战中,我发现一个诡异现象:父子进程打印同一个全局变量的地址是一模一样的,但值却不同。

这说明我们在 C 语言中打印的地址全都是虚拟地址 。操作系统通过页表将虚拟地址映射到物理地址。父子进程初始共享物理内存,当子进程尝试修改数据时,系统会触发"写时拷贝"(Copy-on-Write)机制,为子进程分配新的物理内存,从而保证了进程的独立性。

三、 僵尸进程(Zombie)

  • 成因 :子进程退出,但父进程未使用 wait() 或 waitpid() 读取其退出状态,导致子进程的 PCB 一直保留在内核中,状态变为 Z。

  • 危害:PCB 会占用内存空间,大量僵尸进程会导致内存泄漏,甚至拖垮系统。

  • 解决 :父进程应调用 wait() 回收,或者杀死父进程让僵尸进程变成孤儿被 init 接管。

四、 孤儿进程(Orphan)

  • 成因:父进程先退出了,而子进程还在运行。

  • 结局 :孤儿进程会被系统的 1号进程(init/systemd) 领养,由 1号进程负责它们的回收,因此孤儿进程没有危害。

自我实战操作

实战 1:体验 fork 的双返回值与数据私有
bash 复制代码
cd ~/linux_study/proc_test
vim fork_test.c

(输入以下代码,保存退出)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int g_val = 0; // 全局变量

int main() {
    pid_t id = fork(); // 创建子进程

    if (id < 0) {
        perror("fork error");
        return 1;
    } else if (id == 0) {
        // 只有子进程能进这里
        g_val = 100; // 子进程修改全局变量
        printf("我是子进程,PID: %d, 我爸爸是: %d, g_val: %d, 地址: %p\n", getpid(), getppid(), g_val, &g_val);
    } else {
        // 只有父进程能进这里
        sleep(1); // 等子进程先跑完
        printf("我是父进程,PID: %d, g_val: %d, 地址: %p\n", getpid(), g_val, &g_val);
    }

    return 0;
}

编译运行:

bash 复制代码
gcc fork_test.c -o fork_test
./fork_test

同一个变量 g_val,父子进程打印出来的内存地址一模一样(比如都是 0x...),但值却不一样(父进程是 0,子进程是 100)。

思考:这说明什么?说明 %p 打印出来的根本不是你电脑真实的物理内存地址,而是"虚拟地址"!

实战 2:亲手制造一个"僵尸进程"
bash 复制代码
vim zombie_test.c

(输入以下代码,保存退出)

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    pid_t id = fork();

    if (id == 0) {
        // 子进程只活 3 秒
        printf("我是子进程[%d],我要死了...\n", getpid());
        sleep(3);
        exit(0);
    } else {
        // 父进程活 30 秒,且一直不调用 wait() 回收子进程
        printf("我是父进程[%d],我假装没看见子进程死掉...\n", getpid());
        sleep(30);
    }
    return 0;
}

操作步骤:

  1. 编译运行:gcc zombie_test.c -o zombie_test && ./zombie_test

  2. 马上新开一个终端,敲下面这个命令监控:

bash 复制代码
while true; do ps aux | grep zombie_test | grep -v grep; sleep 1; echo "---"; done

你会看到,3秒钟后,子进程的状态会变成 Z (或者 Z+),这就叫僵尸进程!它会一直在那里挂着,直到 30 秒后父进程结束,它才被系统接管回收。

相关推荐
FlightYe10 分钟前
音视频修炼之容器与协议(四):RTSP详解
android·linux·ffmpeg·音视频·dash
刘马想放假1 小时前
CANviz 使用指南|Linux 安装、DBC 解码、实时曲线与 J1939 实战
linux·python·websocket
kloc2 小时前
聊天室项目入口网关子服务的实现
linux·c++
GeW3 小时前
深圳APEC峰会倒计时:数据库与Linux运维的"隐形考场"
linux
梦到啥说啥4 小时前
Linux网络编程:UDP网络协议
linux·网络协议·udp
无足鸟ICT5 小时前
【RHCA+】ipcrm命令(删除IPC资源)
linux
星恒随风6 小时前
MySQL 从零入门(四):表的增删改查与基础查询详解
linux·服务器·数据库·笔记·mysql
Z21-X7 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
陈希1239 小时前
Linux 网络诊断工具实战:ping、curl、tcpdump、mtr、dig 用法详解
linux·网络·tcpdump
进击的荆棘9 小时前
Linux系统——线程同步与互斥之线程池和安全
linux·运维·服务器·安全·单例模式·线程