/proc/self
当我在我的mksh
shell文件夹中寻找时,我发现了一个奇怪的事情:/proc/self/fd/*
里面有所有标准文件描述符(0 表示 stdin、1 表示 stdout 和 2 表示 stderr)一些文件描述符,但也有一些额外的文件描述符 - 24、25、3。从技术上讲,我可以在 shell 中使用 glob 列出它们:
$ for fd in /proc/self/fd/* ; do echo $fd ; done
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/24
/proc/self/fd/25
/proc/self/fd/3
但是当我尝试stat
或使用find
它们时,它们被报告为不存在。
$ find /proc/self/fd/*
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
find: ‘/proc/self/fd/24’: No such file or directory
find: ‘/proc/self/fd/25’: No such file or directory
/proc/self/fd/3
同样的情况发生在bash
,但仅使用一个辅助文件描述符。
$ for fd in /proc/self/fd/* ; do echo $fd; done
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
/proc/self/fd/255
/proc/self/fd/3
$ find /proc/self/fd/*
/proc/self/fd/0
/proc/self/fd/1
/proc/self/fd/2
find: ‘/proc/self/fd/255’: No such file or directory
/proc/self/fd/3
问题是:这些额外的文件描述符是什么?它们的用途是什么?
答案1
探测/proc/self
是一项棘手的工作,因为它会根据每个进程而变化。当您执行 时/proc/self/fd/*
,shell 会扩展通配符,因此它会列出自己的文件描述符。但是当这些被传递给另一个命令(如find
或 )时ls
,路径现在将针对该进程/proc/self
,并且它可能具有或不具有具有这些数字的 fds。
更棘手的是,shell 可能会在通配符扩展期间打开文件描述符。
比较一下/proc/$$/fd
可能会有所启发:
bash
:
$ ls -l /proc/self/fd /proc/$$/fd/* &
[1] 5172
$ lrwx------ 1 muru muru 64 Jan 1 20:16 /proc/4932/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:16 /proc/4932/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:16 /proc/4932/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:16 /proc/4932/fd/255 -> /dev/pts/1
/proc/self/fd:
total 0
lrwx------ 1 muru muru 64 Jan 1 20:24 0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:24 1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:24 2 -> /dev/pts/1
lr-x------ 1 muru muru 64 Jan 1 20:24 3 -> /proc/5172/fd
[1]+ Done ls --color=auto -l /proc/self/fd /proc/$$/fd/*
通过将其发送到后台,我让 bash 打印 PID,你可以看到指向/proc/self/fd/3
' ls
own /proc/<PID>/fd
,它已打开进行扫描。4932
另一方面,带有 的条目用于 bash 的 fds,而特殊的条目是 255。解释可以在这篇文章:
打开的文件为 0 (stdin)、1 (stdout) 和 2 (stderr)。255 是 bash 使用的一个小技巧,用于在重定向时保留这些文件的副本。这是 bash 独有的。
和mksh
:
$ ls -l /proc/self/fd /proc/$$/fd/* &
[1] 5075
$ lrwx------ 1 muru muru 64 Jan 1 20:22 /proc/5074/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:22 /proc/5074/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:22 /proc/5074/fd/10 -> /dev/tty
lrwx------ 1 muru muru 64 Jan 1 20:22 /proc/5074/fd/2 -> /dev/pts/1
/proc/self/fd:
total 0
lrwx------ 1 muru muru 64 Jan 1 20:22 0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:22 1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 1 20:22 2 -> /dev/pts/1
lr-x------ 1 muru muru 64 Jan 1 20:22 3 -> /proc/5075/fd
[1] + Done ls -l /proc/self/fd /proc/$$/fd/*
实际上是同样的事情,只是额外的 fd 是 10,我敢打赌它的原因和 bash 一样,因为源代码表示 shell 使用了 fd 10 及以后的版本。
我没有获得两个或三个额外的 fds,但这可能是由于通配符扩展期间发生的任何事情,或者由于后台作业或其他一些不为人知的原因。
如果我运行你的for
循环,我会得到一个短暂的 fd 3:
$ for fd in /proc/$$/fd/* ; do ls -l $fd ; done
lrwx------ 1 muru muru 64 Jan 2 17:39 /proc/6012/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:39 /proc/6012/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:39 /proc/6012/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:39 /proc/6012/fd/255 -> /dev/pts/1
ls: cannot access '/proc/6012/fd/3': No such file or directory
这里,用来strace
跟踪执行情况:
strace -e open -o log bash -c 'for fd in /proc/$$/fd/* ; do : ; done'
我们将看到第三个 fd 实际上是/proc/<PID>/fd
:
$ tail log
open("/usr/lib/libreadline.so.7", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/libncursesw.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/dev/tty", O_RDWR|O_NONBLOCK) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/gconv/gconv-modules.cache", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/lib/gconv/gconv-modules", O_RDONLY|O_CLOEXEC) = 3
open("/proc/9975/fd/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
+++ exited with 0 +++
现在的问题是,为什么这个 fd 没有出现在之前的ls
测试中?看起来后台处理与此有关:
$ ls -l /proc/$$/fd/* &
[1] 10091
$ lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/255 -> /dev/pts/1
[1]+ Done ls --color=auto -l /proc/self/fd /proc/$$/fd/*
$ ls -l /proc/$$/fd/*
ls: cannot access '/proc/10076/fd/3': No such file or directory
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/2 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 17:46 /proc/10076/fd/255 -> /dev/pts/1
前景ls
显示丢失的 fd。
现在,再次跟踪strace
:
strace -fe open,execve,fork -o log bash -ic 'ls -l /proc/self/fd /proc/$$/fd/* &'
我们看:
10731 execve("/usr/bin/bash", ["bash", "-ic", "ls -l /proc/$$/fd/* &"], [/* 67 vars */]) = 0
10731 open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
# snip
10734 open("/proc/10731/fd/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
10734 execve("/usr/bin/ls", ["ls", "--color=auto", "-l", "/proc/10731/fd/0", "/proc/10731/fd/1", "/proc/10731/fd/2", "/proc/10731/fd/255"], [/* 68 vars */]) = 0
注意 PID 的变化。通配符扩展似乎发生在分叉之后,但变量扩展发生在前那。因此,fd 3 存在,但在不同的进程中。现在如果你使用self
而不是$$
,你会看到 3 和 255:
$ strace -fe open,execve -o log bash -ic 'ls -l /proc/self/fd/* &'
[1] 10790
ls: cannot access '/proc/self/fd/255': No such file or directory
ls: cannot access '/proc/self/fd/3': No such file or directory
lrwx------ 1 muru muru 64 Jan 2 18:04 /proc/self/fd/0 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 18:04 /proc/self/fd/1 -> /dev/pts/1
lrwx------ 1 muru muru 64 Jan 2 18:04 /proc/self/fd/2 -> /dev/pts/1
附录
AUnix&Linux 相关答案Stackexchange 网站引用了邮件列表:
Fd 255 在内部用作与 tty 的连接,因此它不会干扰使用 exec 重新定位 fds。出于同样的原因,Bash 在处理进程替换“<(foo)”时也会分配高 fds。
安德烈亚斯·施瓦布