进程间传递打开的文件描述符,并不是传递文件描述符的值。先说一下文件描述符。

文件描述符

对内核来说,所有打开的文件都会通过文件描述符引用,文件描述符在进程中是一个非负整数,文件描述符在进程中是从0开始,默认0与标准输入关联、1与标准输出关联、2与标准出错关联。之后进程每打开一个文件或者创建一个新文件的时候,内核都会向进程返回一个文件描述符来表示这个文件,文件描述符是递增的。文件描述符的值与文件没有必然的联系,只是该文件在进程中的一个标识,所以同一文件在不同进程中的文件描述符可能不一样,相同值得文件描述符在不同进程中可能标识不同得文件。文件描述符的取值范围是0~OPEN_MAX。

接下来要明白文件共享涉及的数据结构。

文件数据结构

内核使用三种数据结构来表示打开的文件:

  • 进程中有一个文件描述符表,每个文件描述符占用一项内容,文件描述符与指向对应文件表的指针相关联
  • 内核为进程中打开的文件维护一张文件表,包含文件的状态标志、偏移量、指向v节点的指针
  • 每个打开的文件都有一个v节点,包含了文件类型、对文件进行操作的函数指针、包括i节点

三者的关系如下:

共享文件

在进程间传递文件描述符是非常有用的,通过传递文件描述符,可以让其他进程拥有对文件操作的能力,在网络编程中体现比较多,比如nobody进程协助创建了数据连接,然后将socket的文件描述符传递给服务进程,由服务进程进行数据传输。

这是两个进程分别打开同一文件的情况:

这是进程共享文件描述符的状态·:

所以共享文件描述符就是将不同文件描述符指向一个文件表。这一点与fork产生的父子进程共享已打开的文件描述符是一样的。还要注意,一般文件在关闭文件描述符之后就关闭文件了,但是共享文件的情况不一样,共享文件要等到所有引用的文件描述符关闭之后才可关闭。

UNIX域socket实现传递文件描述符

可以通过UNIX域socket来传递文件描述符,实际是调用了socket中的sendmsg和recvmsg函数,利用sendmsg和recvmsg可以发送附属数据,附属数据可以是是文件描述符,两个函数的原型如下:

  1. ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  2. ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
  3. struct msghdr {
  4. void *msg_name; /* optional address */
  5. socklen_t msg_namelen; /* size of address */
  6. struct iovec *msg_iov; /* scatter/gather array */
  7. size_t msg_iovlen; /* # elements in msg_iov */
  8. void *msg_control; /* ancillary data, see below */
  9. size_t msg_controllen; /* ancillary data buffer len */
  10. int msg_flags; /* flags on received message */
  11. };
  12. //套接口地址成员msg_name与msg_namelen。
  13. //I/O向量引用msg_iov与msg_iovlen。
  14. //附属数据缓冲区成员msg_control与msg_controllen。
  15. //接收信息标记位msg_flags。

其中,对于传递文件描述符有用的成员为:msg_control和msg_controllen,需要注意的是,如果想利用它传递辅助信息,比如文件描述符,必须携带至少一个字节的真实数据,也就是iov指针指向的缓冲区要有数据,iovlen至少是1。

  • msg_control:指向附属数据缓冲区
  • msg_controllen:msg_control所指向的这块缓冲的长度

传递附属数据cmsghdr的结构如下:

  1. struct cmsghdr {
  2. size_t cmsg_len; /* Data byte count, including header(type is socklen_t in POSIX) */
  3. int cmsg_level; /* Originating protocol */
  4. int cmsg_type; /* Protocol-specific type */
  5. /* followed by
  6. unsigned char cmsg_data[]; */
  7. };

首先要明白什么是附属数据,recvmsg与sendmsg函数允许程序发送或是接收附属数据,这些额外的信息受限于一定的格式规则,也就是控制信息头与管理这些信息的宏。

  • cmsg_len 附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。
  • cmsg_level 这个值表明了原始的协议级别(例如,SOL_SOCKET)。
  • cmsg_type 这个值表明了控制信息类型(例如,SCM_RIGHTS)。

为了发送文件描述符,将cmsghdr中的成员设置如下:

  • cmsg_len 设置为cmsghdr的结构长度加一个整型(文件描述符)的长度;
  • cmsg_level 设置为SOL_SOCKET
  • cmsg_type 设置为SCM_SCM_RIGHTS,用以表明我们在传送的内容是访问权,访问权仅能通过UNIX域socket传送

紧随cmsg_type 之后的存放内容,就是描述符。通过CMSG_DATA获取整型量的指针。

如何将文件描述符传递放在附属数据中发送呢?如下:

  1. //附属数据的配置
  2. p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdr
  3. p_cmsg->cmsg_level = SOL_SOCKET;
  4. p_cmsg->cmsg_type = SCM_RIGHTS;
  5. p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); //返回附属数据长度
  6. int *p_fds;
  7. p_fds = (int *) CMSG_DATA(p_cmsg); //返回附属数据的净荷数据地址
  8. *p_fds = fd; //设置待发送的文件描述符 将fd保存在净荷数据地址

