本文共 5351 字,大约阅读时间需要 17 分钟。
对内核来说,所有打开的文件都会通过文件描述符引用,文件描述符在进程中是一个非负整数,文件描述符在进程中是从0开始,默认0与标准输入关联、1与标准输出关联、2与标准出错关联。之后进程每打开一个文件或者创建一个新文件的时候,内核都会向进程返回一个文件描述符来表示这个文件,文件描述符是递增的。文件描述符的值与文件没有必然的联系,只是该文件在进程中的一个标识,所以同一文件在不同进程中的文件描述符可能不一样,相同值得文件描述符在不同进程中可能标识不同得文件。文件描述符的取值范围是0~OPEN_MAX。
接下来要明白文件共享涉及的数据结构。
内核使用三种数据结构来表示打开的文件:
三者的关系如下:
在进程间传递文件描述符是非常有用的,通过传递文件描述符,可以让其他进程拥有对文件操作的能力,在网络编程中体现比较多,比如nobody进程协助创建了数据连接,然后将socket的文件描述符传递给服务进程,由服务进程进行数据传输。
这是两个进程分别打开同一文件的情况:
这是进程共享文件描述符的状态·:
所以共享文件描述符就是将不同文件描述符指向一个文件表。这一点与fork产生的父子进程共享已打开的文件描述符是一样的。还要注意,一般文件在关闭文件描述符之后就关闭文件了,但是共享文件的情况不一样,共享文件要等到所有引用的文件描述符关闭之后才可关闭。
可以通过UNIX域socket来传递文件描述符,实际是调用了socket中的sendmsg和recvmsg函数,利用sendmsg和recvmsg可以发送附属数据,附属数据可以是是文件描述符,两个函数的原型如下:
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */};//套接口地址成员msg_name与msg_namelen。//I/O向量引用msg_iov与msg_iovlen。//附属数据缓冲区成员msg_control与msg_controllen。//接收信息标记位msg_flags。
其中,对于传递文件描述符有用的成员为:msg_control和msg_controllen,需要注意的是,如果想利用它传递辅助信息,比如文件描述符,必须携带至少一个字节的真实数据,也就是iov指针指向的缓冲区要有数据,iovlen至少是1。
传递附属数据cmsghdr的结构如下:
struct cmsghdr { size_t cmsg_len; /* Data byte count, including header(type is socklen_t in POSIX) */ int cmsg_level; /* Originating protocol */ int cmsg_type; /* Protocol-specific type *//* followed by unsigned char cmsg_data[]; */};
首先要明白什么是附属数据,recvmsg与sendmsg函数允许程序发送或是接收附属数据,这些额外的信息受限于一定的格式规则,也就是控制信息头与管理这些信息的宏。
为了发送文件描述符,将cmsghdr中的成员设置如下:
紧随cmsg_type 之后的存放内容,就是描述符。通过CMSG_DATA获取整型量的指针。
如何将文件描述符传递放在附属数据中发送呢?如下:
//附属数据的配置p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdrp_cmsg->cmsg_level = SOL_SOCKET;p_cmsg->cmsg_type = SCM_RIGHTS;p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); //返回附属数据长度int *p_fds;p_fds = (int *) CMSG_DATA(p_cmsg); //返回附属数据的净荷数据地址*p_fds = fd; //设置待发送的文件描述符 将fd保存在净荷数据地址
这些宏定义的作用如下:
#define CMSG_ALIGN(len) ( ((len)+sizeof(long)-1) & ~(sizeof(long)-1) )
为了创建辅助数据,首先初始化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
所以发送文件描述符的代码如下:
void send_fd(int sock_fd, int fd) { int ret; struct msghdr msg; struct cmsghdr *p_cmsg; //附属数据 struct iovec vec; msg.msg_name = NULL; //通过socketpair产生的socket通信 不需要知道ip地址 msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; msg.msg_flags = 0; char sendchar = 0;//至少携带1Byte真实数据 vec.iov_base = &sendchar; vec.iov_len = sizeof(sendchar); char cmsgbuf[CMSG_SPACE(sizeof(fd))]; //附属数据缓冲区大小 msg.msg_control = cmsgbuf; //指向附属数据 msg.msg_controllen = sizeof(cmsgbuf); //附属数据的配置 p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdr p_cmsg->cmsg_level = SOL_SOCKET; p_cmsg->cmsg_type = SCM_RIGHTS; p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd)); //返回附属数据长度 int *p_fds; p_fds = (int *) CMSG_DATA(p_cmsg); //返回附属数据的净荷数据地址 *p_fds = fd; //设置待发送的文件描述符 将fd保存在净荷数据地址 ret = sendmsg(sock_fd, &msg, 0); //发送描述符 if (ret != 1) ERR_EXIT("sendmsg");}
在接收文件描述符的时候:
int recv_fd(const int sock_fd) { int ret; struct msghdr msg; struct iovec vec; int recv_fd; char recvchar; vec.iov_base = &recvchar; vec.iov_len = sizeof(recvchar); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &vec; msg.msg_iovlen = 1; char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))]; msg.msg_control = cmsgbuf; msg.msg_controllen = sizeof(cmsgbuf); msg.msg_flags = 0; int *p_fd; p_fd = (int *) CMSG_DATA(CMSG_FIRSTHDR(&msg)); *p_fd = -1; //先将文件描述符设置为-1 后面判断是否接收到 ret = recvmsg(sock_fd, &msg, 0); //接收文件描述符 if (ret != 1) ERR_EXIT("recvmsg"); struct cmsghdr *p_cmsg; p_cmsg = CMSG_FIRSTHDR(&msg); //返回附属数据部分的第一个cmsghdr if (p_cmsg == NULL) ERR_EXIT("no passed fd"); p_fd = (int *) CMSG_DATA(p_cmsg); //取出第一个cmsghdr中的内容 即传递的文件描述符 recv_fd = *p_fd; if (recv_fd == -1) ERR_EXIT("no passed fd"); return recv_fd;}
转载地址:http://axwzi.baihongyu.com/