c语言 - 如何正确读取进程的文件描述符?



我正在编写一个小程序来列出特定进程的文件描述符,但我很难理解结果。我正在检查的过程如下:

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的配置文件中。我对pipefork的工作原理有基本的了解,但我似乎不明白这里发生了什么。

如有任何帮助或见解,我们将不胜感激,谢谢!

我要注意的第一件事是,与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]'

我看到的父进程的文件描述符都是有意义的。回想一下,STDINSTDOUTSTDERR的文件描述符分别是012。所有这些文件描述符都指向/dev/pts/0,它是一个伪终端(用于运行代码的终端(。接下来,父进程中的文件描述符45分别对应于管道的读写端。回想一下,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复制到STDINSTDOUT,这就是为什么您看到与STDIN_FILENO关联的第一个管道的读取端和与STDOUT_FILENO关联的第二个管道的写入端。最后一个文件描述符,在本例中为STDERR_FILENO/dev/pts/0仍然连接到终端,因为您没有为此调用dup2

我不知道你想用你的程序做什么,但我有几个问题/注意事项要问你:

  • 为什么在分叉一个子进程时使用两个管道?对于两个进程之间的通信,在大多数情况下只需要一个管道(请记住,一个管道有两个用于读取和写入的文件描述符(

最新更新