c - 在不同进程之间共享打开的文件描述符

标签 c linux

有一个进程打开串口并配置它的终端参数的场景。如何将此文件描述符共享给另外两个进程,这两个进程也将通过串行端口传递数据。 是否可以共享打开的文件描述符?如果是的话,那怎么可能呢?

编辑:请注意一件事,这三个进程不是相关进程(不是父/子进程)

最佳答案

最简单的方法是在 fork 其他进程之前打开并配置文件描述符。这将为您省去最多的麻烦。

假设这是不可能的,有办法。

创建一个 unix 域套接字。让主进程监听该套接字。一旦有人连接到它,就发送带有 SCM_RIGHTS 的 cmsg。在我的 linux 系统上,这记录在手册页“cmsg(3)”中。在我的 MacOS(我编写示例代码的地方)上,我找不到它的记录位置。

现在,在需要特殊文件描述符副本的进程中,打开 unix 域套接字读取 cmsg,您现在有了文件描述符。

这是一些示例代码。这很长,因为整个过程非常冗长。它会做的是不共享任何文件描述符,它将描述符发送到文件'/tmp/magic'给客户端,客户端将向它写入一条消息。传递的文件描述符在客户端和服务器的局部变量 magic 中。

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdio.h>
#include <err.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>

#define SOCKET_NAME "/tmp/socket"

#define SPECIAL_FILE "/tmp/magic"

void
server(void)
{
    struct sockaddr_un sun = { 0 };
    socklen_t sunlen;
    int l, fd, magic;

    if ((magic = open(SPECIAL_FILE, O_RDWR|O_CREAT|O_TRUNC, 0600)) == -1)
        err(1, "open");

    if ((l = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
        err(1, "server socket");

    unlink(SOCKET_NAME);

    sun.sun_family = AF_UNIX;
    snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);

    if (bind(l, (struct sockaddr *)&sun, sizeof(sun)) == -1)
        err(1, "bind");

    if (listen(l, 5) == -1)
        err(1, "listen");

    sunlen = sizeof(sun);

    while ((fd = accept(l, (struct sockaddr *)&sun, &sunlen)) != -1) {
        struct msghdr msg = { 0 };
        struct cmsghdr *cmsg;
        struct iovec iov;
        char buf[CMSG_SPACE(sizeof(magic))];

        iov.iov_base = (void *)"ok";
        iov.iov_len = 2;

        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = buf;
        msg.msg_controllen = sizeof(buf);
        cmsg = CMSG_FIRSTHDR(&msg);
        cmsg->cmsg_level = SOL_SOCKET;
        cmsg->cmsg_type = SCM_RIGHTS;
        cmsg->cmsg_len = CMSG_LEN(sizeof(magic));

        *((int *)(void *)CMSG_DATA(cmsg)) = magic;

        if (sendmsg(fd, &msg, 0) == -1)
            err(1, "sendmsg");
        close(fd);
    }

    close(l);
    unlink(SOCKET_NAME);
}

void
client(void)
{
    struct sockaddr_un sun = { 0 };
    struct msghdr msg = { 0 };
    struct cmsghdr *cmsg;
    struct iovec iov;
    int fd, magic;
    char cbuf[CMSG_SPACE(sizeof(int))];
    char buf[16];

    if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) == -1) {
        err(1, "socket");
    }

    sun.sun_family = AF_UNIX;
    snprintf(sun.sun_path, sizeof(sun.sun_path), SOCKET_NAME);

    if (connect(fd, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
        err(1, "connect");
    }

    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = cbuf;
    msg.msg_controllen = sizeof(cbuf);

    if (recvmsg(fd, &msg, 0) == -1)
        err(1, "recvmsg");

    cmsg = CMSG_FIRSTHDR(&msg);
    assert(cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS);
    magic = *(int *)(void *)CMSG_DATA(cmsg);

    if (write(magic, "hello", strlen("hello")) != strlen("hello"))
        err(1, "write(magic)");

    close(magic);
    close(fd);
}

int
main(int argc, char **argv)
{
    switch (fork()) {
    case 0:
        sleep(2);       /* poor mans synchronization */
        printf("starting client\n");
        client();
        _exit(0);
        break;
    case -1:
        err(1, "fork");
    default:
        printf("starting server\n");
        server();
        exit(1);
    }
    return 1;
}

关于这个例子的一些注释:

  • 它适用于 MacOS,我没有在其他系统上测试过,但同样的方法绝对适用于 *BSD 和 Linux。我没有在其他系统上执行此操作的经验。据称这是 POSIX,因此它应该适用于更多系统。
  • 不需要在消息中包含任何有效负载(因此不需要带有“ok”的 iovec),但出于某种原因,如果没有它我将无法正常工作。
  • 你想要一个更好的套接字位置和权限,“/tmp/socket”在真实系统上是一个糟糕的硬编码名称。
  • 根据经验,我知道 CMSG_* 宏在某些操作系统中会导致对齐问题。我相信我在分配缓冲区和来回进行所有魔法转换时所做的一切都是正确的,但是 YMMV 在严格的对齐架构上。您可能需要分配与 malloc 甚至 posix_memalign 更好对齐的缓冲区。

关于c - 在不同进程之间共享打开的文件描述符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23266720/

相关文章:

linux - bash 中的 Bash 不起作用

c - 如何在终端上显示 Xcode 输出?

c - 需要有关 fork 中 processIDs 的 2 个特定问题的帮助(在类似 unix 的系统上)-C 语言

linux - 如何设置被调试进程的当前目录?

php - 加载脚本时在 SSH2_EXEC 之后停止页面加载

linux - 如何确定上次更新 dhcp 地址的时间

linux - 使用 Bash 从文本文件创建用户

c - 寻找重叠的算法

c - 如何正确声明此功能?

c - 在 c 中查找小于的按位运算符