这些宏定义的作用如下:

  • CMSG_FIRSTHDR(): 返回msghdr辅助数据部分指向第一个cmsghdr的指针
  • CMSG_NXTHDR(): 返回参数中cmsghdr的下一个有效cmsghdr。当msg_control buffer中没有足够剩余的空间的时候,返回NULL
  • CMSG_ALIGN(): 给定一个长度,其会返回对齐后相应的长度。它是一个常量表达式,其一般实现如下:
  1. #define CMSG_ALIGN(len) ( ((len)+sizeof(long)-1) & ~(sizeof(long)-1) )
  • CMSG_SPACE(): 返回辅助数据及其所传递的净荷数据的总长度。即sizeof(cmsg_len) + sizeof(cmsg_level) + sizeof(cmsg_type) + len(cmsg_data)长度进行CMSG_ALIGN后的值.
  • CMSG_DATA(): 返回cmsghdr的净荷数据部分
  • CMSG_LEN(): 返回净荷数据长度进行CMSG_ALIGN后的值,一般赋值给cmsghdr.cmsg_len。

为了创建辅助数据,首先初始化msghdr.msg_controllen字段。 在msghdr上使用CMSG_FIRSTHDR()来获取第一个控制消息,然后使用CMSG_NXTHDR()来获取后续的控制消息。在每一个控制消息中,使用CMSG_LEN()来初始化cmsghdr.cmsg_len,使用CMSG_DATA()来初始化cmsghdr.cmsg_data部分

参考:https://ivanzz1001.github.io/records/post/linux/2017/11/04/linux-msghdr

所以发送文件描述符的代码如下:

  1. void send_fd(int sock_fd, int fd) {
  2. int ret;
  3. struct msghdr msg;
  4. struct cmsghdr *p_cmsg; //附属数据
  5. struct iovec vec;
  6. msg.msg_name = NULL; //通过socketpair产生的socket通信 不需要知道ip地址
  7. msg.msg_namelen = 0;
  8. msg.msg_iov = &vec;
  9. msg.msg_iovlen = 1;
  10. msg.msg_flags = 0;
  11. char sendchar = 0;//至少携带1Byte真实数据
  12. vec.iov_base = &sendchar;
  13. vec.iov_len = sizeof(sendchar);
  14. char cmsgbuf[CMSG_SPACE(sizeof(fd))]; //附属数据缓冲区大小
  15. msg.msg_control = cmsgbuf; //指向附属数据
  16. msg.msg_controllen = sizeof(cmsgbuf);
  17. //附属数据的配置
  18. p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdr
  19. p_cmsg->cmsg_level = SOL_SOCKET;
  20. p_cmsg->cmsg_type = SCM_RIGHTS;
  21. p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); //返回附属数据长度
  22. int *p_fds;
  23. p_fds = (int *) CMSG_DATA(p_cmsg); //返回附属数据的净荷数据地址
  24. *p_fds = fd; //设置待发送的文件描述符 将fd保存在净荷数据地址
  25. ret = sendmsg(sock_fd, &msg, 0); //发送描述符
  26. if (ret != 1)
  27. ERR_EXIT("sendmsg");
  28. }

在接收文件描述符的时候:

  1. int recv_fd(const int sock_fd) {
  2. int ret;
  3. struct msghdr msg;
  4. struct iovec vec;
  5. int recv_fd;
  6. char recvchar;
  7. vec.iov_base = &recvchar;
  8. vec.iov_len = sizeof(recvchar);
  9. msg.msg_name = NULL;
  10. msg.msg_namelen = 0;
  11. msg.msg_iov = &vec;
  12. msg.msg_iovlen = 1;
  13. char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
  14. msg.msg_control = cmsgbuf;
  15. msg.msg_controllen = sizeof(cmsgbuf);
  16. msg.msg_flags = 0;
  17. int *p_fd;
  18. p_fd = (int *) CMSG_DATA(CMSG_FIRSTHDR(&msg));
  19. *p_fd = -1; //先将文件描述符设置为-1 后面判断是否接收到
  20. ret = recvmsg(sock_fd, &msg, 0); //接收文件描述符
  21. if (ret != 1)
  22. ERR_EXIT("recvmsg");
  23. struct cmsghdr *p_cmsg;
  24. p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdr
  25. if (p_cmsg == NULL)
  26. ERR_EXIT("no passed fd");
  27. p_fd = (int *) CMSG_DATA(p_cmsg); //取出第一个cmsghdr中的内容 即传递的文件描述符
  28. recv_fd = *p_fd;
  29. if (recv_fd == -1)
  30. ERR_EXIT("no passed fd");
  31. return recv_fd;
  32. }

