进程的文件描述符可以在 /proc/pid/fd 目录中看到,其中一些可以打开并读取或写入。例如,如果我将某些内容通过管道传输到 stdin 上的进程,它将显示为 /proc/p...
进程的文件描述符可以在 /proc/pid/fd 目录中看到,其中一些可以打开并读取或写入。例如,如果我通过管道将某些内容传输到 stdin 上的进程,它将显示为 /proc/pid/fd/0:\'0 -> 'pipe:[19918]'|\' (ls -lF);然后我可以从另一个进程读取它,数据有时会传输到一个进程,而另一些则传输到另一个进程。
但是,如果文件描述符是套接字,则此方法不起作用。它显示为 /proc/pid/fd/3:\'3 -> 'socket:[8577]'=\',但打开它会导致出现“无此设备或地址”错误。
对于第一种情况,fd 似乎变成了命名管道,但在第二种情况下,它似乎没有变成命名套接字。现在,虽然有命名套接字,但它们是由 bind 创建的,并且必须连接到。上面的套接字将是 accept 或 socket/connect 的结果,目前还不清楚如何连接到它。
因此 open 不起作用,connect 也不起作用,因为 fd 没有被监听。有什么办法吗?在我的示例中,两种 fd 类型都在 ls 列表中显示一个数字 - 管道为 19918,套接字为 8577,表示有一个特定的内核端点可以附加到(?),但我不知道可以使用哪个 API 来附加到它。有吗?
在 Linux 中,文本表示中的数字 socket:[#]
是套接字的 inode 编号,请参阅 proc_pid_fd(5) 。 inode 编号用于标识,但不用于寻址(与文件系统路径一样),因此您不能使用 open()
它们,等等。因此,inode 编号不是(如您所说)“内部端点”,而只是标识符(并且甚至不一定随着时间的推移而唯一,因为它们可以重复使用)。
据我所知,你可以复制链接到的 fd,甚至可以跨进程复制。你可以在我的 Go fd 泄漏检查器中的 NewSocketFd .
请记住,两个 fds(另一个进程中的 fd 以及您进程中的重复 fd)都引用同一个套接字、文件等,因此请注意不要更改 fd 引用的资源的状态。
使用重复的 fd,您可以查询套接字配置;另请注意, fdinfo
包含带有 fd 详细信息的伪文本文件 fd
的同级目录 proc_pid_fdinfo(5) .
TheDiveO 的回答给了我一个线索。他的 golang NewSocketFd 使用 Linux 系统调用 pidfd_getfd,自内核 5.4 开始可用。它可以复制和读取由 pidfd_open 创建的 fd 打开的进程的 fd。pidfd_getfd 操作需要 PTRACE_MODE_ATTACH_REALCREDS 权限,这意味着除非您以 root 身份运行,否则这仅适用于获取子进程的 fd,而不仅仅是任何随机进程。以下是写入另一个进程套接字的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
static int pidfd_open (pid_t pid, unsigned flags)
{ return syscall (SYS_pidfd_open, pid, flags); }
static int pidfd_getfd (pid_t pid, unsigned targetfd, unsigned flags)
{ return syscall (SYS_pidfd_getfd, pid, targetfd, flags); }
int main (int argc, const char* const* argv)
{
if (argc != 3) {
printf ("Usage: %s <pid> <fd>\n", argv[0]);
return EXIT_SUCCESS;
}
int pidfd = pidfd_open (atoi(argv[1]), 0);
if (pidfd < 0) {
perror ("pidfd_open");
return EXIT_FAILURE;
}
int tfd = pidfd_getfd (pidfd, atoi(argv[2]), 0);
if (tfd < 0)
perror ("pidfd_getfd");
else {
static const char c_test_data[] = "hello world";
if (sizeof(c_test_data) != write (tfd, c_test_data, sizeof(c_test_data)))
perror ("write");
close (tfd);
}
close (pidfd);
return EXIT_SUCCESS;
}