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 秒后父进程结束,它才被系统接管回收。

相关推荐
钝挫力PROGRAMER1 小时前
从“硬件报错”到“内核崩溃”:一次超微(Supermicro)服务器失联问题解决记录
服务器·内核升级·无法连接
manageengine1 小时前
PHP2Shell 事件复盘:企业应该如何自查、修复与规模化处置
运维·安全·php
第404号工位1 小时前
插上充电宝真能偷光相册?我拆解了一台改装充电宝,实测“恶意充电”的真实攻击链
linux·python·架构
Initialize293081 小时前
Kafka 消息越堆越多、消费越来越慢?五步查清消费积压
运维
天南散修1 小时前
《802.11-2024》第06讲 · 介质接入:MAC 架构与 DCF(CSMA/CA)
linux·网络·wifi·802.11
cpolar技术支持2 小时前
磁盘明明没满,服务却写不进去?做个 inode 与分区占用只读报表页,cpolar 给异地同事看
linux·内网穿透·cpolar·inode·运维排障
时间售卖铺2 小时前
MySQL 表的约束
运维·数据库·mysql·云原生
广州工博科技2 小时前
SAP Joule for Consultants 如何为自动化与集成项目提供支持?
运维·自动化
Lsetea2 小时前
curl访问HTTPS返回304却没有正文:条件请求、缓存与证书验收排查
linux·http·https·ssl证书·curl