欢迎光临
我们一直在努力

Linux 系统编程 · 第 9 章:进程创建

Linux 系统编程 · 第 9 章:进程创建

本章深入讲解 Linux 进程创建的完整体系:fork 的写时复制机制、vfork 的特殊语义、exec 族函数的程序替换原理,以及 fork+exec 组合模式、进程间的资源继承与关闭规则。


目录

  • fork 深度解析

  • 写时复制(Copy-On-Write)

  • vfork — 轻量级进程创建

  • exec 族函数

  • fork + exec 组合模式

  • 进程资源的继承与关闭

  • clone — 底层进程/线程创建

  • 综合实践


  • 1. fork 深度解析

    1.1 fork 的完整语义

     #include <unistd.h>
     #include <sys/types.h>
     ​
     /*
      * fork:创建当前进程的副本(子进程)
      *
      * 返回值:
      *   父进程中:返回子进程的 PID(> 0)
      *   子进程中:返回 0
      *   失败:返回 -1,errno 设置为:
      *     EAGAIN 进程数超过限制(RLIMIT_NPROC 或系统限制)
      *     ENOMEM 内存不足,无法复制页表
      *
      * fork 后子进程继承的内容:
      *   ✓ 代码段(共享,只读)
      *   ✓ 数据段、BSS、堆、栈(写时复制)
      *   ✓ 文件描述符表(共享打开文件表项)
      *   ✓ 信号处理函数(disposition)
      *   ✓ 进程组 ID、会话 ID
      *   ✓ 当前工作目录、根目录
      *   ✓ 文件创建掩码(umask)
      *   ✓ 环境变量
      *   ✓ 资源限制(rlimit)
      *   ✓ nice 值
      *
      * fork 后子进程不继承的内容:
      *   ✗ 父进程的 PID(子进程有自己的 PID)
      *   ✗ 父进程的内存锁(mlock)
      *   ✗ 待处理信号(pending signals 清空)
      *   ✗ 文件记录锁(fcntl 锁)
      *   ✗ 定时器(alarm、setitimer)
      *   ✗ 异步 I/O 操作
      *   ✗ 线程(fork 只复制调用线程)
      */
     pid_t fork(void);

    1.2 fork 的执行流程

     /* 文件名:fork_deep.c
      * 深入演示 fork 的执行流程和各种细节
      */
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <unistd.h>
     #include <fcntl.h>
     #include <errno.h>
     #include <sys/types.h>
     #include <sys/wait.h>
     #include <sys/resource.h>
     #include <signal.h>
     #include <time.h>
     ​
     /* ── 演示1:fork 后的执行顺序不确定 ── */
     void demo_exec_order(void) {
         printf("=== 演示1:fork 后执行顺序不确定 ===\\n\\n");
         /*
          * fork 后父子进程的执行顺序由调度器决定
          * 不能假设父进程或子进程先执行
          * 需要用 wait/信号/管道等同步机制
          */
         for (int i = 0; i < 3; i++) {
             pid_t pid = fork();
             if (pid == 0) {
                 printf(" 子进程 %d(PID=%d)执行\\n", i, getpid());
                 _exit(0);
            }
        }
         /* 等待所有子进程 */
         int status;
         while (wait(&status) > 0);
         printf(" (父子进程执行顺序由调度器决定)\\n\\n");
     }
     ​
     /* ── 演示2:fork 与信号处理 ── */
     static volatile int g_signal_count = 0;
     void sig_usr1(int sig) { (void)sig; g_signal_count++; }
     ​
     void demo_fork_signals(void) {
         printf("=== 演示2:fork 与信号处理 ===\\n\\n");
     ​
         /* 父进程注册信号处理函数 */
         signal(SIGUSR1, sig_usr1);
     ​
         /* 向自己发送信号(pending)*/
         raise(SIGUSR1);
         raise(SIGUSR1);
         printf(" fork 前 pending SIGUSR1 数: %d\\n", g_signal_count);
     ​
         pid_t pid = fork();
         if (pid == 0) {
             /*
              * 子进程继承信号处理函数(disposition)
              * 但不继承待处理信号(pending signals 清空)
              */
             printf(" 子进程继承信号处理函数: %s\\n",
                    signal(SIGUSR1, sig_usr1) != SIG_DFL ? "✓" : "✗");
             printf(" 子进程 pending 信号已清空: g_signal_count=%d\\n",
                    g_signal_count);
             _exit(0);
        }
         waitpid(pid, NULL, 0);
         printf(" 父进程 g_signal_count=%d(未受影响)\\n\\n", g_signal_count);
     }
     ​
     /* ── 演示3:fork 与定时器 ── */
     void demo_fork_timer(void) {
         printf("=== 演示3:fork 不继承定时器 ===\\n\\n");
     ​
         /* 设置 2 秒后的 alarm */
         alarm(2);
         printf(" 父进程设置 alarm(2)\\n");
         printf(" 剩余时间: %u 秒\\n", alarm(0));   /* 查询并取消 */
         alarm(2);   /* 重新设置 */
     ​
         pid_t pid = fork();
         if (pid == 0) {
             /* 子进程不继承定时器 */
             unsigned remaining = alarm(0);   /* 查询子进程的 alarm */
             printf(" 子进程的 alarm 剩余: %u 秒(不继承父进程定时器)\\n",
                    remaining);
             _exit(0);
        }
         waitpid(pid, NULL, 0);
         alarm(0);   /* 取消父进程的 alarm */
         printf(" 父进程取消 alarm\\n\\n");
     }
     ​
     /* ── 演示4:fork 与文件描述符 ── */
     void demo_fork_fd(void) {
         printf("=== 演示4:fork 与文件描述符共享 ===\\n\\n");
     ​
         const char *path = "/tmp/fork_fd_demo.txt";
         int fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
     ​
         /* 写入初始内容 */
         write(fd, "ABCDEFGHIJ", 10);
         lseek(fd, 0, SEEK_SET);   /* 回到开头 */
     ​
         printf(" fork 前文件偏移: %ld\\n", (long)lseek(fd, 0, SEEK_CUR));
     ​
         pid_t pid = fork();
         if (pid == 0) {
             /* 子进程:读取3字节(移动共享偏移量)*/
             char buf[4] = {0};
             read(fd, buf, 3);
             printf(" 子进程读取3字节: [%s],偏移变为: %ld\\n",
                    buf, (long)lseek(fd, 0, SEEK_CUR));
             close(fd);
             _exit(0);
        }
         waitpid(pid, NULL, 0);
     ​
         /* 父进程:偏移量已被子进程移动 */
         printf(" 子进程退出后,父进程文件偏移: %ld(被子进程修改!)\\n",
                (long)lseek(fd, 0, SEEK_CUR));
     ​
         char buf[8] = {0};
         read(fd, buf, 7);
         printf(" 父进程继续读取: [%s]\\n\\n", buf);
     ​
         close(fd);
         unlink(path);
     }
     ​
     /* ── 演示5:fork 失败处理 ── */
     void demo_fork_failure(void) {
         printf("=== 演示5:fork 失败处理 ===\\n\\n");
     ​
         /* 临时降低进程数限制来模拟 fork 失败 */
         struct rlimit rl, old_rl;
         getrlimit(RLIMIT_NPROC, &old_rl);
     ​
         /* 设置为当前进程数(不允许再创建新进程)*/
         rl.rlim_cur = 1;
         rl.rlim_max = old_rl.rlim_max;
         setrlimit(RLIMIT_NPROC, &rl);
     ​
         pid_t pid = fork();
         if (pid == -1) {
             printf(" fork 失败(预期): errno=%d (%s)\\n",
                    errno, strerror(errno));
        } else if (pid == 0) {
             _exit(0);
        } else {
             waitpid(pid, NULL, 0);
        }
     ​
         /* 恢复限制 */
         setrlimit(RLIMIT_NPROC, &old_rl);
         printf(" 进程数限制已恢复\\n\\n");
     }
     ​
     int main(void) {
         demo_exec_order();
         demo_fork_signals();
         demo_fork_timer();
         demo_fork_fd();
         demo_fork_failure();
         return 0;
     }
     gcc -o fork_deep fork_deep.c
     ./fork_deep
     # 输出示例:
     # === 演示1:fork 后执行顺序不确定 ===
     #   子进程 0(PID=12346)执行
     #   子进程 2(PID=12348)执行
     #   子进程 1(PID=12347)执行
     #   (父子进程执行顺序由调度器决定)
     #
     # === 演示2:fork 与信号处理 ===
     #   fork 前 pending SIGUSR1 数: 2
     #   子进程继承信号处理函数: ✓
     #   子进程 pending 信号已清空: g_signal_count=0
     #   父进程 g_signal_count=2(未受影响)

    1.3 多进程 fork 模式

     /* 文件名:fork_patterns.c
      * 演示常见的多进程 fork 模式
      */
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <unistd.h>
     #include <sys/types.h>
     #include <sys/wait.h>
     #include <errno.h>
     ​
     /* ── 模式1:顺序 fork(串行创建子进程)── */
     void pattern_sequential(int n) {
         printf("【模式1:顺序 fork(父进程创建 %d 个子进程)】\\n", n);
         /*
          * 父进程依次创建 n 个子进程
          * 每个子进程都是父进程的直接子进程
          * 子进程之间是兄弟关系
          */
         pid_t pids[16];
         for (int i = 0; i < n; i++) {
             pids[i] = fork();
             if (pids[i] == -1) {
                 perror("fork");
                 break;
            }
             if (pids[i] == 0) {
                 /* 子进程 */
                 printf(" 子进程 %d: PID=%d, PPID=%d\\n",
                        i, getpid(), getppid());
                 usleep(100000 * (i + 1));
                 _exit(i);
            }
             /* 父进程继续循环 */
        }
     ​
         /* 父进程等待所有子进程 */
         for (int i = 0; i < n; i++) {
             if (pids[i] > 0) {
                 int status;
                 waitpid(pids[i], &status, 0);
                 printf(" 子进程 %d(PID=%d)退出码: %d\\n",
                        i, pids[i], WEXITSTATUS(status));
            }
        }
         printf("\\n");
     }
     ​
     /* ── 模式2:链式 fork(进程链)── */
     void pattern_chain(int depth) {
         printf("【模式2:链式 fork(进程链,深度=%d)】\\n", depth);
         /*
          * 每个进程 fork 一个子进程,形成链式结构
          * 进程0 → 进程1 → 进程2 → … → 进程n
          * 每个进程的 PPID 是上一个进程的 PID
          */
         int level = 0;
         while (level < depth) {
             pid_t pid = fork();
             if (pid == -1) { perror("fork"); break; }
             if (pid == 0) {
                 /* 子进程继续链 */
                 level++;
                 if (level == depth) {
                     /* 链的末端 */
                     printf(" 链末端: 层级=%d, PID=%d, PPID=%d\\n",
                            level, getpid(), getppid());
                     _exit(0);
                }
                 /* 继续循环创建下一级 */
            } else {
                 /* 父进程等待子进程 */
                 waitpid(pid, NULL, 0);
                 printf(" 层级=%d, PID=%d 完成\\n", level, getpid());
                 break;
            }
        }
         if (level == 0) printf("\\n");
     }
     ​
     /* ── 模式3:双重 fork(防止僵尸进程)── */
     void pattern_double_fork(void) {
         printf("【模式3:双重 fork(防止僵尸进程)】\\n");
         /*
          * 双重 fork 技巧:
          * 父进程 fork 出子进程1
          * 子进程1 再 fork 出子进程2(孙进程)
          * 子进程1 立即退出
          * 孙进程被 init 收养,父进程无需等待孙进程
          * 孙进程退出时由 init 回收,不会变成僵尸
          */
         pid_t child1 = fork();
         if (child1 == -1) { perror("fork"); return; }
     ​
         if (child1 == 0) {
             /* 子进程1:立即再 fork */
             pid_t child2 = fork();
             if (child2 == -1) { perror("fork"); _exit(1); }
     ​
             if (child2 == 0) {
                 /* 孙进程:被 init 收养,执行实际工作 */
                 printf(" 孙进程 PID=%d, PPID=%d(将被 init 收养)\\n",
                        getpid(), getppid());
                 usleep(100000);
                 printf(" 孙进程 PID=%d, PPID=%d(已被 init 收养)\\n",
                        getpid(), getppid());
                 _exit(0);
            }
     ​
             /* 子进程1:立即退出(孙进程变为孤儿,被 init 收养)*/
             printf(" 子进程1 PID=%d 立即退出\\n", getpid());
             _exit(0);
        }
     ​
         /* 父进程:只需等待子进程1(很快退出)*/
         int status;
         waitpid(child1, &status, 0);
         printf(" 父进程:子进程1已退出,无需等待孙进程\\n");
         usleep(200000);   /* 等孙进程输出 */
         printf("\\n");
     }
     ​
     /* ── 模式4:fork 后立即 exec(最常见模式)── */
     void pattern_fork_exec(void) {
         printf("【模式4:fork + exec(执行外部命令)】\\n");
     ​
         pid_t pid = fork();
         if (pid == -1) { perror("fork"); return; }
     ​
         if (pid == 0) {
             /* 子进程:执行外部命令 */
             execlp("echo", "echo", "Hello from exec!", NULL);
             /* exec 失败才会到这里 */
             perror("execlp");
             _exit(127);
        }
     ​
         int status;
         waitpid(pid, &status, 0);
         printf(" 外部命令退出码: %d\\n\\n", WEXITSTATUS(status));
     }
     ​
     int main(void) {
         pattern_sequential(3);
         pattern_chain(3);
         pattern_double_fork();
         pattern_fork_exec();
         return 0;
     }
     gcc -o fork_patterns fork_patterns.c
     ./fork_patterns
     # 输出示例:
     # 【模式1:顺序 fork(父进程创建 3 个子进程)】
     #   子进程 0: PID=12346, PPID=12345
     #   子进程 1: PID=12347, PPID=12345
     #   子进程 2: PID=12348, PPID=12345
     #   子进程 0(PID=12346)退出码: 0
     #   子进程 1(PID=12347)退出码: 1
     #   子进程 2(PID=12348)退出码: 2


    2. 写时复制(Copy-On-Write)

    2.1 COW 原理

     写时复制(Copy-On-Write,COW)机制:
     ─────────────────────────────────────────────────────────────
      fork 后的内存状态:
     ​
      父进程页表                   子进程页表
      ┌──────────────────┐         ┌──────────────────┐
      │ 虚拟页A → 物理页1│         │ 虚拟页A → 物理页1│ ← 共享!
      │ 虚拟页B → 物理页2│         │ 虚拟页B → 物理页2│ ← 共享!
      │ 虚拟页C → 物理页3│         │ 虚拟页C → 物理页3│ ← 共享!
      └──────────────────┘         └──────────────────┘
      所有页标记为只读(即使原来可写)
     ​
      当子进程写入虚拟页A时:
      1. 触发写保护异常(Page Fault)
      2. 内核分配新的物理页4
      3. 将物理页1的内容复制到物理页4
      4. 更新子进程页表:虚拟页A → 物理页4
      5. 恢复物理页1的写权限(父进程可写)
      6. 子进程继续执行写操作
     ​
      结果:
      父进程页表                   子进程页表
      ┌──────────────────┐         ┌──────────────────┐
      │ 虚拟页A → 物理页1│(可写) │ 虚拟页A → 物理页4│(新页)
      │ 虚拟页B → 物理页2│(共享) │ 虚拟页B → 物理页2│(共享)
      │ 虚拟页C → 物理页3│(共享) │ 虚拟页C → 物理页3│(共享)
      └──────────────────┘         └──────────────────┘
     ​
      优势:
      ✓ fork 速度极快(只复制页表,不复制物理内存)
      ✓ 如果子进程立即 exec,几乎不需要复制任何内存
      ✓ 只读数据(代码段、常量)永远共享,节省内存
     ─────────────────────────────────────────────────────────────
     /* 文件名:cow_demo.c
      * 演示写时复制的行为和性能影响
      */
     #include <stdio.h>
     #include <stdlib.h>
     #include <string.h>
     #include <unistd.h>
     #include <sys/types.h>
     #include <sys/wait.h>
     #include <sys/mman.h>
     #include <time.h>
     ​
     static long long now_ns(void) {
         struct timespec ts;
         clock_gettime(CLOCK_MONOTONIC, &ts);
         return ts.tv_sec * 1000000000LL + ts.tv_nsec;
     }
     ​
     /* 读取进程的物理内存使用量(RSS)*/
     long get_rss_kb(void) {
         FILE *fp = fopen("/proc/self/status", "r");
         if (!fp) return -1;
         char line[128];
         long rss = 0;
         while (fgets(line, sizeof(line), fp)) {
             if (strncmp(line, "VmRSS:", 6) == 0) {
                 sscanf(line + 6, " %ld", &rss);
                 break;
            }
        }
         fclose(fp);
         return rss;
     }
     ​
     int main(void) {
         const size_t MEM_SIZE = 64 * 1024 * 1024;   /* 64MB */
     ​
         printf("=== 写时复制(COW)演示 ===\\n\\n");
     ​
         /* 分配 64MB 内存并写入数据 */
         char *data = malloc(MEM_SIZE);
         memset(data, 0xAB, MEM_SIZE);
         printf("父进程分配并写入 64MB 内存\\n");
         printf("父进程 RSS: %ld KB\\n\\n", get_rss_kb());
     ​
         /* ── 场景1:子进程只读(COW 不触发)── */
         printf("【场景1:子进程只读(COW 不触发)】\\n");
         long long t1 = now_ns();
         pid_t pid = fork();
         long long t2 = now_ns();
     ​
         if (pid == 0) {
             /* 子进程:只读数据,不触发 COW */
             long sum = 0;
             for (size_t i = 0; i < MEM_SIZE; i += 4096)
                 sum += data[i];   /* 只读 */
             printf(" 子进程只读,RSS: %ld KB(与父进程共享物理页)\\n",
                    get_rss_kb());
            (void)sum;
             _exit(0);
        }
         printf(" fork 耗时: %lld μs(只复制页表)\\n",
                (t2 – t1) / 1000);
         waitpid(pid, NULL, 0);
     ​
         /* ── 场景2:子进程写入(触发 COW)── */
         printf("\\n【场景2:子进程写入(触发 COW)】\\n");
         t1 = now_ns();
         pid = fork();
         t2 = now_ns();
     ​
         if (pid == 0) {
             printf(" 子进程 fork 后 RSS: %ld KB\\n", get_rss_kb());
     ​
             /* 写入所有页,触发 COW */
             long long write_start = now_ns();
             memset(data, 0xCD, MEM_SIZE);   /* 触发 COW,复制所有页 */
             long long write_end = now_ns();
     ​
             printf(" 子进程写入后 RSS: %ld KB(COW 触发,复制了物理页)\\n",
                    get_rss_kb());
             printf(" COW 写入耗时: %lld ms\\n",
                    (write_end – write_start) / 1000000);
             _exit(0);
        }
         printf(" fork 耗时: %lld μs\\n", (t2 – t1) / 1000);
         waitpid(pid, NULL, 0);
     ​
         /* ── 场景3:验证 COW 的独立性 ── */
         printf("\\n【场景3:验证 COW 独立性】\\n");
         int shared_val = 42;
         printf(" fork 前 shared_val = %d,地址 = %p\\n",
                shared_val, (void *)&shared_val);
     ​
         pid = fork();
         if (pid == 0) {
             printf(" 子进程看到 shared_val = %d,地址 = %p(相同虚拟地址)\\n",
                    shared_val, (void *)&shared_val);
             shared_val = 999;   /* 触发 COW */
             printf(" 子进程修改后 shared_val = %d,地址 = %p(新物理页)\\n",
                    shared_val, (void *)&shared_val);
             _exit(0);
        }
         waitpid(pid, NULL, 0);
         printf(" 父进程的 shared_val = %d(未受子进程修改影响)\\n\\n",
                shared_val);
     ​
         free(data);
         return 0;
     }
     gcc -O2 -o cow_demo cow_demo.c
     ./cow_demo
     # 输出示例:
     # === 写时复制(COW)演示 ===
     #
     # 父进程分配并写入 64MB 内存
     # 父进程 RSS: 65536 KB
     #
     # 【场景1:子进程只读(COW 不触发)】
     #   fork 耗时: 234 μs(只复制页表)
     #   子进程只读,RSS: 65540 KB(与父进程共享物理页)
     #
     # 【场景2:子进程写入(触发 COW)】
     #   fork 耗时: 198 μs
     #   子进程 fork 后 RSS: 65540 KB
     #   子进程写入后 RSS: 131072 KB(COW 触发,复制了物理页)
     #   COW 写入耗时: 45 ms
     #
     # 【场景3:验证 COW 独立性】
     #   fork 前 shared_val = 42,地址 = 0x7ffd…
     #   子进程看到 shared_val = 42,地址 = 0x7ffd…(相同虚拟地址)
     #   子进程修改后 shared_val = 999,地址 = 0x7ffd…(新物理页)
     #   父进程的 shared_val = 42(未受子进程修改影响)


    3. vfork — 轻量级进程创建

    3.1 vfork 的特殊语义

    /* 文件名:vfork_demo.c
    * 演示 vfork 的特殊语义和使用限制
    *
    * ⚠️ 注意:vfork 在现代 Linux 中已基本废弃
    * 现代 fork + COW 已足够高效
    * 本节仅用于理解历史背景和底层机制
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    int g_value = 10;

    int main(void) {
    printf("=== vfork vs fork 对比 ===\\n\\n");

    printf("【vfork 的特殊语义】\\n");
    printf(" 1. 子进程与父进程共享地址空间(不复制页表!)\\n");
    printf(" 2. 父进程被挂起,直到子进程调用 exec 或 _exit\\n");
    printf(" 3. 子进程必须立即 exec 或 _exit,不能做其他操作\\n");
    printf(" 4. 子进程修改变量会影响父进程(共享地址空间)\\n\\n");

    printf("【vfork 演示】\\n");
    printf(" vfork 前: g_value = %d\\n", g_value);

    pid_t pid = vfork();

    if (pid == 0) {
    /*
    * 子进程:与父进程共享地址空间
    * 修改变量会影响父进程!
    * 必须立即 exec 或 _exit
    */
    g_value = 999; /* 修改共享地址空间中的变量 */
    printf(" 子进程修改 g_value = %d\\n", g_value);

    /*
    * ⚠️ 必须用 _exit,不能用 exit!
    * exit 会调用 atexit 函数和刷新 stdio 缓冲区
    * 这会破坏父进程的 stdio 状态
    */
    _exit(0);
    }

    /*
    * 父进程:子进程调用 _exit 后才继续执行
    * 由于共享地址空间,g_value 已被子进程修改
    */
    printf(" 父进程(子进程退出后): g_value = %d(被子进程修改!)\\n\\n",
    g_value);

    /* ── vfork 的正确用法:立即 exec ── */
    printf("【vfork 的正确用法:立即 exec】\\n");
    pid = vfork();
    if (pid == 0) {
    /* 正确:立即执行 exec */
    execlp("echo", "echo", "vfork + exec 正确用法", NULL);
    _exit(127); /* exec 失败 */
    }
    int status;
    waitpid(pid, &status, 0);

    /* ── vfork vs fork 性能对比 ── */
    printf("\\n【vfork vs fork 性能对比(各 10000 次)】\\n");

    struct timespec t1, t2;
    int ROUNDS = 10000;

    /* fork 性能 */
    clock_gettime(CLOCK_MONOTONIC, &t1);
    for (int i = 0; i < ROUNDS; i++) {
    pid_t p = fork();
    if (p == 0) _exit(0);
    waitpid(p, NULL, 0);
    }
    clock_gettime(CLOCK_MONOTONIC, &t2);
    long long fork_ns = (t2.tv_sec – t1.tv_sec) * 1000000000LL
    + (t2.tv_nsec – t1.tv_nsec);
    printf(" fork: %lld μs/次\\n", fork_ns / ROUNDS / 1000);

    /* vfork 性能 */
    clock_gettime(CLOCK_MONOTONIC, &t1);
    for (int i = 0; i < ROUNDS; i++) {
    pid_t p = vfork();
    if (p == 0) _exit(0);
    waitpid(p, NULL, 0);
    }
    clock_gettime(CLOCK_MONOTONIC, &t2);
    long long vfork_ns = (t2.tv_sec – t1.tv_sec) * 1000000000LL
    + (t2.tv_nsec – t1.tv_nsec);
    printf(" vfork: %lld μs/次\\n", vfork_ns / ROUNDS / 1000);
    printf(" vfork 比 fork 快约 %.1f 倍\\n",
    (double)fork_ns / vfork_ns);

    return 0;
    }
    gcc -o vfork_demo vfork_demo.c
    ./vfork_demo
    # 输出示例:
    # === vfork vs fork 对比 ===
    #
    # 【vfork 演示】
    # vfork 前: g_value = 10
    # 子进程修改 g_value = 999
    # 父进程(子进程退出后): g_value = 999(被子进程修改!)
    #
    # 【vfork vs fork 性能对比(各 10000 次)】
    # fork: 120 μs/次
    # vfork: 45 μs/次
    # vfork 比 fork 快约 2.7 倍


    4. exec 族函数

    4.1 exec 族函数总览

    #include <unistd.h>

    /*
    * exec 族函数:用新程序替换当前进程的映像
    *
    * exec 不创建新进程,而是:
    * 1. 加载新程序的代码段、数据段
    * 2. 重置堆、栈
    * 3. 保留 PID、PPID、打开的 fd(除非设置了 FD_CLOEXEC)
    * 4. 从新程序的 main 函数开始执行
    *
    * 函数命名规律:
    * l = list(参数列表,以 NULL 结尾)
    * v = vector(参数数组 char *argv[])
    * p = path(在 PATH 中搜索程序)
    * e = environment(自定义环境变量)
    */

    /* 参数列表 + 不搜索 PATH */
    int execl(const char *path, const char *arg, … /*, NULL */);

    /* 参数列表 + 搜索 PATH */
    int execlp(const char *file, const char *arg, … /*, NULL */);

    /* 参数列表 + 自定义环境变量 */
    int execle(const char *path, const char *arg, … /*, NULL, char *const envp[] */);

    /* 参数数组 + 不搜索 PATH */
    int execv(const char *path, char *const argv[]);

    /* 参数数组 + 搜索 PATH */
    int execvp(const char *file, char *const argv[]);

    /* 参数数组 + 自定义环境变量 */
    int execve(const char *path, char *const argv[], char *const envp[]);

    /* 参数数组 + fd 指定程序(Linux 特有,安全)*/
    int execveat(int dirfd, const char *pathname,
    char *const argv[], char *const envp[], int flags);

    /*
    * 所有 exec 函数:
    * 成功:不返回(进程映像已被替换)
    * 失败:返回 -1,设置 errno
    */

    4.2 exec 族函数详细演示

    /* 文件名:exec_family.c
    * 演示 exec 族函数的各种用法
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <errno.h>

    /* 辅助:fork 后在子进程中执行,父进程等待 */
    #define RUN_IN_CHILD(code) do { \\
    pid_t _pid = fork(); \\
    if (_pid == 0) { code; _exit(127); } \\
    int _st; waitpid(_pid, &_st, 0); \\
    printf(" 退出码: %d\\n", WEXITSTATUS(_st)); \\
    } while(0)

    int main(void) {
    printf("=== exec 族函数演示 ===\\n\\n");

    /* ── execl:参数列表,绝对路径 ── */
    printf("【execl:参数列表 + 绝对路径】\\n");
    RUN_IN_CHILD({
    printf(" 执行: execl(\\"/bin/echo\\", …)\\n");
    fflush(stdout);
    execl("/bin/echo", "echo", "Hello from execl!", NULL);
    perror("execl");
    });

    /* ── execlp:参数列表,搜索 PATH ── */
    printf("\\n【execlp:参数列表 + 搜索 PATH】\\n");
    RUN_IN_CHILD({
    printf(" 执行: execlp(\\"echo\\", …)\\n");
    fflush(stdout);
    execlp("echo", "echo", "Hello from execlp!", NULL);
    perror("execlp");
    });

    /* ── execv:参数数组,绝对路径 ── */
    printf("\\n【execv:参数数组 + 绝对路径】\\n");
    RUN_IN_CHILD({
    char *args[] = { "ls", "-la", "/tmp", NULL };
    printf(" 执行: execv(\\"/bin/ls\\", args)\\n");
    fflush(stdout);
    execv("/bin/ls", args);
    perror("execv");
    });

    /* ── execvp:参数数组,搜索 PATH ── */
    printf("\\n【execvp:参数数组 + 搜索 PATH】\\n");
    RUN_IN_CHILD({
    char *args[] = { "uname", "-r", NULL };
    printf(" 执行: execvp(\\"uname\\", args)\\n");
    fflush(stdout);
    execvp("uname", args);
    perror("execvp");
    });

    /* ── execle:参数列表 + 自定义环境变量 ── */
    printf("\\n【execle:参数列表 + 自定义环境变量】\\n");
    RUN_IN_CHILD({
    char *envp[] = {
    "MY_VAR=hello_from_execle",
    "PATH=/usr/bin:/bin",
    NULL
    };
    printf(" 执行: execle(\\"/usr/bin/env\\", …)\\n");
    fflush(stdout);
    execle("/usr/bin/env", "env", NULL, envp);
    perror("execle");
    });

    /* ── execve:最底层,参数数组 + 自定义环境变量 ── */
    printf("\\n【execve:底层接口(其他 exec 函数的基础)】\\n");
    RUN_IN_CHILD({
    char *args[] = { "printenv", "EXEC_TEST_VAR", NULL };
    char *envp[] = {
    "EXEC_TEST_VAR=execve_value",
    "PATH=/usr/bin:/bin",
    NULL
    };
    printf(" 执行: execve(\\"/usr/bin/printenv\\", args, envp)\\n");
    fflush(stdout);
    execve("/usr/bin/printenv", args, envp);
    perror("execve");
    });

    /* ── exec 失败的情况 ── */
    printf("\\n【exec 失败情况】\\n");
    RUN_IN_CHILD({
    /* 文件不存在 */
    execl("/nonexistent/program", "program", NULL);
    printf(" execl 失败: errno=%d (%s)\\n",
    errno, strerror(errno));
    _exit(1);
    });

    RUN_IN_CHILD({
    /* 没有执行权限 */
    execl("/etc/passwd", "passwd", NULL);
    printf(" execl 失败: errno=%d (%s)\\n",
    errno, strerror(errno));
    _exit(1);
    });

    /* ── exec 后保留的属性 ── */
    printf("\\n【exec 后保留的进程属性】\\n");
    printf(" ✓ PID、PPID、PGID、SID\\n");
    printf(" ✓ 打开的文件描述符(除非设置 FD_CLOEXEC)\\n");
    printf(" ✓ 当前工作目录\\n");
    printf(" ✓ 文件创建掩码(umask)\\n");
    printf(" ✓ 资源限制(rlimit)\\n");
    printf(" ✓ 进程组、会话\\n");
    printf(" ✗ 信号处理函数(重置为默认)\\n");
    printf(" ✗ 内存映射(mmap)\\n");
    printf(" ✗ 线程(只保留调用 exec 的线程)\\n");
    printf(" ✗ 内存锁(mlock)\\n");

    return 0;
    }
    gcc -o exec_family exec_family.c
    ./exec_family
    # 输出示例:
    # === exec 族函数演示 ===
    #
    # 【execl:参数列表 + 绝对路径】
    # 执行: execl("/bin/echo", …)
    # Hello from execl!
    # 退出码: 0
    #
    # 【execlp:参数列表 + 搜索 PATH】
    # 执行: execlp("echo", …)
    # Hello from execlp!
    # 退出码: 0
    #
    # 【execle:参数列表 + 自定义环境变量】
    # 执行: execle("/usr/bin/env", …)
    # MY_VAR=hello_from_execle
    # PATH=/usr/bin:/bin
    # 退出码: 0

    4.3 exec 的内部机制

    /* 文件名:exec_internals.c
    * 演示 exec 的内部机制:PID 保留、fd 继承、信号重置
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    /*
    * 这个程序被设计为既可以作为"父程序"运行
    * 也可以作为 exec 后的"子程序"运行
    * 通过环境变量 EXEC_ROLE 区分
    */
    int main(int argc, char *argv[]) {
    char *role = getenv("EXEC_ROLE");

    if (role && strcmp(role, "child") == 0) {
    /* ── 作为 exec 后的子程序运行 ── */
    printf("\\n [exec 后的子程序]\\n");
    printf(" PID = %d(与 exec 前相同)\\n", getpid());
    printf(" PPID = %d\\n", getppid());

    /* 检查继承的 fd */
    printf(" 继承的 fd:\\n");
    for (int fd = 0; fd <= 10; fd++) {
    if (fcntl(fd, F_GETFD) != -1) {
    char link[256] = {0};
    char path[64];
    snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
    readlink(path, link, sizeof(link) – 1);
    printf(" fd=%d → %s\\n", fd, link);
    }
    }

    /* 检查信号处理(exec 后应重置为默认)*/
    struct sigaction sa;
    sigaction(SIGUSR1, NULL, &sa);
    printf(" SIGUSR1 处理函数: %s(exec 后重置为默认)\\n",
    sa.sa_handler == SIG_DFL ? "SIG_DFL ✓" :
    sa.sa_handler == SIG_IGN ? "SIG_IGN" : "自定义");

    /* 检查环境变量 */
    printf(" EXEC_ROLE = %s\\n", getenv("EXEC_ROLE"));
    printf(" INHERITED_VAR = %s\\n",
    getenv("INHERITED_VAR") ? getenv("INHERITED_VAR") : "(未设置)");
    return 0;
    }

    /* ── 作为父程序运行 ── */
    printf("=== exec 内部机制演示 ===\\n\\n");

    /* 设置信号处理函数(exec 后会重置)*/
    signal(SIGUSR1, SIG_IGN); /* 忽略 SIGUSR1 */
    printf("设置 SIGUSR1 为 SIG_IGN\\n");

    /* 打开一个文件(测试 fd 继承)*/
    int fd3 = open("/etc/hostname", O_RDONLY);
    int fd4 = open("/etc/hostname", O_RDONLY | O_CLOEXEC);
    printf("打开 fd=%d(无 FD_CLOEXEC,exec 后继承)\\n", fd3);
    printf("打开 fd=%d(有 FD_CLOEXEC,exec 后关闭)\\n", fd4);

    /* 设置环境变量 */
    setenv("EXEC_ROLE", "child", 1);
    setenv("INHERITED_VAR", "from_parent", 1);

    printf("当前 PID = %d\\n", getpid());
    printf("\\nexecv 替换进程映像…\\n");
    fflush(stdout);

    /* exec 替换当前进程 */
    char *args[] = { argv[0], NULL };
    execv(argv[0], args);

    /* 只有 exec 失败才会到这里 */
    perror("execv");
    return 1;
    }
    gcc -o exec_internals exec_internals.c
    ./exec_internals
    # 输出示例:
    # === exec 内部机制演示 ===
    #
    # 设置 SIGUSR1 为 SIG_IGN
    # 打开 fd=3(无 FD_CLOEXEC,exec 后继承)
    # 打开 fd=4(有 FD_CLOEXEC,exec 后关闭)
    # 当前 PID = 12345
    #
    # execv 替换进程映像…
    #
    # [exec 后的子程序]
    # PID = 12345(与 exec 前相同)← PID 保留!
    # PPID = 12344
    # 继承的 fd:
    # fd=0 → /dev/pts/0
    # fd=1 → /dev/pts/0
    # fd=2 → /dev/pts/0
    # fd=3 → /etc/hostname ← 继承(无 FD_CLOEXEC)
    # (fd=4 已关闭,有 FD_CLOEXEC)
    # SIGUSR1 处理函数: SIG_DFL ✓(exec 后重置为默认)
    # INHERITED_VAR = from_parent ← 环境变量继承


    5. fork + exec 组合模式

    5.1 标准 shell 命令执行模型

    /* 文件名:fork_exec_shell.c
    * 实现一个简单的 shell 命令执行器
    * 演示 fork + exec 的标准组合模式
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    /* 执行命令并等待完成 */
    int run_command(char *const argv[], char *const envp[]) {
    pid_t pid = fork();
    if (pid == -1) {
    perror("fork");
    return -1;
    }

    if (pid == 0) {
    /* 子进程:执行命令 */

    /* 关闭不需要的 fd(安全实践)*/
    /* 实际 shell 会在这里设置重定向 */

    if (envp) {
    execvpe(argv[0], argv, envp);
    } else {
    execvp(argv[0], argv);
    }

    /* exec 失败 */
    fprintf(stderr, "exec '%s' 失败: %s\\n",
    argv[0], strerror(errno));
    _exit(127); /* 127 = 命令未找到(shell 约定)*/
    }

    /* 父进程:等待子进程完成 */
    int status;
    if (waitpid(pid, &status, 0) == -1) {
    perror("waitpid");
    return -1;
    }

    if (WIFEXITED(status)) return WEXITSTATUS(status);
    if (WIFSIGNALED(status)) return 128 + WTERMSIG(status);
    return -1;
    }

    /* 带输出重定向的命令执行 */
    int run_with_redirect(char *const argv[],
    const char *stdout_file,
    const char *stdin_file) {
    pid_t pid = fork();
    if (pid == -1) { perror("fork"); return -1; }

    if (pid == 0) {
    /* 重定向 stdout */
    if (stdout_file) {
    int fd = open(stdout_file,
    O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd == -1) { perror("open stdout"); _exit(1); }
    dup2(fd, STDOUT_FILENO);
    close(fd);
    }

    /* 重定向 stdin */
    if (stdin_file) {
    int fd = open(stdin_file, O_RDONLY);
    if (fd == -1) { perror("open stdin"); _exit(1); }
    dup2(fd, STDIN_FILENO);
    close(fd);
    }

    execvp(argv[0], argv);
    perror("execvp");
    _exit(127);
    }

    int status;
    waitpid(pid, &status, 0);
    return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
    }

    /* 管道连接两个命令(cmd1 | cmd2)*/
    int run_pipeline(char *const cmd1[], char *const cmd2[]) {
    int pipefd[2];
    if (pipe(pipefd) == -1) { perror("pipe"); return -1; }

    /* 创建 cmd1 进程 */
    pid_t pid1 = fork();
    if (pid1 == -1) { perror("fork cmd1"); return -1; }

    if (pid1 == 0) {
    /* cmd1:stdout → 管道写端 */
    close(pipefd[0]);
    dup2(pipefd[1], STDOUT_FILENO);
    close(pipefd[1]);
    execvp(cmd1[0], cmd1);
    perror("execvp cmd1");
    _exit(127);
    }

    /* 创建 cmd2 进程 */
    pid_t pid2 = fork();
    if (pid2 == -1) { perror("fork cmd2"); return -1; }

    if (pid2 == 0) {
    /* cmd2:stdin ← 管道读端 */
    close(pipefd[1]);
    dup2(pipefd[0], STDIN_FILENO);
    close(pipefd[0]);
    execvp(cmd2[0], cmd2);
    perror("execvp cmd2");
    _exit(127);
    }

    /* 父进程:关闭管道,等待两个子进程 */
    close(pipefd[0]);
    close(pipefd[1]);

    int status1, status2;
    waitpid(pid1, &status1, 0);
    waitpid(pid2, &status2, 0);

    return WIFEXITED(status2) ? WEXITSTATUS(status2) : -1;
    }

    int main(void) {
    printf("=== fork + exec 组合模式演示 ===\\n\\n");

    /* ── 基本命令执行 ── */
    printf("【基本命令执行】\\n");
    char *cmd1[] = { "echo", "Hello from fork+exec!", NULL };
    int ret = run_command(cmd1, NULL);
    printf("退出码: %d\\n\\n", ret);

    /* ── 带自定义环境变量 ── */
    printf("【自定义环境变量】\\n");
    char *cmd2[] = { "printenv", "CUSTOM_VAR", NULL };
    char *envp[] = { "CUSTOM_VAR=custom_value",
    "PATH=/usr/bin:/bin", NULL };
    ret = run_command(cmd2, envp);
    printf("退出码: %d\\n\\n", ret);

    /* ── 输出重定向 ── */
    printf("【输出重定向到文件】\\n");
    char *cmd3[] = { "ls", "/etc", NULL };
    ret = run_with_redirect(cmd3, "/tmp/ls_output.txt", NULL);
    printf("ls /etc 输出已重定向到 /tmp/ls_output.txt,退出码: %d\\n", ret);
    system("wc -l /tmp/ls_output.txt");
    unlink("/tmp/ls_output.txt");
    printf("\\n");

    /* ── 管道 ── */
    printf("【管道:ls /etc | grep conf】\\n");
    char *pipe_cmd1[] = { "ls", "/etc", NULL };
    char *pipe_cmd2[] = { "grep", "conf", NULL };
    ret = run_pipeline(pipe_cmd1, pipe_cmd2);
    printf("管道退出码: %d\\n\\n", ret);

    /* ── 命令不存在的处理 ── */
    printf("【命令不存在的处理】\\n");
    char *cmd4[] = { "nonexistent_command_xyz", NULL };
    ret = run_command(cmd4, NULL);
    printf("退出码: %d(127 = 命令未找到)\\n", ret);

    return 0;
    }
    gcc -o fork_exec_shell fork_exec_shell.c
    ./fork_exec_shell
    # 输出示例:
    # === fork + exec 组合模式演示 ===
    #
    # 【基本命令执行】
    # Hello from fork+exec!
    # 退出码: 0
    #
    # 【自定义环境变量】
    # custom_value
    # 退出码: 0
    #
    # 【管道:ls /etc | grep conf】
    # adduser.conf
    # ca-certificates.conf
    # …
    # 管道退出码: 0

    5.2 posix_spawn — fork+exec 的高效替代

    /* 文件名:posix_spawn_demo.c
    * 演示 posix_spawn:fork+exec 的高效替代
    * 在某些平台上比 fork+exec 更高效(避免 COW 开销)
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <spawn.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <fcntl.h>
    #include <time.h>

    extern char **environ;

    static long long now_ns(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000000LL + ts.tv_nsec;
    }

    int main(void) {
    printf("=== posix_spawn 演示 ===\\n\\n");

    /* ── 基本用法 ── */
    printf("【基本用法】\\n");
    pid_t pid;
    char *args[] = { "echo", "Hello from posix_spawn!", NULL };

    int ret = posix_spawn(&pid, "/bin/echo", NULL, NULL, args, environ);
    if (ret != 0) {
    fprintf(stderr, "posix_spawn 失败: %s\\n", strerror(ret));
    return 1;
    }
    printf("posix_spawn 创建进程 PID=%d\\n", pid);

    int status;
    waitpid(pid, &status, 0);
    printf("退出码: %d\\n\\n", WEXITSTATUS(status));

    /* ── 带文件操作(重定向)── */
    printf("【带文件操作(输出重定向)】\\n");

    posix_spawn_file_actions_t file_actions;
    posix_spawn_file_actions_init(&file_actions);

    /* 将 stdout 重定向到文件 */
    posix_spawn_file_actions_addopen(&file_actions,
    STDOUT_FILENO,
    "/tmp/spawn_output.txt",
    O_WRONLY | O_CREAT | O_TRUNC,
    0644);

    char *args2[] = { "ls", "/etc", NULL };
    ret = posix_spawn(&pid, "/bin/ls", &file_actions, NULL, args2, environ);
    if (ret == 0) {
    waitpid(pid, &status, 0);
    printf("ls /etc 输出已重定向,退出码: %d\\n", WEXITSTATUS(status));
    system("wc -l /tmp/spawn_output.txt");
    unlink("/tmp/spawn_output.txt");
    }
    posix_spawn_file_actions_destroy(&file_actions);

    /* ── 带属性设置 ── */
    printf("\\n【带属性设置(进程组)】\\n");

    posix_spawnattr_t attr;
    posix_spawnattr_init(&attr);

    /* 设置子进程为新进程组的领导 */
    posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP);
    posix_spawnattr_setpgroup(&attr, 0); /* 0 = 使用子进程 PID */

    char *args3[] = { "echo", "new process group", NULL };
    ret = posix_spawn(&pid, "/bin/echo", NULL, &attr, args3, environ);
    if (ret == 0) {
    waitpid(pid, &status, 0);
    printf("子进程 PID=%d 在新进程组中\\n", pid);
    }
    posix_spawnattr_destroy(&attr);

    /* ── 性能对比:posix_spawn vs fork+exec ── */
    printf("\\n【性能对比(各 1000 次)】\\n");
    int ROUNDS = 1000;
    long long t1, t2;

    /* fork+exec */
    t1 = now_ns();
    for (int i = 0; i < ROUNDS; i++) {
    pid_t p = fork();
    if (p == 0) {
    execl("/bin/true", "true", NULL);
    _exit(1);
    }
    waitpid(p, NULL, 0);
    }
    t2 = now_ns();
    printf(" fork+exec: %lld μs/次\\n",
    (t2 – t1) / ROUNDS / 1000);

    /* posix_spawn */
    char *true_args[] = { "true", NULL };
    t1 = now_ns();
    for (int i = 0; i < ROUNDS; i++) {
    posix_spawn(&pid, "/bin/true", NULL, NULL, true_args, environ);
    waitpid(pid, NULL, 0);
    }
    t2 = now_ns();
    printf(" posix_spawn: %lld μs/次\\n",
    (t2 – t1) / ROUNDS / 1000);

    return 0;
    }
    gcc -o posix_spawn_demo posix_spawn_demo.c
    ./posix_spawn_demo
    # 输出示例:
    # === posix_spawn 演示 ===
    #
    # 【基本用法】
    # Hello from posix_spawn!
    # posix_spawn 创建进程 PID=12346
    # 退出码: 0
    #
    # 【性能对比(各 1000 次)】
    # fork+exec: 245 μs/次
    # posix_spawn: 198 μs/次


    6. 进程资源的继承与关闭

    6.1 fork 后的资源管理

    /* 文件名:resource_inherit.c
    * 演示 fork/exec 后各种资源的继承规则
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/resource.h>
    #include <sys/mman.h>
    #include <pthread.h>

    /* ── 测试1:文件描述符继承 ── */
    void test_fd_inherit(void) {
    printf("=== 文件描述符继承规则 ===\\n\\n");

    /* 打开各种类型的 fd */
    int fd_normal = open("/etc/hostname", O_RDONLY);
    int fd_cloexec = open("/etc/hostname", O_RDONLY | O_CLOEXEC);
    int pipefd[2];
    pipe(pipefd);

    printf("fd_normal = %d(无 FD_CLOEXEC)\\n", fd_normal);
    printf("fd_cloexec = %d(有 FD_CLOEXEC)\\n", fd_cloexec);
    printf("pipe 读端 = %d,写端 = %d\\n\\n", pipefd[0], pipefd[1]);

    pid_t pid = fork();
    if (pid == 0) {
    printf("【fork 后子进程的 fd】\\n");
    printf(" fd_normal (%d): %s\\n", fd_normal,
    fcntl(fd_normal, F_GETFD) != -1 ? "继承 ✓" : "未继承");
    printf(" fd_cloexec (%d): %s(fork 不受 FD_CLOEXEC 影响)\\n",
    fd_cloexec,
    fcntl(fd_cloexec, F_GETFD) != -1 ? "继承 ✓" : "未继承");
    printf(" pipe 读端 (%d): %s\\n", pipefd[0],
    fcntl(pipefd[0], F_GETFD) != -1 ? "继承 ✓" : "未继承");

    /* exec 后 FD_CLOEXEC 的 fd 会被关闭 */
    printf("\\n【exec 后的 fd(通过 /proc 验证)】\\n");
    /* 设置 FD_CLOEXEC 标志 */
    fcntl(fd_normal, F_SETFD, FD_CLOEXEC); /* 现在设置 */

    char fd_str[8], cloexec_str[8];
    snprintf(fd_str, sizeof(fd_str), "%d", fd_normal);
    snprintf(cloexec_str, sizeof(cloexec_str), "%d", fd_cloexec);

    /* 执行一个程序来检查 fd */
    char *args[] = { "sh", "-c",
    "ls /proc/self/fd 2>/dev/null", NULL };
    execvp("sh", args);
    _exit(0);
    }
    waitpid(pid, NULL, 0);

    close(fd_normal);
    close(fd_cloexec);
    close(pipefd[0]);
    close(pipefd[1]);
    printf("\\n");
    }

    /* ── 测试2:信号处理继承 ── */
    void sig_handler(int sig) {
    printf(" 收到信号 %d\\n", sig);
    }

    void test_signal_inherit(void) {
    printf("=== 信号处理继承规则 ===\\n\\n");

    /* 设置各种信号处理 */
    signal(SIGUSR1, sig_handler); /* 自定义处理函数 */
    signal(SIGUSR2, SIG_IGN); /* 忽略 */
    /* SIGTERM 保持默认(SIG_DFL)*/

    printf("父进程信号设置:\\n");
    printf(" SIGUSR1: 自定义处理函数\\n");
    printf(" SIGUSR2: SIG_IGN(忽略)\\n");
    printf(" SIGTERM: SIG_DFL(默认)\\n\\n");

    pid_t pid = fork();
    if (pid == 0) {
    struct sigaction sa;

    sigaction(SIGUSR1, NULL, &sa);
    printf("【fork 后子进程】\\n");
    printf(" SIGUSR1: %s(继承自定义处理函数)\\n",
    sa.sa_handler == sig_handler ? "自定义 ✓" : "其他");

    sigaction(SIGUSR2, NULL, &sa);
    printf(" SIGUSR2: %s(继承 SIG_IGN)\\n",
    sa.sa_handler == SIG_IGN ? "SIG_IGN ✓" : "其他");

    /* exec 后信号处理重置 */
    printf("\\n exec 后信号处理规则:\\n");
    printf(" ✓ SIG_DFL → 保持 SIG_DFL\\n");
    printf(" ✓ SIG_IGN → 保持 SIG_IGN(重要!)\\n");
    printf(" ✗ 自定义函数 → 重置为 SIG_DFL\\n");
    _exit(0);
    }
    waitpid(pid, NULL, 0);
    printf("\\n");
    }

    /* ── 测试3:资源限制继承 ── */
    void test_rlimit_inherit(void) {
    printf("=== 资源限制继承 ===\\n\\n");

    /* 修改文件描述符限制 */
    struct rlimit rl;
    getrlimit(RLIMIT_NOFILE, &rl);
    printf("父进程 RLIMIT_NOFILE: 软=%lu 硬=%lu\\n",
    (unsigned long)rl.rlim_cur,
    (unsigned long)rl.rlim_max);

    /* 降低软限制 */
    rl.rlim_cur = 64;
    setrlimit(RLIMIT_NOFILE, &rl);

    pid_t pid = fork();
    if (pid == 0) {
    struct rlimit child_rl;
    getrlimit(RLIMIT_NOFILE, &child_rl);
    printf("子进程 RLIMIT_NOFILE: 软=%lu(继承父进程修改)\\n",
    (unsigned long)child_rl.rlim_cur);
    _exit(0);
    }
    waitpid(pid, NULL, 0);

    /* 恢复 */
    rl.rlim_cur = rl.rlim_max;
    setrlimit(RLIMIT_NOFILE, &rl);
    printf("\\n");
    }

    /* ── 测试4:fork 与多线程的危险 ── */
    static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

    void *thread_func(void *arg) {
    (void)arg;
    pthread_mutex_lock(&g_mutex);
    sleep(10); /* 持有锁的线程 */
    pthread_mutex_unlock(&g_mutex);
    return NULL;
    }

    void test_fork_multithread(void) {
    printf("=== fork 与多线程(重要警告)===\\n\\n");
    printf("⚠️ 在多线程程序中使用 fork 非常危险!\\n\\n");
    printf("问题:\\n");
    printf(" 1. fork 只复制调用线程,其他线程消失\\n");
    printf(" 2. 其他线程持有的锁在子进程中永远不会释放\\n");
    printf(" 3. 子进程可能死锁!\\n\\n");

    /* 创建一个持有锁的线程 */
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    usleep(100000); /* 等线程获取锁 */

    printf("主线程 fork(另一个线程持有 mutex)…\\n");
    pid_t pid = fork();
    if (pid == 0) {
    printf("子进程尝试获取 mutex(可能死锁!)\\n");
    /* 在实际程序中,这里会死锁 */
    /* 我们用 trylock 来演示 */
    int ret = pthread_mutex_trylock(&g_mutex);
    if (ret == 0) {
    printf("子进程获取 mutex 成功(运气好)\\n");
    pthread_mutex_unlock(&g_mutex);
    } else {
    printf("子进程无法获取 mutex(死锁风险!errno=%d)\\n", ret);
    }
    _exit(0);
    }
    waitpid(pid, NULL, 0);

    pthread_cancel(tid);
    pthread_join(tid, NULL);

    printf("\\n解决方案:\\n");
    printf(" 1. fork 后立即 exec(最安全)\\n");
    printf(" 2. 使用 pthread_atfork 注册 fork 处理函数\\n");
    printf(" 3. 避免在多线程程序中使用 fork\\n");
    }

    int main(void) {
    test_fd_inherit();
    test_signal_inherit();
    test_rlimit_inherit();
    test_fork_multithread();
    return 0;
    }
    gcc -o resource_inherit resource_inherit.c -lpthread
    ./resource_inherit
    # 输出示例:
    # === 文件描述符继承规则 ===
    #
    # fd_normal = 3(无 FD_CLOEXEC)
    # fd_cloexec = 4(有 FD_CLOEXEC)
    #
    # 【fork 后子进程的 fd】
    # fd_normal (3): 继承 ✓
    # fd_cloexec (4): 继承 ✓(fork 不受 FD_CLOEXEC 影响)
    # pipe 读端 (5): 继承 ✓


    7. clone — 底层进程/线程创建

    7.1 clone 系统调用

    /* 文件名:clone_demo.c
    * 演示 clone 系统调用:进程和线程创建的底层接口
    */
    #define _GNU_SOURCE
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sched.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/mman.h>
    #include <signal.h>

    /*
    * clone 是 fork/vfork/pthread_create 的底层实现
    *
    * flags 控制子进程与父进程共享哪些资源:
    * CLONE_VM 共享虚拟内存(线程)
    * CLONE_FS 共享文件系统信息(工作目录、根目录)
    * CLONE_FILES 共享文件描述符表
    * CLONE_SIGHAND 共享信号处理函数
    * CLONE_THREAD 加入父进程的线程组(线程)
    * CLONE_NEWPID 新 PID 命名空间(容器)
    * CLONE_NEWNET 新网络命名空间(容器)
    * CLONE_NEWNS 新挂载命名空间(容器)
    * CLONE_NEWUTS 新 UTS 命名空间(主机名)
    * CLONE_NEWIPC 新 IPC 命名空间
    * CLONE_NEWUSER 新用户命名空间
    *
    * fork ≈ clone(SIGCHLD)
    * vfork ≈ clone(CLONE_VM | CLONE_VFORK | SIGCHLD)
    * pthread_create ≈ clone(CLONE_VM | CLONE_FS | CLONE_FILES |
    * CLONE_SIGHAND | CLONE_THREAD | …)
    */

    #define STACK_SIZE (1024 * 1024) /* 1MB 栈 */

    /* 子进程/线程的入口函数 */
    int child_func(void *arg) {
    const char *label = (const char *)arg;
    printf(" [%s] PID=%d, PPID=%d\\n", label, getpid(), getppid());
    return 0;
    }

    int main(void) {
    printf("=== clone 系统调用演示 ===\\n\\n");

    /* 分配子进程/线程的栈 */
    char *stack1 = mmap(NULL, STACK_SIZE,
    PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK,
    -1, 0);
    char *stack2 = mmap(NULL, STACK_SIZE,
    PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK,
    -1, 0);

    /* ── clone 创建进程(类似 fork)── */
    printf("【clone 创建进程(类似 fork)】\\n");
    printf(" flags = SIGCHLD(只共享信号)\\n");
    pid_t pid1 = clone(child_func, stack1 + STACK_SIZE,
    SIGCHLD, /* 子进程退出时发送 SIGCHLD */
    (void *)"clone-process");
    if (pid1 == -1) { perror("clone process"); }
    else {
    waitpid(pid1, NULL, 0);
    printf(" 子进程 PID=%d 已退出\\n\\n", pid1);
    }

    /* ── clone 创建线程(类似 pthread_create)── */
    printf("【clone 创建线程(类似 pthread_create)】\\n");
    printf(" flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND\\n");
    pid_t pid2 = clone(child_func, stack2 + STACK_SIZE,
    CLONE_VM | CLONE_FS | CLONE_FILES |
    CLONE_SIGHAND | SIGCHLD,
    (void *)"clone-thread");
    if (pid2 == -1) { perror("clone thread"); }
    else {
    waitpid(pid2, NULL, 0);
    printf(" 线程 TID=%d 已退出\\n\\n", pid2);
    }

    /* ── fork/vfork/clone 对比 ── */
    printf("=== fork / vfork / clone 对比 ===\\n\\n");
    printf("%-15s %-12s %-12s %-12s\\n",
    "特性", "fork", "vfork", "clone");
    printf("%s\\n", "─────────────────────────────────────────────────");
    printf("%-15s %-12s %-12s %-12s\\n",
    "地址空间", "COW复制", "共享", "可配置");
    printf("%-15s %-12s %-12s %-12s\\n",
    "文件描述符", "复制", "共享", "可配置");
    printf("%-15s %-12s %-12s %-12s\\n",
    "父进程挂起", "否", "是", "可配置");
    printf("%-15s %-12s %-12s %-12s\\n",
    "命名空间", "继承", "继承", "可新建");
    printf("%-15s %-12s %-12s %-12s\\n",
    "典型用途", "进程创建", "已废弃", "线程/容器");

    munmap(stack1, STACK_SIZE);
    munmap(stack2, STACK_SIZE);
    return 0;
    }
    gcc -o clone_demo clone_demo.c
    ./clone_demo
    # 输出示例:
    # === clone 系统调用演示 ===
    #
    # 【clone 创建进程(类似 fork)】
    # flags = SIGCHLD(只共享信号)
    # [clone-process] PID=12346, PPID=12345
    # 子进程 PID=12346 已退出
    #
    # 【clone 创建线程(类似 pthread_create)】
    # flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND
    # [clone-thread] PID=12347, PPID=12345
    # 线程 TID=12347 已退出


    8. 综合实践

    8.1 实现一个简单的进程池

    /* 文件名:process_pool.c
    * 实现一个简单的进程池(Worker Pool)
    * 演示 fork + exec + 进程管理的综合应用
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <signal.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <time.h>

    #define POOL_SIZE 4
    #define MAX_TASKS 20

    /* 任务结构 */
    typedef struct {
    int task_id;
    int duration_ms; /* 模拟任务耗时 */
    char command[64]; /* 要执行的命令 */
    } Task;

    /* 工作进程信息 */
    typedef struct {
    pid_t pid;
    int busy; /* 是否正在执行任务 */
    int task_id; /* 当前任务 ID */
    time_t start_time; /* 任务开始时间 */
    } Worker;

    static Worker workers[POOL_SIZE];
    static volatile int g_running = 1;

    void sig_chld(int sig) {
    (void)sig;
    int status;
    pid_t pid;
    /* 非阻塞等待所有已退出的子进程 */
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
    for (int i = 0; i < POOL_SIZE; i++) {
    if (workers[i].pid == pid) {
    printf(" [工作进程 %d] PID=%d 完成任务 #%d,退出码=%d\\n",
    i, pid, workers[i].task_id,
    WIFEXITED(status) ? WEXITSTATUS(status) : -1);
    workers[i].busy = 0;
    workers[i].pid = 0;
    break;
    }
    }
    }
    }

    void sig_int(int sig) {
    (void)sig;
    g_running = 0;
    }

    /* 找一个空闲的工作进程槽 */
    int find_free_worker(void) {
    for (int i = 0; i < POOL_SIZE; i++) {
    if (!workers[i].busy) return i;
    }
    return -1;
    }

    /* 提交任务到进程池 */
    int submit_task(const Task *task) {
    int slot = find_free_worker();
    if (slot == -1) return -1; /* 没有空闲工作进程 */

    pid_t pid = fork();
    if (pid == -1) { perror("fork"); return -1; }

    if (pid == 0) {
    /* 工作进程:执行任务 */
    printf(" [工作进程 slot=%d] PID=%d 开始任务 #%d(%dms)\\n",
    slot, getpid(), task->task_id, task->duration_ms);

    /* 模拟任务执行 */
    usleep(task->duration_ms * 1000);

    /* 执行实际命令(如果有)*/
    if (task->command[0]) {
    char *args[] = { "sh", "-c", task->command, NULL };
    execvp("sh", args);
    }

    _exit(0);
    }

    /* 父进程:记录工作进程信息 */
    workers[slot].pid = pid;
    workers[slot].busy = 1;
    workers[slot].task_id = task->task_id;
    workers[slot].start_time = time(NULL);

    return slot;
    }

    int main(void) {
    printf("=== 进程池演示(池大小=%d)===\\n\\n", POOL_SIZE);

    /* 初始化 */
    memset(workers, 0, sizeof(workers));

    /* 注册信号处理 */
    struct sigaction sa_chld = {
    .sa_handler = sig_chld,
    .sa_flags = SA_RESTART | SA_NOCLDSTOP,
    };
    sigemptyset(&sa_chld.sa_mask);
    sigaction(SIGCHLD, &sa_chld, NULL);
    signal(SIGINT, sig_int);

    /* 创建任务队列 */
    Task tasks[MAX_TASKS];
    for (int i = 0; i < MAX_TASKS; i++) {
    tasks[i].task_id = i + 1;
    tasks[i].duration_ms = 200 + (i % 5) * 100; /* 200~600ms */
    snprintf(tasks[i].command, sizeof(tasks[i].command),
    "echo 'Task #%d done'", i + 1);
    }

    printf("提交 %d 个任务到进程池…\\n\\n", MAX_TASKS);

    int submitted = 0;
    int completed = 0;

    while ((submitted < MAX_TASKS || completed < MAX_TASKS) && g_running) {
    /* 尝试提交新任务 */
    while (submitted < MAX_TASKS) {
    int slot = submit_task(&tasks[submitted]);
    if (slot == -1) break; /* 没有空闲工作进程 */
    printf(" 任务 #%d 提交到 slot=%d(PID=%d)\\n",
    tasks[submitted].task_id, slot, workers[slot].pid);
    submitted++;
    }

    /* 等待任意子进程完成 */
    int status;
    pid_t done_pid = waitpid(-1, &status, WNOHANG);
    if (done_pid > 0) {
    for (int i = 0; i < POOL_SIZE; i++) {
    if (workers[i].pid == done_pid) {
    completed++;
    printf(" 任务 #%d 完成(%d/%d)\\n",
    workers[i].task_id, completed, MAX_TASKS);
    workers[i].busy = 0;
    workers[i].pid = 0;
    break;
    }
    }
    } else {
    usleep(10000); /* 10ms 轮询间隔 */
    }
    }

    /* 等待所有工作进程完成 */
    while (wait(NULL) > 0);

    printf("\\n所有 %d 个任务已完成!\\n", MAX_TASKS);
    return 0;
    }
    gcc -O2 -o process_pool process_pool.c
    ./process_pool
    # 输出示例:
    # === 进程池演示(池大小=4)===
    #
    # 提交 20 个任务到进程池…
    #
    # 任务 #1 提交到 slot=0(PID=12346)
    # 任务 #2 提交到 slot=1(PID=12347)
    # 任务 #3 提交到 slot=2(PID=12348)
    # 任务 #4 提交到 slot=3(PID=12349)
    # [工作进程 slot=0] PID=12346 开始任务 #1(200ms)
    # …
    # 所有 20 个任务已完成!

    8.2 fork/exec 速查手册

    #!/bin/bash
    # 文件名:fork_exec_guide.sh
    # 功能:fork/exec 使用指南和常见陷阱

    cat << 'EOF'
    ═══════════════════════════════════════════════════════════════
    fork / exec 使用指南
    ═══════════════════════════════════════════════════════════════

    【fork 使用模板】
    ─────────────────────────────────────────────────────────────
    fflush(stdout); // ① fork 前刷新缓冲区
    pid_t pid = fork();
    if (pid == -1) {
    perror("fork"); // ② 检查错误
    // 错误处理
    } else if (pid == 0) {
    // 子进程
    // … 做一些工作 …
    _exit(0); // ③ 子进程用 _exit,不用 exit
    } else {
    // 父进程
    int status;
    waitpid(pid, &status, 0); // ④ 必须 wait,防止僵尸
    }

    【fork + exec 模板】
    ─────────────────────────────────────────────────────────────
    pid_t pid = fork();
    if (pid == 0) {
    // 设置重定向(dup2)
    // 关闭不需要的 fd
    execvp(argv[0], argv);
    perror("exec");
    _exit(127); // exec 失败,用 127 表示命令未找到
    }
    waitpid(pid, &status, 0);

    【exec 函数选择】
    ─────────────────────────────────────────────────────────────
    已知完整路径 + 参数列表 → execl("/bin/ls", "ls", "-l", NULL)
    搜索 PATH + 参数列表 → execlp("ls", "ls", "-l", NULL)
    已知完整路径 + 参数数组 → execv("/bin/ls", argv)
    搜索 PATH + 参数数组 → execvp("ls", argv)
    自定义环境变量 → execle/execve(带 envp 参数)

    【常见陷阱】
    ─────────────────────────────────────────────────────────────
    ❌ fork 前未 fflush → 缓冲区内容被复制,输出重复
    ❌ 子进程用 exit → 调用 atexit,破坏父进程状态
    ❌ 父进程不 wait → 子进程变成僵尸进程
    ❌ 多线程中 fork → 其他线程消失,可能死锁
    ❌ exec 后访问旧变量 → 地址空间已替换,未定义行为
    ❌ 忘记关闭 fd → fd 泄漏到子进程

    【资源继承速查】
    ─────────────────────────────────────────────────────────────
    资源 fork 继承 exec 保留
    ─────────────────────────────────────────
    PID 新 PID 保留
    文件描述符 ✓ 继承 ✓(除 FD_CLOEXEC)
    信号处理函数 ✓ 继承 ✗(重置为默认)
    SIG_IGN ✓ 继承 ✓ 保留
    环境变量 ✓ 继承 ✓ 保留(或自定义)
    工作目录 ✓ 继承 ✓ 保留
    umask ✓ 继承 ✓ 保留
    资源限制 ✓ 继承 ✓ 保留
    内存映射 COW 复制 ✗ 清除
    定时器 ✗ 不继承 ✗ 清除
    待处理信号 ✗ 清空 ✗ 清空
    文件记录锁 ✗ 不继承 ✗ 清除
    内存锁(mlock) ✗ 不继承 ✗ 清除

    ═══════════════════════════════════════════════════════════════
    EOF


    知识点总结

    第 9 章 核心知识图谱
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ┌──────────────────────────────────────────────────────────┐
    │ 进程创建 │
    └────────┬──────────┬──────────┬──────────┬────────────────┘
    │ │ │ │
    ┌──────▼───┐ ┌────▼─────┐ ┌──▼──────┐ ┌▼──────────────┐
    │ fork │ │ vfork │ │ exec │ │ clone │
    └──────┬───┘ └────┬─────┘ └──┬──────┘ └┬──────────────┘
    │ │ │ │
    COW机制 共享地址空间 execl/v/p/e CLONE_VM
    执行顺序 父进程挂起 exec内部机制 CLONE_FILES
    信号继承 立即exec/_exit PID保留 命名空间
    fd共享 已废弃 fd继承规则 容器基础
    定时器不继承 信号重置

    exec 函数命名规律:
    l = list(参数列表) v = vector(参数数组)
    p = path(搜索PATH) e = environment(自定义环境)

    fork + exec 标准模式:
    fflush → fork → 子进程设置重定向 → exec → 父进程 waitpid

    黄金法则:
    ① fork 前必须 fflush(stdout/stderr)
    ② 子进程用 _exit,不用 exit(避免重复调用 atexit)
    ③ 父进程必须 waitpid,防止僵尸进程
    ④ exec 后 FD_CLOEXEC 的 fd 自动关闭(安全实践)
    ⑤ exec 后自定义信号处理函数重置为 SIG_DFL
    ⑥ 多线程程序中 fork 后应立即 exec(避免死锁)
    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━


    📚 参考资料

    • man 2 fork / man 2 vfork / man 2 clone

    • man 3 exec / man 3 execl / man 3 execvp / man 2 execve

    • man 3 posix_spawn / man 3 posix_spawn_file_actions_addopen

    • man 2 waitpid / man 7 signal

    • 《Linux/UNIX 系统编程手册》第 24、25、27、28 章 — Michael Kerrisk

    • 《UNIX 环境高级编程(APUE)》第 8 章 — W. Richard Stevens

    • Linux 内核源码:kernel/fork.c(fork/clone 实现)

    赞(0)
    未经允许不得转载:171主机测评 » Linux 系统编程 · 第 9 章:进程创建
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址