第 28 章 进程创建与程序执行的更多细节 (Process Creation and Program Execution in More Detail)

      +

      核心结论

      • 进程记账 (BSD Process Accounting):内核为每个终止的进程写一条记录到系统记账文件,含用户 ID、CPU 时间、内存平均使用、退出状态等;通过 acct() 启用/禁用;需 CONFIG_BSD_PROCESS_ACCT 内核选项。

      • acct 记录在进程终止时写入:Linux 2.6.10+ 改为「整个进程最后线程退出时」写一条;NPTL 之前的 LinuxThreads 每线程一条;记录按终止时间排序(不是启动时间);崩溃时不写入未终止进程。

      • comp_t 是 3 位 base-8 指数 + 13 位尾数的「压缩时钟滴答」:表示 mantissa × 8^exp;最大可表示 (2^13-1) × 8^7;需要 long long 转换。

      • clone() 是 Linux 特有的进程/线程创建系统调用:fork/vfork/clone 在内核统一实现为 do_fork();clone() 允许精细控制子进程共享哪些属性(fd 表/信号 disposition/虚拟内存/线程组等);是 NPTL 线程实现的基础。

      • POSIX 线程语义对应 clone() flags 组合:NPTL 用 CLONE_VM|CLONE_FILES|CLONE_FS|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|CLONE_SYSVSEM;fork ≈ SIGCHLD;vfork ≈ CLONE_VM|CLONE_VFORK|SIGCHLD

      • vfork/clone 创建子进程比 fork 快得多:fork 需复制页表并标记所有页为 COW;vfork/clone 共享内存,几乎零成本;但 fork+exec 总时间差异因 exec 主导而变小(表 28-3)。

      本章主旨

      本章是「进程生命周期」四章的终章——深入第 24-27 章未覆盖的细节。读者应掌握:进程记账(acct() 系统调用 + struct acct/v3 记录)、Linux 特有的 clone() 系统调用及其 flags 组合(线程实现的基石)、fork/vfork/clone 性能差异(页表复制是 fork 瓶颈)、fork+exec 对进程属性的完整影响(表 28-4 是全书最常被引用的对照表之一)。理解 clone flags 是理解「线程与进程的本质区别 = 属性共享程度」的关键——POSIX 线程只是诸多可能组合中的一种。

      一、核心概念

      本章围绕 6 个核心概念展开:先讲进程记账机制(acct 结构 + v3 扩展),然后是 clone() 系统调用(flags 的语义与线程实现),再到性能对比,最后用 fork/exec 属性表收束。

      概念 定义 + 重要性 实现提示

      进程记账 (BSD Process Accounting)

      内核在每个进程终止时写一条记录到系统记账文件(典型 /var/log/pacct);包含 UID/GID/控制终端/启动时间/CPU 时间/平均内存/退出码/命令行;ac_flag 位掩码记录 AFORK/ASU/AXSIG/ACORE。

      §28.1;acct() 启用/禁用;需 CAP_SYS_PACCT;CONFIG_BSD_PROCESS_ACCT;disk space 监控 /proc/sys/kernel/acct。

      acct_v3 格式 (Linux 2.6.8+)

      扩展 acct 格式:32 位 UID/GID、新增 ac_pid/ac_ppid、ac_etime 改为 float、ac_version=3;解决传统格式 16 位 UID 无法表示大 ID 的问题;需 CONFIG_BSD_PROCESS_ACCT_V3。

      §28.1;acct_v3_view.c 读取 v3 记录;与传统记录不兼容。

      clone() 系统调用

      Linux 特有的进程创建系统调用;接受 func/child_stack/flags/func_arg + 可选 ptid/tls/ctid;flags 的低字节是终止信号;其余字节控制父子共享哪些属性(VM/files/fs/sighand/thread 等)。

      §28.2;glibc clone() wrapper 调用 sys_clone() 后跳转到 func;child_stack 必须指向分配栈的高地址(栈向下生长)。

      clone() flags 与线程实现

      POSIX 线程 = CLONE_VM(共享内存)+ CLONE_FILES/CLONE_FS/CLONE_SIGHAND(POSIX 必需)+ CLONE_THREAD(同线程组共享 TGID)+ CLONE_SETTLS(线程本地存储)+ CLONE_PARENT_SETTID/CLONE_CHILD_CLEARTID(用 futex 实现 pthread_join)。

      §28.2.1;表 28-2;LinuxThreads 用不同 flags 集合(无 CLONE_THREAD,每线程独立 PID)。

      waitpid() 扩展:WCLONE/WALL/__WNOTHREAD

      Linux 特有的 waitpid options:WCLONE 等 clone 子(终止信号 ≠ SIGCHLD);WALL 等所有子;__WNOTHREAD 限制为调用进程直接子。

      §28.2.2;不与 waitid() 共用;用 clone() 时通常需 __WCLONE(除非终止信号 = SIGCHLD)。

      fork+exec 对进程属性影响 (表 28-4)

      全书最常引用的对照表:哪些属性跨 exec 保留/重置、哪些属性跨 fork 继承/共享/不复用;要点:exec 复位 handled 信号 + 浮点环境 + locale;fork 不继承 interval timer/挂起信号/record lock;mmap MAP_NORESERVE 跨 fork 继承。

      §28.4;表 28-4;配合第 5/9/21/22/27/29/33/35/39 等章节交叉引用。

      二、详细笔记

      28.1 进程记账 (BSD Process Accounting)

      What:内核在每个进程终止时写一条 struct acct(v2 或 v3)记录到系统记账文件;用 acct(pathname) 启用,acct(NULL) 禁用;需 CAP_SYS_PACCT。

      Why:历史用途——多用户 UNIX 系统按资源消耗计费;现代用途——审计未监控进程的活动、调试异常退出(信号杀、core dump)、统计用户 CPU 消耗。

      How

      acct() 用法(§28.1):

      // 摘自《The Linux Programming Interface》第 28 章
      // 摘自 procexec/acct_on.c
      #define _BSD_SOURCE
      #include <unistd.h>
      int acct(const char *acctfile);   /* NULL 禁用;非空启用并指定文件 */

      传统 struct acct 字段(§28.1):

      • ac_flag 位掩码:AFORK/ASU/AXSIG/ACORE

      • ac_uid/ac_gid:16 位 UID/GID(v3 扩展为 32 位)。

      • ac_tty:控制终端(daemon 无 → 0)。

      • ac_btime:启动时间(time_t)。

      • ac_utime/ac_stime/ac_etime/ac_mem:comp_t 类型,需除以 sysconf(_SC_CLK_TCK) 转秒。

      • ac_exitcode:终止状态(v3 扩展字段)。

      • ac_comm[17]:最后 exec 的命令名(basename)。

      comp_t 解码(§28.1,Listing 28-2):

      // 摘自《The Linux Programming Interface》第 28 章 — Listing 28-2
      // 摘自 procexec/acct_view.c
      static long long comptToLL(comp_t ct) {
          const int EXP_SIZE = 3, MANTISSA_SIZE = 13;
          const int MANTISSA_MASK = (1 << MANTISSA_SIZE) - 1;
          long long mantissa = ct & MANTISSA_MASK;
          long long exp = (ct >> MANTISSA_SIZE) & ((1 << EXP_SIZE) - 1);
          return mantissa << (exp * 3);   /* 8^exp = 左移 3*exp 位 */
      }

      ac_flag 含义(表 28-1):

      • AFORK:fork 创建但未 exec 即终止。

      • ASU:使用了超级用户权限。

      • AXSIG:被信号终止(非所有 UNIX 都有)。

      • ACORE:产生了 core dump。

      磁盘空间控制(§28.1):/proc/sys/kernel/acct 含 3 个值——high-waterlow-waterfrequency;free space < low-water% 暂停记账;> high-water% 恢复。

      acct_v3 改进(§28.1):32 位 UID/GID、新增 ac_pid/ac_ppidac_etime 改 float、ac_version=3;解决 16 位 UID 无法表示 32 位 user namespace ID 的问题。

      When:审计服务器资源使用、追踪谁在跑什么命令、调试神秘退出(结合 core dump 信号识别)。

      Example

      // 摘自《The Linux Programming Interface》第 28 章 — Listing 28-1
      // 摘自 procexec/acct_on.c
      if (argc > 2 || (argc > 1 && strcmp(argv[1], "--help") == 0))
          usageErr("%s [file]\n");
      if (acct(argv[1]) == -1)
          errExit("acct");
      printf("Process accounting %s\n",
              (argv[1] == NULL) ? "disabled" : "enabled");
      exit(EXIT_SUCCESS);

      28.2 clone() 系统调用

      What:Linux 特有的进程/线程创建系统调用;通过 flags 位掩码控制父子共享哪些属性;底层 fork/vfork/clone 都是 do_fork()

      Why:线程库需要精细控制「什么共享、什么私有」——POSIX 线程只是众多可能组合之一;理解 clone flags 是理解「线程 vs 进程」本质差异的钥匙。

      How

      clone() 签名(§28.2):

      // 摘自《The Linux Programming Interface》第 28 章
      #define _GNU_SOURCE
      #include <sched.h>
      int clone(int (*func)(void *), void *child_stack,
                int flags, void *func_arg, ...
                /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */);

      关键设计点:

      • 子从 func(func_arg) 开始,而非从 clone() 返回处继续。

      • child_stack 必须指向调用者分配栈的高地址(栈向下生长);IA-64 改用 clone2() 同时指定 start + size 避免依赖栈方向。

      • flags 低字节是终止信号(默认 SIGCHLD);其余字节是共享标志位。

      • ptid/ctid/tls 用于线程实现(NPTL 必需)。

      • 父用 waitpid(child_pid, &status, WCLONE) 等待;若终止信号 = SIGCHLD 可省略 WCLONE。

      flags 总览(表 28-2):

      • 线程实现必需CLONE_VM(共享内存)、CLONE_FILES(共享 fd 表)、CLONE_FS(共享 umask/cwd/root)、CLONE_SIGHAND(共享信号 disposition)。

      • 线程组CLONE_THREAD(同 TGID,getpid() 返回相同值;exec 时所有其他线程终止)。

      • 线程本地存储CLONE_SETTLS + tls 参数(NPTL 用 struct user_desc)。

      • 线程 ID 传递CLONE_PARENT_SETTID(写 ptid)/ CLONE_CHILD_SETTID(写 ctid)/ CLONE_CHILD_CLEARTID(子终止时清 ctid,触发 futex 唤醒 pthread_join)。

      • vfork 兼容CLONE_VFORK(父挂起直到子 exec 或 _exit)。

      • 容器/namespaceCLONE_NEWNS(mount)、CLONE_NEWPID(PID)、CLONE_NEWNET(net)、CLONE_NEWIPC(SysV IPC)、CLONE_NEWUSER(user)、CLONE_NEWUTS(hostname);2.6.19+ 引入。

      • 其他CLONE_IO(共享 I/O context,2.6.25+)、CLONE_PARENT(子 PPID = caller PPID)、CLONE_PTRACE(同时 trace 子)、CLONE_SYSVSEM(共享 SysV semaphore undo)、CLONE_VM(共享虚拟内存)。

      CLONE_THREAD 特殊语义(§28.2.1):

      • 同一线程组的 KSE 共享 TGID——getpid() 返回 TGID。

      • 线程组第一个线程(TID=TGID)是「线程组 leader」。

      • 线程组任一线程 exec → 其他线程全部终止,新程序在 leader 中运行(POSIX 要求)。

      • 线程组任一线程 fork → 任何线程可用 wait 等待(默认跨线程组 wait)。

      • CLONE_THREAD 子不能用 wait 等待(POSIX 要求);用 pthread_join + futex(CLONE_CHILD_CLEARTID)。

      • Linux 2.4 引入 gettid() 返回 TID;TID 系统全局唯一(除 leader 外不与 PID 冲突)。

      • LinuxThreads(兼容旧 NPTL)不用 CLONE_THREAD——故每线程独立 PID。

      fork/vfork/clone 等价(§28.2.1):

      • fork ≈ clone(SIGCHLD)

      • vfork ≈ clone(CLONE_VM | CLONE_VFORK | SIGCHLD)

      • NPTL pthread_create ≈ clone(CLONE_VM|CLONE_FILES|CLONE_FS|CLONE_SIGHAND|CLONE_THREAD|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID|CLONE_SYSVSEM)

      • glibc fork() wrapper 自 NPTL 起直接调用 clone()(而非 sys_fork),会触发 pthread_atfork handlers。

      waitpid 扩展(§28.2.2):

      • __WCLONE:等 clone 子(终止信号 ≠ SIGCHLD)。

      • __WALL:等所有子(忽略 type)。

      • __WNOTHREAD:限制为调用进程直接子(默认跨同线程组兄弟线程)。

      • 不与 waitid() 共用。

      When:实现线程库、写容器、写自定义 IPC 守护进程;应用代码通常不直接调 clone(),用 pthread_create 即可。

      Example

      // 摘自《The Linux Programming Interface》第 28 章 — Listing 28-3 (简化)
      // 摘自 procexec/t_clone.c
      static int childFunc(void *arg) {
          if (close(*((int *) arg)) == -1) errExit("close");
          return 0;
      }
      const int STACK_SIZE = 65536;
      char *stack = malloc(STACK_SIZE);
      char *stackTop = stack + STACK_SIZE;   /* Assume stack grows down */
      int flags = (argc > 1) ? CLONE_FILES : 0;
      if (clone(childFunc, stackTop, flags | SIGCHLD, (void *) &fd) == -1)
          errExit("clone");
      if (waitpid(-1, NULL, 0) == -1) errExit("waitpid");
      /* 检查 fd 是否被子的 close 影响 */
      if (write(fd, "x", 1) == -1 && errno == EBADF)
          printf("file descriptor %d has been closed\n", fd);

      28.3 fork/vfork/clone 性能对比

      What:表 28-3 显示 fork/vfork/clone 在不同进程大小下的创建速度——fork 慢且随进程增大变慢,vfork/clone 快且恒定。

      Why:理解 fork 的瓶颈(页表复制 + COW 标记);知道 vfork 在大进程下的巨大优势;理解为什么现代 Linux 仍推荐 fork+exec 而非 vfork(除非性能关键)。

      How:性能差异原因(§28.3):

      • fork 慢:复制整个页表 + 标记 data/heap/stack 页为只读(COW);进程越大越慢。

      • vfork 快:不复制页表/页;父子共享地址空间;父挂起直到子 exec/_exit;与进程大小无关。

      • clone(VM) 快:与 vfork 类似;额外 flags 复制 fd table / fs attrs / sighand table——开销恒定。

      • fork+exec 差异缩小:exec 主导耗时(读 ELF + 加载段 + 初始化);fork/vfork 的差异相对不明显。

      实测数据(表 28-3,100,000 次创建):

      • 1.7 MB 进程:fork 22.27s;vfork 3.52s;clone 2.97s。

      • 11.7 MB 进程:fork 126.93s;vfork 3.53s;clone 2.93s。

      • fork+exec vs vfork+exec:差异 < 2 倍(exec 主导)。

      注意:

      • fd 数影响 vfork——clone(CLONE_FILES) 不复制 fd 表,开销恒定;vfork 需复制 fd 表。

      • 测试重复 exec 同一程序——内核缓冲命中,实际 exec 成本被低估。

      When:写高性能 fork-and-exec 服务器(web server、agent);子进程需立即 exec → 用 vfork 或 clone(CLONE_VFORK) 优化;否则默认 fork 即可。

      Example

      // 摘自《The Linux Programming Interface》第 28 章
      // 测试 fork vs vfork 速度:循环 100,000 次创建子 + wait
      struct timeval start, end;
      gettimeofday(&start, NULL);
      for (int i = 0; i < N; i++) {
          pid_t pid = fork();
          if (pid == 0) _exit(0);   /* 子立即退出 */
          waitpid(pid, NULL, 0);
      }
      gettimeofday(&end, NULL);
      /* 比较:vfork 同样循环;clone(CLONE_VM|CLONE_VFORK) 同样循环 */

      28.4 fork/exec 对进程属性的影响

      What:表 28-4 是全书最常被引用的对照表——列出每个进程属性在 fork(继承/共享/不复用)和 exec(保留/重置)时的行为。

      Why:理解「子进程从父进程继承什么」是写并发程序、调试 fork 后行为的关键;理解「exec 保留什么」是写 daemon 重读配置、set-UID 程序的基础。

      How:关键属性(§28.4):

      • exec 保留:PID、PPID、PGID/SID、real UID/GID、cwd/root/umask、信号屏蔽字、挂起信号、interval timer、alarm、resource limits、nice 值、调度策略、文件锁、locale 不保留(exec 后 setlocale(LC_ALL, "C"))、浮点环境不保留(exec 后重置为默认)。

      • exec 重置:handled 信号 → SIG_DFL、locale → C、浮点环境 → 默认、共享内存段自动 detach、aio 操作取消、PR_SET_DUMPABLE 重置(set-UID 时清零)、exit handlers 不保留。

      • fork 继承:栈段、数据段、堆段、环境变量、内存映射(MAP_NORESERVE 标志也继承;MADV_DONTFORK 标记的不继承)、UID/GID/补充组、cwd/root/umask、信号 disposition、信号屏蔽字、alternate signal stack、nice 值、调度策略、resource limits、文件描述符表(共享打开文件描述但独立 fd table)、文件锁引用、locale、浮点环境。

      • fork 共享:文件描述符所指打开文件描述(共享偏移 + 状态标志)、CLONE_FILES 时共享 fd 表。

      • fork 不继承:PID、PPID(独立)、interval timer、alarm、POSIX timer、挂起信号、record lock(fcntl)、aio 操作、PR_SET_PDEATHSIG、PR_SET_NAME、process/child CPU times、resource usages、SysV semaphore 调整、System V 共享内存段(detach)、POSIX 共享内存对象(fd 不继承但可重新 open)、消息队列描述符(child 需重新 mq_open)、未决的内存锁(mlock)。

      When:写 fork-based 服务器——子进程需重设 timer/alarm/handler;写 exec 重读配置的 daemon——知道哪些状态保留;调试 fork 后挂起信号丢失问题。

      Example(子进程清理模式):

      // 摘自《The Linux Programming Interface》第 28 章 — fork 后清理
      pid_t pid = fork();
      if (pid == 0) {
          /* 子进程清理从父继承的不需要状态 */
          signal(SIGCHLD, SIG_DFL);          /* 重置信号处置 */
          alarm(0);                          /* 取消 alarm */
          /* 清空 inherited aio / pthread state */
          execl("/bin/cmd", "cmd", (char *) NULL);
          _exit(127);
      }

      28.5 容器与 namespace flags

      What:Linux 2.6.19+ 新增一组 CLONE_NEW* flags,用于为子进程创建独立的内核资源视图——这是容器(Docker/LXC)的实现基础。

      Why:容器是「共享内核的轻量虚拟化」——比完整虚拟化(KVM/VMware)开销低得多;理解 clone flags 是理解容器隔离机制的关键。

      How

      namespace flags(§28.2.1):

      • CLONE_NEWNS:mount namespace——独立 mount 点集合。

      • CLONE_NEWPID:PID namespace——独立 PID 空间(容器内 PID 1 是 init)。

      • CLONE_NEWNET:network namespace——独立网络栈(网卡/路由/iptables)。

      • CLONE_NEWIPC:SysV IPC namespace。

      • CLONE_NEWUSER:user namespace——独立 UID/GID 映射。

      • CLONE_NEWUTS:UTS namespace——独立 hostname/domainname。

      容器实现要点:

      • 容器内进程看到的「全局资源」其实是内核 namespace 抽象后的视图。

      • namespace 可嵌套;外层 namespace 看不到内层(但内层通过 /proc/<pid>/ns/ 看到父)。

      • 与 unshare(2) 结合可让已有进程脱离某些 namespace。

      • 与 cgroup 结合实现资源限制(CPU、内存、磁盘 I/O)。

      When:写容器引擎、沙箱、多租户隔离环境;理解 Docker podman runc 的实现。

      Example(创建 PID namespace):

      // 摘自《The Linux Programming Interface》第 28 章
      #include <sched.h>
      #include <sys/wait.h>
      #include <unistd.h>
      #include <stdio.h>
      
      int child(void *arg) {
          printf("PID in namespace: %d\n", getpid());   /* 在容器内通常是 1 */
          execl("/bin/sh", "sh", (char *) NULL);
          return 1;
      }
      
      int main() {
          char stack[4096];
          /* CLONE_NEWPID + CLONE_NEWNS + CLONE_NEWUTS 等 */
          pid_t pid = clone(child, stack + 4096,
                            CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUTS | SIGCHLD,
                            NULL);
          waitpid(pid, NULL, 0);
          return 0;
      }

      三、关键图表

      fork/exec 对进程属性影响摘要(精简版表 28-4)
      进程属性 exec() fork() 接口/备注

      PID, PPID

      保留

      不继承

      fork 后子独立 PID

      进程组 ID, 会话 ID

      保留

      继承

      setpgid, setsid

      真实/有效/saved UID/GID

      保留

      继承

      setuid, setgid(第 9 章)

      补充组 ID

      保留

      继承

      setgroups, initgroups

      文件描述符表

      保留(除非 FD_CLOEXEC)

      继承(独立副本)

      open/close/dup/pipe

      文件偏移 + 状态标志

      保留

      共享

      lseek, fcntl

      当前工作目录 + 根目录

      保留

      继承

      chdir, chroot

      umask

      保留

      继承

      umask

      信号 disposition(handled)

      → SIG_DFL

      继承

      sigaction, signal

      信号 disposition(SIG_IGN/SIG_DFL)

      保留

      继承

      -

      信号屏蔽字

      保留

      继承

      sigprocmask

      挂起信号

      保留

      不继承

      -

      interval timer (setitimer)

      保留

      不继承

      setitimer

      alarm

      保留

      不继承

      alarm

      POSIX timer

      不保留

      不继承

      timer_create

      替代信号栈

      不保留

      继承

      sigaltstack

      调度策略 + nice 值

      保留

      继承

      sched_setscheduler, nice

      resource limits

      保留

      继承

      setrlimit

      进程 CPU 时间

      保留

      不继承

      times

      资源使用(getrusage)

      保留

      不继承

      getrusage

      共享内存段(SysV)

      自动 detach

      继承引用

      shmat

      POSIX 共享内存对象

      自动关闭

      继承引用

      shm_open

      异步 I/O 操作

      取消

      不继承

      aio_read/write

      文件锁(fcntl record lock)

      保留

      不继承(但共享引用)

      fcntl

      线程

      不保留

      只复制调用线程

      pthread_create

      locale

      → "C"

      继承

      setlocale

      浮点环境

      → 默认

      继承

      fenv

      退出处理器(atexit)

      不保留

      继承

      atexit

      文件系统 ID(setfsuid)

      保留

      继承

      setfsuid, setfsgid

      timerfd 计时器

      保留

      继承引用

      timerfd_create

      capabilities

      见 §39.5

      继承

      capset

      CPU 亲和性

      保留

      继承

      sched_setaffinity

      内存映射(mmap)

      不保留

      继承

      MAP_NORESERVE 标志继承

      内存锁(mlock)

      不保留

      不继承

      mlock

      详见 TLPI 表 28-4。

      clone() flags 速查表(精简版表 28-2)
      Flag 效果

      CLONE_VM

      父子共享虚拟内存(同 vfork)

      CLONE_FILES

      父子共享 fd 表

      CLONE_FS

      父子共享 umask/cwd/root

      CLONE_SIGHAND

      父子共享信号 disposition(需 CLONE_VM)

      CLONE_THREAD

      父子同线程组(共享 TGID/PID)

      CLONE_VFORK

      父挂起直到子 exec/_exit

      CLONE_PARENT

      子 PPID = 调用者 PPID

      CLONE_SETTLS

      tls 参数描述线程本地存储

      CLONE_PARENT_SETTID

      写子 TID 到 ptid(早于内存复制)

      CLONE_CHILD_SETTID

      写子 TID 到 ctid(子内存)

      CLONE_CHILD_CLEARTID

      子终止时清 ctid(futex 唤醒 pthread_join)

      CLONE_SYSVSEM

      父子共享 SysV semaphore undo

      CLONE_IO

      子共享父 I/O context(2.6.25+)

      CLONE_NEWNS

      子得新 mount namespace

      CLONE_NEWPID

      子得新 PID namespace

      CLONE_NEWNET

      子得新 network namespace

      CLONE_NEWIPC

      子得新 SysV IPC namespace

      CLONE_NEWUSER

      子得新 user namespace

      CLONE_NEWUTS

      子得新 UTS namespace

      CLONE_PTRACE

      父被 trace 时子也 trace

      CLONE_UNTRACED

      阻止 CLONE_PTRACE 强制施加

      CLONE_PID

      子同父 PID(2.6 起 obsolete;系统启动专用)

      fork ≈ SIGCHLD;vfork ≈ CLONE_VM | CLONE_VFORK | SIGCHLD;NPTL 线程见 §28.2.1。

      四、思维导图

      mindmap
        root((第 28 章 进程创建进阶))
          进程记账
            acct 系统调用
            struct acct 传统
            struct acct v3 32位 UID
            comp_t 压缩时钟
            ac flag 位掩码
            AFORK ASU AXSIG ACORE
            磁盘空间控制
          clone 系统调用
            func child stack
            flags 低字节终止信号
            CLONE VM 共享内存
            CLONE FILES FS SIGHAND
            CLONE THREAD 同线程组
            CLONE SETTLS 线程本地
            SETTID CLEARTID futex
            容器 namespace flags
            IA 64 clone2 改进栈方向
          waitpid 扩展
            WCLONE 等 clone 子
            WALL 等所有子
            WNOTHREAD 限直接子
            不与 waitid 共用
          性能对比
            fork 慢 页表复制
            vfork 快 共享内存
            clone 接近 vfork
            fd 数影响 vfork
            exec 主导 fork exec 差异缩小
          fork exec 属性
            exec 保留 PID PPID
            exec 复位 handled SIG
            exec 重置 locale 浮点
            fork 继承 fd disposition
            fork 不继承 timer pending
            表 28 4 全书最常引用
          容器与 namespace
            NEWNS NEWNET NEWPID
            NEWIPC NEWUSER NEWUTS
            轻量虚拟化
            unshare 反向操作

      五、重点与易错点

      1. 进程记账记录按终止时间排序,不是启动时间——崩溃时不写入未终止进程;ac_btime 是启动时间但记录顺序由终止时间决定。

      2. Linux 2.6.10+ 起记账按进程(最后一个线程退出时写一条)——之前 LinuxThreads 每线程一条;混淆这两者会得到不同的 CPU 统计。

      3. comp_t 是 3 位 base-8 指数 + 13 位尾数——表示 mantissa × 8^exp;最大值 (2^13-1) × 8^7;必须用 long long 转换(32 位 unsigned long 在 x86-32 不够)。

      4. acct_v3 用 32 位 UID/GID——传统格式 16 位 UID 无法表示 32 位 user namespace ID;用 v3 格式避免 ID 截断。

      5. clone() 子从 func 开始,不是从 clone() 返回处——glibc wrapper 调 sys_clone 后跳转到 func;child_stack 必须指向分配栈的高地址(IA-64 用 clone2 同时指定 start+size)。

      6. CLONE_SIGHAND 必须配合 CLONE_VM——Linux 2.6 起硬性要求;不能单独用 CLONE_SIGHAND。

      7. CLONE_THREAD 子不能用 wait 等待——POSIX 要求;用 pthread_join + futex(CLONE_CHILD_CLEARTID 触发);这是「线程不是进程」的关键差异。

      8. 线程组任一线程 exec → 其他所有线程终止——POSIX 要求;新程序在 leader 中运行;exec 后 CLONE_THREAD 的终止信号重置为 SIGCHLD。

      9. LinuxThreads 与 NPTL 关键差异:LinuxThreads 不用 CLONE_THREAD——故每线程独立 PID、getpid 返回不同值;NPTL 用 CLONE_THREAD——同 TGID、getpid 返回相同值。

      10. fork() 性能瓶颈是页表复制 + COW 标记——进程越大越慢;vfork/clone 共享内存几乎零成本;但 fork+exec 总时间差异因 exec 主导而变小。

      11. vfork 复制 fd 表有开销——clone(CLONE_FILES) 不复制,开销恒定;vfork 复制数与父 fd 数成正比。

      12. 表 28-4 是写 fork 子进程清理代码的圣经——子必须重设 timer/alarm/signal handler;不能依赖 interval timer/POSIX timer/挂起信号继承。

      13. exec 后 locale 重置为 "C"——C run-time 自动调用 setlocale(LC_ALL, "C");浮点环境重置为默认;exit handlers 不保留。

      14. exec 不保留 sigaltstack + SA_ONSTACK——信号栈随旧栈消失;SA_ONSTACK 自动清除(详见第 27 章)。

      15. mmap MAP_NORESERVE 标志跨 fork 继承——但 madvise(MADV_DONTFORK) 标记的映射不继承;写 fork-and-exec 守护进程时可用 MADV_DONTFORK 防止敏感内存被复制到子。

      16. CLONE_NEW flags 实现容器*——CLONE_NEWPID 让容器内 PID 1 是 init;Docker/LXC/podman 都基于这些 flags。

      17. unshare(2) 是 clone 的反向操作——Linux 2.6.16+;让进程脱离部分 namespace;与 clone flags 互补。

      18. acct_on.c 需 _BSD_SOURCE 特性测试宏——acct() 不在 SUSv3 中;Linux 在 <unistd.h> 中声明但需 _BSD_SOURCE 启用;现代代码建议 #define _GNU_SOURCE 替代。

        • 跨章衔接:第 24 章 fork 基础 → 第 25 章进程终止 → 第 26 章 wait → 第 27 章 exec → 第 28 章属性对照表与 clone;第 29-33 章线程实现基于 clone flags;第 35 章调度策略在 exec/fork 行为;第 39 章 capabilities 跨 exec 变化;第 49 章资源限制与 cgroup。