正常
Linux 进程
进程这一章咱们从进程本身的生命周期起步:头一篇把 fork 的语义、exec 的变身、僵尸的收尸与孤儿的收养走全,收进一个 RAII 的 child_process 封装。全系列公共工具(unique_fd、sys_call、errno_code)沿用思维基石两篇的定义,本目录的文章只引用、不再重定义。
进程创建与生命周期:fork/exec/posix_spawn一个进程怎么生出来、怎么体面地退场:fork 一次调用两次返回,COW 用 smaps_rollup 的 Pss 实证(子进程未写时父子各记一半、写满 64 MiB 后两边之和约 128 MiB 即物理复制,同一变量地址全程不变);fd 整表继承落在共享偏移上(父子接力读,自己另 open 从 0 开始);1 GiB 父进程 fork 要 31.6 ms 而 posix_spawn 恒定 0.48 ms,贵在页表不在页(136 倍);exec 五变体 strace 下全落 execve、execlp 的 PATH 搜索是用户态连试 8 次 ENOENT、不带 CLOEXEC 的 fd 穿过 exec 且偏移延续、失败是普通返回原程序继续跑;僵尸双阶段时序 S→Z→ENOENT、三种收尸姿势对照(阻塞 wait 按 200/401/601ms 收齐、WNOHANG 轮询 33 次空手 30 次、SIGCHLD 加 SA_RESTART 零 EINTR 而去掉实测 2 次)、退出码只有低 8 位(0x1234 到手 0x34)、SIGCHLD 设 SIG_IGN 内核自动收;孤儿收养者实测是 pid 249 的 Relay 而非 systemd,按 subreaper 机制写并注明教科书口径;posix_spawn 的 file_actions 三场景(addopen/adddup2/addclose)与 vfork 只引 man 原文;收在 RAII child_process:构造即 spawn、析构四档处置(SIGTERM 请退、百毫秒不退 SIGKILL)、move-only 进 vector 3/3 清干净,detach 的代价如实记下 State 为 Z守护进程、会话与环境:setsid、双 fork、rlimit 与 environ一个不想跟着终端一起死的进程怎么跟会话、终端、资源限制打交道,六组实验逐项作答:setpgid(0,0) 只改 PGID 一列而 SID 不动、setsid 让 pid==pgid==sid 三位一体、组长再调报 EPERM 的编号道理、kill(-pgid,SIGHUP) 组内三成员同一毫秒各收一条而组外父进程零接收;守护化经典步骤的变化链实测(重头戏):setsid 后 TIOCGSID 变 ENOTTY 但 fd0 的 readlink 仍指旧 tty,描述符不会自动断,这正是重定向那一步的真正理由,fork#1 让 shell 提前收工、孤儿被 249 的 Relay 收养(与上一篇 e4 互证)、fork#2 让孙进程永远当不上会话长、open tty 再获控制终端的路就此断掉、chdir 与 umask 各管什么,daemon(0,0) 返回后仍是会话长的对照,script 关掉 pty 反咬实验自身的活例子;终端的死亡:close(pty master) 后会话长被 SIGHUP 处决、前台组同毫秒各收一条,nohup 的 strace 全过程只有四件事加 exec、零 setsid,disown 与 setsid 的判据是 sid 等不等于自己 pid;rlimit 的 EMFILE、软限双向、硬限单向阀门(降过就回不去)、RLIMIT_STACK 8 MiB 软限对 maps 的 136 KiB、RLIMIT_CPU 软 1 秒 SIGXCPU 可捕获到硬 2 秒 SIGKILL 退出码 137 的双段时序与 SIGXCPU 只见一次的时点解释;environ 数组与字符串都住在 [stack] 段(argv[0] 与 environ[0] 相距 13 字节)、putenv 不拷贝改缓冲区 getenv 跟着变的所有权陷阱、首次 setenv 后数组从栈搬到堆、execle 白名单 2 条对 execve 全量 70 条;/proc 速览收尾:R/S/Z 三态、cmdline 的 NUL 拼接 22 字节、僵尸 cmdline 为空、收尸后目录蒸发;Windows 侧无会话与控制终端、新组默认带隐式忽略位广播 Ctrl+C 不响应的实测口径对照IPC:管道、FIFO 与 POSIX 消息队列两个进程怎么交换数据,七组实验把内核给的通道走全:管道写满 65536 字节后第 17 块 write 从 +0.2 ms 卡到 +2000.3 ms、读者读走一块即放行的流控时序,读端全关 SIGPIPE 加 EPIPE(errno 32) 而写端全关 read 返 0 的 EOF;pipe2(O_CLOEXEC) 三姿势用 /proc/self/fd 自证标志逐 fd、dup2 把 stdout 接进管道而 stderr 落终端的工程版;strace 改教科书:glibc 2.44 的 popen 实际走 pipe2 加 clone3(CLONE_VM|CLONE_VFORK) 的 posix_spawn 路数而非 fork(glibc 2.29 起如此);FIFO 让无关进程按路径相会(读端 open 干等 801.1 ms、双向互等各 500.1 ms)、O_WRONLY|O_NONBLOCK 无读者回 ENXIO 而 O_RDONLY|O_NONBLOCK 无写者成功、紧接着 read 返 0 的 EOF 陷阱(poll 上永真就绪)、4096==PIPE_BUF 多轮零撕裂而 8192 概率撕裂(撕出 33 窗口、整条释放时被掩盖、按页释放立刻显形);POSIX mq 的实体落在 /dev/mqueue、非特权上限 msg_max=10/msgsize_max=8192、三条 10/50/200 字节按 prio 1→3→2 发而按 3/2/1 收的边界保留,对照 pipe 第一次 read(64) 就把三条拼团、mq_notify 的 si_code=-3(SI_MESGQ) 与一次性注册(第二条到货信号不来);POSIX 信号量两名额三进程 worker2 干等 699.4 ms 到 post 放行、sem_unlink 与 shm_unlink 同构;SCM_RIGHTS 传 fd 是复制引用非移交(接收方拿到自己的新编号 fd=4、fdinfo 证 pos 16→32→56 接续、发送方 close 原始 fd 后照读);收在六通道适用矩阵、决策序与带宽对照(shm 7304 ≫ pipe 2349 ≥ mq 1245 MiB/s,引内存篇基线不重测)信号(上):sigaction 与异步信号安全信号怎么到达、handler 里能做什么,七组实测正面回答:阻塞期连发 3 次 SIGUSR1 只 pending 1 位、解阻塞后 handler 只跑 1 次(标准信号不排队),投递精确落在解阻塞那次 sigprocmask 的返回路上,handler 行永远插在主循环两行 write 之间从不劈行;sigaction 家族实测 SA_SIGINFO 三种 si_code(raise 的 SI_TKILL、他人 kill 的 SI_USER、itimer 的 SI_KERNEL)、sa_mask 连自身一起屏蔽、SA_NODEFER 同信号嵌套、SA_RESETHAND 二次触发死于 SIG_DFL,signal() 读回 sa_flags=0x14000000=SA_RESTART|glibc 内部 SA_RESTORER(蹦床用,BSD 语义持久);重头戏是异步信号安全:handler 里 printf 的增行大头是形态完好的整行重复(独立复跑实测 648 个行号各完好出现两次,缓冲写入位置回卷后同一行从头重拷,grep 完好模式一个也筛不出),少数才是碎片——存档 run 的损坏帧索引恰好 21 帧(最狠一帧 M 009[H 行号拷到一半被插、无 handler 痕迹的纯损坏帧,病根是共享缓冲的半更新而非交错),glibc 2.44 的 stdio 锁同线程递归、教科书式死锁没出现,出现的是状态损坏,write 安全指不损坏自身状态但 DONE 仍被粘进未 flush 的缓冲行;可重入篇缺 volatile 的忙等被 -O2 提出加载、汇编只剩 jmp .L5、循环里连圈数计数都没有,timeout 3 秒杀到 exit 124 输出停在 busy-waiting,volatile 版每圈 movl、5.38 亿圈后干净退出,strtok/localtime 的静态状态模拟与 _r 后缀辨析(strtok_r 在安全表、localtime_r 也不在);工程模式 flag/self-pipe/siglongjmp 三件(siglongjmp 引内存管理篇);SIGCHLD 三子退出合流 1 位、循环 waitpid 收 3 而 naive 只收 1 留俩僵尸、SA_NOCLDSTOP/SA_NOCLDWAIT;收尾 SIGKILL/SIGSTOP 不可捕获实测 sigaction 直接 EINVAL 与常用信号速览表信号(下):实时信号、signalfd 与 pidfdhandler 之外信号还有哪些更好的消费方式,六组实验给出三条出路:实时信号补排队的课(本机 SIGRTMIN=34/SIGRTMAX=64,glibc 的 NPTL 占掉 32/33;阻塞期 sigqueue 连发 3 次 SIGRTMIN+1,handler 跑满 3 次且 101/102/103 按 FIFO 到账,对照 SIGUSR1 三发只记 1 次;si_value 原样读回、union sigval 同一字节两种读法),投递顺序立小节讲反直觉:内核出队小号在前(sigwaitinfo 与全屏蔽 sa_mask 两路证明、与 man 7 signal(7) 口径一致),空 sa_mask 批量放行时信号帧按出队序叠栈、后叠的执行在前,handler 看到的是大号倒挂(64 先于 35×3),同号 FIFO 恒成立,跨号顺序别依赖 handler;signalfd 的纪律是阻塞在前创建在后(不阻塞则 handler 消费、poll 200ms 零事件的漏信号实证),读走即消费与 handler 双向互斥,一次 read 384 字节 3 条 signalfd_siginfo、siginfo 与 signalfd_siginfo 字段对照表,与 timerfd 混挂 151/202/301/402ms 交错时间线(self-pipe 的内核原生版),动态扩掩码不换 fd、FD_CLOEXEC 位实测创建时定性;pidfd 重头戏:poll 得 POLLIN 后 waitid(P_PIDFD) 拿退出码 42 的第三种收尸、收尸后再 poll 得 POLLIN|POLLHUP、二次 waitid ECHILD,pidfd_send_signal 带 sival_int=777 到账而内核不代填 si_pid 的实测,pidfd_getfd 父到子通兄弟 EPERM(yama ptrace_scope=1 的边界),pid 复用竞态真实现场(新 user+pid namespace 烧低号段绕开 RESERVED_PIDS,kill(老数字 301) 返回 0 误伤新进程 B 而 pidfd_send_signal(老句柄) 拿 ESRCH),glibc 2.44 有 sys/pidfd.h 封装而实验走裸 syscall 的编号教学,三种收尸路径对比表;sigwaitinfo 零 handler 定点取、与 signalfd 同一条 pending 队列双向互斥谁读谁消费(21/22、31/32 双向验证,一个阻塞集别开两个消费者);压轴优雅关闭:mini prefork echo 服务器里 SIGTERM 从 signalfd 进事件循环,状态机 RUNNING 到 DRAINING(close listener、worker drain 干完在途)到 REAPING(poll pidfd 逐个 waitid)到 EXIT 全时序,朴素 flag 版对照(SA_RESTART 下旗子 t=200 立了没人看、t=503 才生效还多接一条,不开则 accept/read/recv 处处查旗),开发中真踩的继承 listen fd 与 SIGPIPE 两处如实记;收尾十设施 x86-64 syscall 编号速查表(rt_sigaction 13/rt_sigqueueinfo 129/rt_sigtimedwait 128/signalfd4 289/pidfd 三件 434/424/438/waitid 247)与 Windows 无信号、控制台事件加 WaitFor 家族的纯文字预告
进程章的五篇至此齐了,后续的章节咱们按总纲的学习路线接着走。