【问题标题】:Can I share a file descriptor to another process on linux or are they local to the process?我可以将文件描述符共享给 linux 上的另一个进程,还是它们是进程本地的?
【发布时间】:2011-01-22 10:54:56
【问题描述】:

假设我有 2 个进程,ProcessA 和 ProcessB。如果我在 ProcessA 中执行 int fd=open(somefile),我可以通过 IPC 将文件描述符 fd 的值传递给 ProcessB 并让它操作同一个文件吗?

【问题讨论】:

标签: c linux


【解决方案1】:

您可以通过unix domain 套接字将文件描述符传递给另一个进程。 这是传递这样一个文件描述符的代码,取自Unix Network Programming

ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);

    cmptr = CMSG_FIRSTHDR(&msg);
    cmptr->cmsg_len = CMSG_LEN(sizeof(int));
    cmptr->cmsg_level = SOL_SOCKET;
    cmptr->cmsg_type = SCM_RIGHTS;
    *((int *) CMSG_DATA(cmptr)) = sendfd;
#else
    msg.msg_accrights = (caddr_t) &sendfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    return(sendmsg(fd, &msg, 0));
}
/* end write_fd */

这是接收文件描述符的代码

ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
    struct msghdr   msg;
    struct iovec    iov[1];
    ssize_t         n;
    int             newfd;

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    union {
      struct cmsghdr    cm;
      char              control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr  *cmptr;

    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
#else
    msg.msg_accrights = (caddr_t) &newfd;
    msg.msg_accrightslen = sizeof(int);
#endif

    msg.msg_name = NULL;
    msg.msg_namelen = 0;

    iov[0].iov_base = ptr;
    iov[0].iov_len = nbytes;
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;

    if ( (n = recvmsg(fd, &msg, 0)) <= 0)
        return(n);

#ifdef  HAVE_MSGHDR_MSG_CONTROL
    if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
        cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
        if (cmptr->cmsg_level != SOL_SOCKET)
            err_quit("control level != SOL_SOCKET");
        if (cmptr->cmsg_type != SCM_RIGHTS)
            err_quit("control type != SCM_RIGHTS");
        *recvfd = *((int *) CMSG_DATA(cmptr));
    } else
        *recvfd = -1;       /* descriptor was not passed */
#else
/* *INDENT-OFF* */
    if (msg.msg_accrightslen == sizeof(int))
        *recvfd = newfd;
    else
        *recvfd = -1;       /* descriptor was not passed */
/* *INDENT-ON* */
#endif

    return(n);
}
/* end read_fd */

【讨论】:

  • 请注意,文件描述符的实际数值通常在两个进程中会有所不同。
  • 您可以通过这种方式传递数字。这并不神奇地意味着它将在两端作为同一文件的文件描述符。
  • @EJP SCM_RIGHTS 的想法是它会。虽然没有想到,但我敢肯定有一些警告。 (即,这个概念很像 dup(),但在不相关的进程之间)
【解决方案2】:

如果两个进程属于同一个用户,那么你可以简单地使用procfs。

char fd_path[64];  // actual maximal length: 37 for 64bit systems
snprintf(fd_path, sizeof(fd_path), "/proc/%d/fd/%d", SOURCE_PID, SOURCE_FD);
int new_fd = open(fd_path, O_RDWR);

当然你需要一些IPC机制来共享SOURCE_FD的值。参见例如“Linux C: upon receiving a signal, is it possible to know the PID of the sender?”。

【讨论】:

  • 您是指 snprintf 中的“/proc/%d/fd/%d”吗?
  • 对于未来的读者:这适用于真实文件,但绝对不适用于未命名的域套接字 (socketpair()),可能不适用于命名域套接字,我不是确定管道。对于域套接字,如果您在文件系统路径上调用open(),您将获得ENXIOconnect() 会失败,因为它只在未连接的域套接字的上下文中才有意义; bind() 会说地址已在使用中。
【解决方案3】:

在 2020 年,在 Linux 5.6 及更高版本上,向 Linux 添加了一个新的系统调用,这将使一个进程能够获取另一个进程的文件描述符的副本,该进程的文件描述符由带有 pidfd_getfd() 系统调用的 pidfd 引用.

【讨论】:

    【解决方案4】:

    您可以使用此线程中描述的方法 nos,或者(更传统的)方式,通过创建它在相关进程(通常是父子或兄弟姐妹)之间共享它,分叉的进程会自动接收副本。

    确实,分叉的进程会获取您所有的 FD,并且可以使用它们,除非它们关闭它们(这通常是个好主意)。

    因此,如果一个父级派生了两个子级,如果他们都有一个他们没有关闭的文件描述符,它现在是共享的(即使父级随后关闭它)。例如,这可能是从一个孩子到另一个孩子的管道。这就是 shell 重定向的方式

    ls -l | more
    

    工作。

    【讨论】:

      【解决方案5】:

      注意上例中,接收时变量的设置,如:

      msg.msg_name = NULL;
      msg.msg_namelen = 0;
      
      iov[0].iov_base = ptr;
      iov[0].iov_len = nbytes;
      msg.msg_iov = iov;
      msg.msg_iovlen = 1;
      

      不是必需的。带有标头的消息结构的整个想法是,接收站点不必知道它读取了什么,并且可以通过检查(第一个)标头,它是什么类型的消息以及期望什么。

      【讨论】:

      • 虽然您在技术上是正确的,但在这种情况下指定缓冲区是有充分理由的:为了发送 OOB 消息(在这种情况下为套接字控制消息),您需要指定一个非-空消息(参见 unix_stream_sendmsg,例如lxr.free-electrons.com/source/net/unix/af_unix.c#L1836)。在没有 iovec 的情况下接收时,Linux 将一遍又一遍地传递该消息。因此,要读取多条 OOB 消息,您必须在某个时间点读取消息数据。
      猜你喜欢
      • 2017-07-08
      • 1970-01-01
      • 1970-01-01
      • 2015-02-06
      • 1970-01-01
      • 1970-01-01
      • 2011-10-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多