如何从外部检测进程输出流是否关闭

本文探讨了在Linux系统中如何从外部检测进程输出流是否关闭的技术问题,通过具体代码示例和lsof工具分析管道状态,提供了实用的诊断方法和解决方案。

如何从外部检测进程输出流是否关闭

问题描述

假设在Ubuntu机器上有以下Python脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/env python3
import sys
import time

try:
  i = 1
  while True:
    print(i)
    i += 1
except Exception as e:
  sys.stderr.write(f"We caught an exception {e!r}\n")
  sys.stderr.flush()

while True:
  sys.stderr.write("Sleeping for a minute\n")
  sys.stderr.flush()
  time.sleep(60)

使用以下命令运行:

1
./some_test.py | head -n 10

输出结果为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
1
2
3
4
5
6
7
8
9
10
We caught an exception BrokenPipeError(32, 'Broken pipe')
Sleeping for a minute
Sleeping for a minute
...

进程不会停止。问题是:从外部能否看出输出流已关闭?

诊断分析

使用lsof检查进程文件描述符:

1
lsof -p 28466

输出显示:

1
2
3
4
5
6
7
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF     NODE NAME
python3 28466 root  cwd    DIR  252,3     4096      132 /root
python3 28466 root  rtd    DIR  252,3     4096      128 /
python3 28466 root  txt    REG  252,3  5904904     9540 /usr/bin/python3.10
python3 28466 root    0u   CHR  136,3      0t0        6 /dev/pts/3
python3 28466 root    1w  FIFO   0,13      0t0   110475 pipe
python3 28466 root    2u   CHR  136,3      0t0        6 /dev/pts/3

检查/proc文件系统:

1
ls -l /proc/28466/fd

输出:

1
2
3
4
total 0
lrwx------ 1 root root 64 Oct 20 13:44 0 -> /dev/pts/3
l-wx------ 1 root root 64 Oct 20 13:44 1 -> 'pipe:[110475]'
lrwx------ 1 root root 64 Oct 20 13:44 2 -> /dev/pts/3

技术解答

流实际上并未关闭:能够尝试写入而不收到EBADF错误就是证明(参见man 2 write)。

在Linux上,对于管道,可以检查所有运行中的进程,看是否有其他进程对同一管道持有打开的文件描述符。如果找到一个,则管道仍可写入而无错误;如果没找到,则要么没有适当权限查看读取进程,要么管道不再可用于写入。

使用lsof的示例:

1
2
3
4
5
6
$ sleep 3m | sleep 10 &
[1] 62807 62808
$ lsof -p 62807 -ad 1 +E
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
sleep   62807 stephane    1w  FIFO   0,14      0t0 806533 pipe 62808,sleep,0r
sleep   62808 stephane    0r  FIFO   0,14      0t0 806533 pipe 62807,sleep,1w

10秒后运行相同命令:

1
2
3
$ lsof -p 62807 -ad 1 +E
COMMAND   PID     USER   FD   TYPE DEVICE SIZE/OFF   NODE NAME
sleep   62807 stephane    1w  FIFO   0,14      0t0 806533 pipe

此时,lsof无法找到在62807进程的fd 1上的管道的任何其他读取端打开的文件描述符。

不使用lsof的等效方法(zsh代码):

1
2
3
4
5
$ sleep 3m | sleep 4m &
[1] 63098 63100
$ ls -ogd /proc/*/fd/*(e['[[ $REPLY -ef /proc/63098/fd/1 ]]'])
l-wx------ 1 64 Oct 20 17:12 /proc/63098/fd/1 -> 'pipe:[810032]'
lr-x------ 1 64 Oct 20 17:12 /proc/63100/fd/0 -> 'pipe:[810032]'

这些符号链接的权限指示了每个进程如何打开管道。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计