ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PHASE 4

PHASE 4 process group process tree情景前面 Phase 3 解决的是workload 不结束时可以终止 child。但真实 CI 命令往往不是只有一个进程。例如TaskForge │ └── shell │ └── g│ ├── cc1plus └── assembler也就是说一个任务可能启动一串后代进程。这时候如果 timeout 发生你只杀直接 child可能出现child 死了但后代还活着结果就是CPU 还在占内存还在用日志还在写pipe 写端可能还没关闭引入进程组和进程树process tree进程树看父子关系process group进程组看组成员关系。process tree → 谁是谁生出来的 process group → 哪些进程可以作为一组一起管理/发信号不一定代表并列关系为什么做process grouptimeout/cancel ↓ 不是只杀直接 child ↓ 而是尽量把属于这个任务的一组进程一起处理PGIDPID → 标识“这个进程是谁” PGID → 标识“这个进程属于哪个进程组”也就是PGID100├── PID100├── PID101├── PID102└── PID103代码setpgid(0, 0);第一个 0→ “当前进程自己”第二个 0→ “用当前进程自己的 PID 作为 PGID”所以如果当前 child PID500执行setpgid(0,0);效果大致就是 PID500PGID500这时它既是这个进程组的成员也是group leader进程组组长如果它后面正常创建一些子进程这些后代通常也会继承这个 process group。于是可能变成PGID500PID500→ shell PID501→ gPID502→ cc1plus PID503→ assembler 这样 TaskForge 后面就有机会不是只针对 PID500而是针对 整个 PGID500统一发信号。父子双方都要setpgid()因为fork后会有race不知道fork() 以后parent 和 child 谁先运行TaskForge 不能赌 “child 一定会在我需要之前把 setpgid 做好”。因为如果只靠 child 设万一parent 急着要管理比如要杀进程组了 ↓ 但 child 还没轮到执行、还没 setpgid ↓ 进程组还没建立 ↓ parent 的 kill 打不到正确的组但 TaskForge 希望尽快把 child 放进属于这个任务的新进程组里。所以这里采用一种双保险child exec 前自己 setpgid parent 也尝试给 child 设置 PGID但不能保证设置pgid一定成功因为parent的setpgid(child_pid,child_pid)也可能失败所以接下来要getpgid(child)确保这个 child 实际上的 PGID 是不是确实等于 child 自己的 PIDtaskforge的终止策略先发 SIGTERM等待一段 grace period宽限期如果目标还没退出再升级到 SIGKILL。sigterm和sigkillSIGTERM → “请你终止” → 目标程序可以处理也可以忽略 SIGKILL → “强制终止” → 不能被捕获或忽略也就是先 SIGTERM ↓ 给程序机会正常收尾 如果还不退出 ↓ 再 SIGKILL发送对象的区别kill(pid,SIGTERM);//给一个进程发 SIGTERM。而kill(-pgid,SIGTERM);//给整个 process group 发 SIGTERM。这里的-pgid不是说“负 PID”。这个负号是 kill() 接口的一种特殊语义负数表示按进程组发送。为什么不一上来就sigkill?因为程序可能需要做正常收尾例如写完日志 关闭文件 释放临时资源 保存必要状态在这个终止过程中TaskForge 仍然要继续读取 stdout/stderr。因为程序收到 SIGTERM 后可能还在做最后的日志输出。如果 parent 这时候不读 pipe又可能把 pipe 填满导致清理过程反而卡住。如果过来宽限期还没退出就SIGKILL→ 强制终止主结果和清理结果为什么要分开关键场景直接 child 自己已经正常 exit(0)但它之前创建的某个后代进程还活着。那么可以同时出现 主进程结果 成功正常退出 清理结果 还需要终止残留后代 甚至清理可能失败所以保存两个清理结果outcome → 这个任务/直接 child 最终怎么结束 cleanup_outcome → 残留进程和清理过程处理得怎么样实际保存范围TaskForge 不能夸大成“能杀掉所有后代进程”。TaskForge 能比较可靠地清理的是仍然留在这个任务所拥有 PGID 里的成员。但如果某个后代自己主动“逃出去”比如调用setsid()建立新的 session会话或者主动切换到别的 process group那么它就可能已经不属于原来的 PGID 了。这时kill(-pgid, SIGTERM);只能覆盖仍然属于这个 pgid 的进程父子关系存在不代表进程仍然属于同一个 process group。shell PGID500│ ├── A PGID500│ └── B 原本 PGID500后来调用setsid()→ 离开原进程组混淆kill(-pgid,signal)→ 按进程组发送信号waitpid(child_pid,...)→ 回收直接 childTaskForge 可以通过 kill(-pgid, signal) 对整个进程组发信号但 waitpid() 主要用于回收它自己的直接 child普通后代并不会因为处在同一个 PGID 里就自动都变成 TaskForge 可以逐个 waitpid() 的对象。
返回列表