Linux 进程间传递文件描述符的更多相关文章

  1. 进程间传递文件描述符fd

    众所周知,子进程会继承父进程已经打开的文件描述符fd,但是fork之后的是不会被继承的,这个时候是否无能无力了?答应是NO.Linux提供了一个系统调用sendmsg,借助它,可以实现进程间传递文件描 ...

  2. 进程间传递文件描述符——sendmsg和recvmsg函数

    先引入一个例子,该程序的目的是子进程向父进程传递文件描述符,并通过该文件描述符读取buf. #include <func.h> int main(){ int fds[2]; pipe(f ...

  3. [转] linux系统文件流、文件描述符与进程间关系详解

    http://blog.sina.com.cn/s/blog_67b74aea01018ycx.html linux(unix)进程与文件的关系错综复杂,本教程试图详细的阐述这个问题. 包括:     ...

  4. Linux 利用进程打开的文件描述符(/proc)恢复被误删文件

    Linux 利用进程打开的文件描述符(/proc)恢复被误删文件 在 windows 上删除文件时,如果文件还在使用中,会提示一个错误:但是在 linux 上删除文件时,无论文件是否在使用中,甚至是还 ...

  5. Linux中通过Socket文件描述符寻找连接状态介绍

    针对下文的总结:socket是一种文件描述符 进程的打开文件描述符表 Linux的三个系统调用:open,socket,pipe 返回的都是一个描述符.不同的进程中,他们返回的描述符可以相同.那么,在 ...

  6. linux内核中的文件描述符(二)--socket和文件描述符

    http://blog.csdn.net/ce123_zhouwei/article/details/8459730 Linux内核中的文件描述符(二)--socket和文件描述符 Kernel ve ...

  7. linux专题一之文件描述符、重定向、管道符、tee命令

    本节讨论一下几个问题: 1. 文件描述符. 2. 重定向. 3. 管道符 4. tee的用法. 1. 文件描述符. 在linux系统中一切皆文件.文件夹和设备都是文件.如何用来区别不同的文件呢?这里的 ...

  8. linux最大允许的文件描述符open files数nofile修改

    open file resource limit 是linux中process可以打开的文件句柄数量.增加这个数值需要调整两个配置: 第一步, 修改系统最大允许的文件描述符 查看当前的设置: $ ca ...

  9. C/C++ 父子进程之间的文件描述符问题

    在C程序中,文件由文件指针或者文件描述符表示.ISO C的标准I/0库函数(fopen, fclose, fread, fwrite, fscanf, fprintf等)使用文件指针,UNIX的I/O ...

随机推荐

  1. 结构型模式 -- 代理模式(静态代理&动态代理)

    静态代理: 真实角色和代理角色实现相同的接口,代理角色拥有真实角色的引用.代理角色去执行方法,对于某些"真正"需要真实角色自己执行的方法时,在代理角色内部就调用真实角色的方法,其他 ...

  2. android开发相关知识笔记

    1.xpage页面打开: openPage(TestFragment.class) openPage("标识") // 页面打开等待结果返回: openPageForResult( ...

  3. 『心善渊』Selenium3.0基础 — 29、使用HTMLTestRunner生成unittest的HTML报告

    目录 1.HTMLTestRunner介绍 2.HTMLTestRunner的使用 3.测试报告示例 4.封装成模块 1.HTMLTestRunner介绍 HTMLTestRunner是一个基于uni ...

  4. CSS 世界中的方位与顺序

    在 CSS 中,我们经常会与各种方向方位打交道. 譬如 margin.padding,它们就会有 margin-left.margin-right 或者是 padding-left.padding-r ...

  5. 一、Java预科学习

    1.1.什么是计算机 计算机(computer)俗称电脑,是现代一种用于高速计算的电子计算机器,可以进行数值计算,又可以进行逻辑计算,还具有存储记忆功能.是能够按照程序运行,自动.高速处理海量数据的现 ...

  6. ES6 模版字符串及常用的es6扩展方法

    1.ES6 模版字符串es6 模版字符串主要用于简化字符串的拼接 <script type="text/javascript"> let obj={name:'rdb' ...

  7. mybatis-4-Mapper映射文件

    Mapper映射文件 映射文件的Mapper标签包含标签 1.CDUS增删改查 2.参数处理 (1)直接传入参数 单个参数 //传入当个参数 public Employee getEmployeeBy ...

  8. 03 高性能IO模型:采用多路复用机制的“单线程”Redis

    本篇重点 三个问题: "Redis真的只有单线程吗?""为什么用单线程?""单线程为什么这么快?" "Redis真的只有单线程吗? ...

  9. 【Azure Developer】【Python 】使用 azure.identity 和 azure.common.credentials 获取Azure AD的Access Token的两种方式

    问题描述 使用Python代码,展示如何从Azure AD 中获取目标资源的 Access Token. 如要了解如何从AAD中获取 client id,client secret,tenant id ...

  10. 构建后端第6篇之---java 多态的本质 父类引用 指向子类实现

    张艳涛写于2021-2-20 今天来个破例了,不用英文写了,今天在家里电脑写的工具不行,简单的说 主题是:java多态的原理与实现 结论是:java的多态 Father father= new Son ...