IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Unix 如何杀死一个进程和它的子孙进程? -> 正文阅读

[系统运维]Unix 如何杀死一个进程和它的子孙进程?

最近周末在维护一个开源项目,叫做 air。它是一个 Golang 的热加载代码的工具,会监听本地的文件变化,然后自动重新加载。

遇到的问题

最近遇到一个特别有意思的问题,就是使用 kill -9 pid 命令杀死进程的时候虽然会杀死它的子进程,但是它的孙子进程还是会继续存活。

背景

简而言之,就是我们的热加载组件会运行命令,然后会监听文件变化,一旦文件变化就会 kill 掉之前进程,然后重新编译代码,再执行运行的命令。

但是遇到一个用户提了这样一个问题: https://github.com/cosmtrek/air/issues/216#issuecomment-982348931 在执行命令的时候使用 dlv exec --accept-multiclient --log --headless --continue --listen :2345 --api-version 2 ./tmp/main 来运行代码与开启调试,我们的组件不会彻底的将进程杀死,而是会继续存活。导致下次一次起来的时候对应的端口会被占用。

排查问题

通过 ps -efj | grep "tmp/main" 能很清楚的看到实际上运行这条命令会起来三个进程

1
2
3
1594910868 75277 74711   0 10:09PM ttys005    0:00.14 dlv exec --accep xt       75277      0    1 S    s005
1594910868 75280 75277   0 10:09PM ttys005    0:00.02 /Library/Develop xt       75280      0    1 S+   s005
1594910868 75281 75280   0 10:09PM ttys005    0:00.01 ./tmp/main       xt       75280      0    1 SX+  s005

而且是很清晰的能看出来,进程的祖孙关系:

75277 是父进程

75280 是子进程

75281 是孙进程

如果你只是采用 kill -9 pid 来杀死进程,那么它的子进程也会被杀死,但是孙子进程还是会继续存活。

1
2
3
> kill -9 75277
> ps -ef | grep "tmp/main"
1594910868 75281     1   0 10:09PM ttys005    0:00.01 ./tmp/main

可以发现只剩下 75281 这个进程了,而且这个进程的父进程现在变成了 1, 孤儿进程了。属实是孤儿了。

如果这个进程还继续占用着端口,会造成下次执行命令的时候无法正常热加载。

解决方案

查阅了各种资料之后,找到了一个很好的解决方案: 使用 pgid 参数来让进程组的进程共享一个进程组号。

1
2
3
4
  PID  PPID  PGID   UID   C STIME   TTY             TIME CMD              
75837 74711 75837 1594910868   0 10:22PM ttys005    0:00.23 dlv exec --accep 
75840 75837 75840 1594910868   0 10:22PM ttys005    0:00.02 /Library/Develop 
75841 75840 75840 1594910868   0 10:22PM ttys005    0:00.01 ./tmp/main       

可以看到第三列就是对应的 pgid, 虽然我们使用命令启动的 pgid 不同,但是我们可以使用 Golang 来设置进程组号,这样就可以共享进程组号了。

同时在 kill 进程的时候,也要使用这个 pgid 参数,这样就可以杀死对应的进程组了。可以参考 man kill

Negative PID values may be used to choose whole process groups; see the PGID column in ps command output.

就是对于 pid 的代表的是 PGID 也就是整个进程组,kill 的时候会将整个进程组中的进程杀死。

虽然在上面的命令中是没办法共享进程的,但是对于这个 bug 来说,我们可以使用 Setpgid 来开启 PGID,这样启动的进程就可以共享进程组号了。同时使用 syscall.Kill(-pgid, 15) 来杀死进程组。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cmd := exec.Command( some_command )
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Start()

pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err == nil {
syscall.Kill(-pgid, 15) // note the minus sign
}

cmd.Wait()

总结

在单测中添加相关的单测,保证 kill 所有子进程这个行为不会因为迭代而丢失。https://github.com/cosmtrek/air/commit/1c27effe33a180f3fbbcee8f2d9ea7122d89a50b#diff-6266cec6be43e607de84d431f656ea78fac62405058d84312d9c12f3f52c7462R146

参考资料

推荐环节

最后最后和大家分享一些最近在看的好文,想过用周刊的方式发送但是因为看的比较零散,就放在每篇博文的最后,希望大家能够收获!

  • 間歇高效率的番茄工作法 看了一个news letter的文章,发现了一个至关重要的技巧: 如果你为一个任务设置了一个番茄钟,但是提早完成了,比方说你为一本书的某个章节记笔记,但你提早完成了 - 你不应该立即进入到下一个任务,或者提早结束这个番茄钟。

  • 在日本写无限 alert 会被抓

  • Golang 检查两个模块依赖好用命令

    • go mod graph
    • go mod why -m “module”

LastMod 2022-01-24 (92e9596)

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-01-25 10:59:37  更:2022-01-25 11:00:30 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/16 7:34:17-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码