Linux 中如何在进程间传递文件描述符#
有些场景,我们需要让父进程的某个文件描述符(记作 fd1)指向子进程对应的文件描述符(记作 fd2) 指向的打开文件表项,也就是 fd1 和 fd2 指向同一个处于内核中的打开文件表,又或者是让两个不相关的进程共享同一个打开文件表项。在 Linux 下,我们可以用 UNIX 套接字来在进程之间传递特殊的辅助数据,以实现打开文件表项的共享。
下面的例子是子进程打开一个名为 “text.txt” 的文件,得到一个文件描述符 fdToPass,然后将 fdToPass 对应的打开文件表项共享给父进程。
#include <stdio.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define BUFFER_SIZE 1024
int sendFd(int fd, int fdToSend) {
struct iovec iov[1];
char dummy = 0;
iov[0].iov_base = &dummy;
iov[0].iov_len = 1;
struct msghdr msg;
char controlBuf[CMSG_SPACE(sizeof(int))];
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = controlBuf;
msg.msg_controllen = sizeof(controlBuf);
struct cmsghdr *cm = CMSG_FIRSTHDR(&msg);
if (cm == NULL) {
return -1;
}
cm->cmsg_len = CMSG_LEN(sizeof(int));
cm->cmsg_level = SOL_SOCKET;
cm->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cm)) = fdToSend;
if (sendmsg(fd, &msg, 0) < 0) {
return -1;
}
return 0;
}
int recvFd(int fd) {
struct iovec iov[1];
char dummy = 0;
iov[0].iov_base = &dummy;
iov[0].iov_len = 1;
struct msghdr msg;
char controlBuf[CMSG_SPACE(sizeof(int))];
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_control = controlBuf;
msg.msg_controllen = sizeof(controlBuf);
if (recvmsg(fd, &msg, 0) < 0) {
return -1;
}
struct cmsghdr *cm = CMSG_FIRSTHDR(&msg);
if (cm == NULL || cm->cmsg_type != SCM_RIGHTS) {
return -1;
}
int fdRecvd = *((int *) CMSG_DATA(cm));
return fdRecvd;
}
int main() {
int pipeFd[2];
int fdToPass = 0;
if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pipeFd)) {
fprintf(stderr, "socketpair() failed: %s\n", strerror(errno));
return 1;
}
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "fork() failed: %s\n", strerror(errno));
return 1;
}
if (pid == 0) {
close(pipeFd[0]);
fdToPass = open("test.txt", O_RDWR, 0666);
if (fdToPass < 0) {
fprintf(stderr, "open \"test.txt\" failed: %s\n", strerror(errno));
return 1;
}
if (sendFd(pipeFd[1], fdToPass) < 0) {
fprintf(stderr, "sendFd() failed: %s\n", strerror(errno));
return 1;
}
close(fdToPass);
return 0;
}
close(pipeFd[1]);
if ((fdToPass = recvFd(pipeFd[0])) < 0) {
fprintf(stderr, "recvFd() failed: %s\n", strerror(errno));
return 1;
}
char buf[BUFFER_SIZE] = {0};
read(fdToPass, buf, BUFFER_SIZE);
printf("I got data from fd %d: %s\n", fdToPass, buf);
close(fdToPass);
return 0;
}c