我正在编写一个小程序来列出特定进程的文件描述符,但我很难理解结果。我正在检查的过程如下:
int main() {
int fds1[2];
int fds2[2];
pipe(fds1);
pipe(fds2);
pid_t pid = fork();
if (pid == 0) {
dup2(fds1[0], STDIN_FILENO);
dup2(fds2[1], STDOUT_FILENO);
close(fds1[0]);
close(fds1[1]);
close(fds2[0]);
close(fds2[1]);
sleep(2);
return 0;
}
close(fds1[0]);
close(fds2[1]);
waitpid(pid, NULL, 0);
return 0;
}
fd检查程序代码如下所示:
let path_str = format!("/proc/{}/fd", self.pid);
let dir = Path::new(&path_str);
let mut fds = Vec::new();
for entry in fs::read_dir(dir).ok()? {
let path = entry.ok()?.path();
let filename = path.file_name()?;
let fd = fname.to_str()?.to_string().parse::<usize>().ok()?
fds.push(fd);
}
ls -l /proc/{pid}/fd
在运行上述程序时的结果给了我以下列表:
0 -> /dev/pts/6
1 -> /dev/pts/6
2 -> /dev/pts/6
22 -> /dev/pts/1
30 -> /dev/pts/4
4 -> /home/{user}/.spectrwm.conf
5 -> 'pipe:[168640]'
6 -> 'pipe:[168641]'
我很困惑为什么/dev/pts/x
有5个符号链接,为什么底部的3个fds包含在这个过程的文件描述符中,尤其是我的WM的配置文件中。我对pipe
和fork
的工作原理有基本的了解,但我似乎不明白这里发生了什么。
如有任何帮助或见解,我们将不胜感激,谢谢!
我要注意的第一件事是,与spectrwm.conf
相关的文件描述符可能是由您的窗口管理器以某种方式打开的(可能是通过LD_PRELOAD或类似的方式(。您可以通过使用strace
来查看打开了哪些文件描述符以及如何打开(但是,在这种情况下,您应该使用标志-ff
,因为您正在分叉一个新进程(。
您还可以使用GDB来确定何时进行某些系统调用(即打开(,以确定流程在执行中的哪个点打开与/home/{user}/.spectrwm.conf
相关的文件描述符(请参阅捕获点(。
至于其他文件描述符,为了帮助您了解发生了什么,我再次运行了您的代码。以下是父进程的文件描述符:
user@pop-os:~$ ls -l /proc/39783/fd
total 0
lrwx------ 1 user user 64 Mar 21 14:03 0 -> /dev/pts/0
lrwx------ 1 user user 64 Mar 21 14:03 1 -> /dev/pts/0
lrwx------ 1 user user 64 Mar 21 14:03 2 -> /dev/pts/0
l-wx------ 1 user user 64 Mar 21 14:03 4 -> 'pipe:[544306]'
lr-x------ 1 user user 64 Mar 21 14:03 5 -> 'pipe:[544307]'
我看到的父进程的文件描述符都是有意义的。回想一下,STDIN
、STDOUT
和STDERR
的文件描述符分别是0
、1
和2
。所有这些文件描述符都指向/dev/pts/0
,它是一个伪终端(用于运行代码的终端(。接下来,父进程中的文件描述符4
和5
分别对应于管道的读写端。回想一下,libcpipe
函数为读取和写入端填充一个长度为2的数组(但您关闭了与管道相关的两个文件描述符——管道fds1
的读取端和管道fds2
的写入端,因此打开的文件描述符在父级中是有意义的(。
下面是子进程的文件描述符:
user@pop-os:~$ ls -l /proc/39784/fd
total 0
lr-x------ 1 user user 64 Mar 21 14:03 0 -> 'pipe:[544306]'
l-wx------ 1 user user 64 Mar 21 14:03 1 -> 'pipe:[544307]'
lrwx------ 1 user user 64 Mar 21 14:03 2 -> /dev/pts/0
这些文件描述符也是有意义的。回想一下,dup2
创建文件描述符的副本,但将复制的文件描述符分配给特定的编号。这允许您执行进程间通信(也是shell用来实现管道操作符|
的东西——只需分叉n
进程,创建n - 1
管道,并创建适当的dup2
调用(。
在您的子进程中,我们可以看到您将管道fds复制到STDIN
和STDOUT
,这就是为什么您看到与STDIN_FILENO
关联的第一个管道的读取端和与STDOUT_FILENO
关联的第二个管道的写入端。最后一个文件描述符,在本例中为STDERR_FILENO
,/dev/pts/0
仍然连接到终端,因为您没有为此调用dup2
。
我不知道你想用你的程序做什么,但我有几个问题/注意事项要问你:
- 为什么在分叉一个子进程时使用两个管道?对于两个进程之间的通信,在大多数情况下只需要一个管道(请记住,一个管道有两个用于读取和写入的文件描述符(