Can I share a file descriptor to another process on linux or are they local to the process?
Say I have 2 processes, ProcessA and ProcessB. If I perform int fd=open(somefile)
in ProcessA, can I then pass the value of file descriptor fd
over IPC to Pro开发者_StackOverflowcessB and have it manipulate the same file?
You can pass a file descriptor to another process over unix domain sockets. Here's the code to pass such a file descriptor, taken from 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 */
And here's the code to receive the file descriptor
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 */
If both processes belong the the same user, then you can simply make use of the 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);
Of course you would need to some IPC mechanism to share the value of SOURCE_FD
. See e.g. “Linux C: upon receiving a signal, is it possible to know the PID of the sender?”.
In 2020, on Linux versions 5.6 and above, a new system call was added to Linux that’ll enable a process to obtain a duplicate of a file descriptor of another process referred to by a pidfd with the pidfd_getfd()
system call.
You can use the method nos described in this thread, or the (more conventional) way, by sharing it between related processes (typically parent-child or siblings) by having it created, the forked processes automatically receive a copy.
Indeed, forked processes get all your FDs and can use them unless they close them (which is generally a good idea).
Therefore if a parent forks two children, if they both have a file descriptor they didn't close, it is now shared (even if the parent subsequently closes it). This could, for example, be a pipe from one child to another. This is how shell redirects like
ls -l | more
Work.
Note that in the example of above, the setting of variables when receiving, like:
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;
is not required. The whole idea of a message structure with headers is that the receiving site does not have to know what it reads, and can by checking the (first) header, what kind of message it is and what to expect.
Short Answer:
Try pidfd_getfd
Long Answer
The pidfd_getfd() system call allocates a new file descriptor in the calling process (Process B). This new file descriptor is a duplicate of an existing file descriptor, targetfd, in the process (Process A) referred to by the PID file descriptor pidfd . Of course you need a mechanism to get targetfd from Process A.
newfd = syscall(SYS_pidfd_getfd, int pidfd, int targetfd, 0);
We get the PID file descriptor pidfd from pidfd_open().
pidfd = syscall(SYS_pidfd_open, pid_t pid, 0);
The effect of pidfd_getfd() is similar to the use of SCM_RIGHTS messeges, but in order to pass a file descriptor using an SCM_RIGHTS message, the two processes must first establish a UNIX domain socket connection, so requires cooperation on the part of the process whose file descriptor is being copied. By contrast, no such cooperation is necessary when using pidfd_getfd().
A dummy example
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <string.h>
#define MMAP_NAME "/tmp/mmap"
struct shared_mem{
int targetfd;
};
int main(void){
int fd;
struct shared_mem *shmp;
unlink(MMAP_NAME);
fd = open(MMAP_NAME, O_CREAT | O_RDWR, 00600);
ftruncate(fd, sizeof(struct shared_mem));
shmp = (struct shared_mem *)mmap(NULL,
sizeof(struct shared_mem),
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd,
0);
if (fork() == 0){
sleep(5);
write(syscall(SYS_pidfd_getfd,
syscall(SYS_pidfd_open, getppid(), 0),
shmp->targetfd,
0),
"Messege from Child\n",
strlen("Messege from Child\n"));
close(shmp->targetfd);
exit(EXIT_SUCCESS);
}else{
shmp->targetfd = open("foo.txt", O_RDWR | O_CREAT);
write(shmp->targetfd, "Messege from Parent\n", strlen("Messege from Parent\n"));
wait(NULL);
}
munmap(shmp, sizeof(struct shared_mem));
return EXIT_SUCCESS;
}
..
# cat foo.txt
Messege from Parent
Messege from Child
精彩评论