传送进程描述符,简单的来说,就是进程A打开一个文件f,获得了一个文件描述符fd1,然后进程A将该描述符通过某些方式,传递给了B,此时B就具有了描述符fd2(注意,fd1 不一定等于fd2),从而可以通过fd2对文件f进行读写等一系列的操作。其实本质上
相当于A,B两个进程同时打开了文件f。
具体实现其实比较简单,例如当一个父进程要向子进程传递一个文件描述符时,首先会在fork产生子进程以前,调用socketpair建立一个套接字对,用于父子进程之间的通信。之后父进程打开文件f,获得文件描述符fd1。接着通过sendmsg将包含文件描述符fd1的消息发送出去,而在子进程通过recvmsg接收消息,从中获取出文件描述符fd2。最后,子进程就能通过操作fd2对文件f进行一系列的读写操作。
最后,需要注意的是,当发送进程将文件描述符传送给接收进程以后,通常会关闭该描述符。不过,发送进程关闭该描述符并不会真的关闭该文件或设备,其原因是该文件描述符仍然视为由接收进程打开(即使接收进程尚未接收到该描述符,此时称该描述符在飞行中...in flight)。简单的代码示例,如下所示:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <sys/socket.h> // CONTROLLEN 为cmsghdr加一个文件描述符的长度 #define CONTROLLEN CMSG_LEN(sizeof(int)) int send_fd(int fd, int fd_to_send) { struct iovec iov[1]; struct msghdr msg; char buf[1]; // buf用于表示传递的描述符是否合法,合法buf[0]=0, 否则buf[0]=1 struct cmsghdr *cmptr = NULL; iov[0].iov_base = buf; iov[0].iov_len = 1; msg.msg_iov = iov; // array of IO buffers msg.msg_iovlen = 1; // number of elements in array msg.msg_name = NULL; msg.msg_namelen = 0; if (fd_to_send < 0) { msg.msg_control = NULL; msg.msg_controllen = 0; buf[0] = 1; } else { // cmsghdr 包含了要传递的信息 if ((cmptr = malloc(CONTROLLEN)) == NULL) { return -1; } cmptr->cmsg_level = SOL_SOCKET; cmptr->cmsg_type = SCM_RIGHTS; cmptr->cmsg_len = CONTROLLEN; msg.msg_control = cmptr; msg.msg_controllen= CONTROLLEN; *(int*)CMSG_DATA(cmptr) = fd_to_send; buf[0] = 0; } if (sendmsg(fd, &msg, 0) != 1) { return -1; } return 0; } int recv_fd(int fd, int *fd_to_recv) { int nr; char buf[1]; struct iovec iov[1]; struct msghdr msg; struct cmsghdr *cmptr = NULL; iov[0].iov_base = buf; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_name = NULL; msg.msg_namelen = 0; if ((cmptr = malloc(CONTROLLEN)) == NULL) { return -1; } msg.msg_control = cmptr; msg.msg_controllen = CONTROLLEN; if(recvmsg(fd, &msg, 0) < 0) { printf("recvmsg error "); return -1; } if(msg.msg_controllen < CONTROLLEN) { printf("recv_fd get invalid fd "); return -1; } *fd_to_recv = *(int*)CMSG_DATA(cmptr); return 0; } int main() { int fd; pid_t pid; int sockpair[2]; int status; char fname[256]; status = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair); if (status < 0) { printf("Call socketpair error, errno is %d ", errno); return errno; } pid = fork(); if (pid == 0) { close(sockpair[1]); status = recv_fd(sockpair[0], &fd); if (status != 0) { printf("[CHILD]: recv error, errno is %d ", status); return status; } status = write(fd, "Yao DengDeng", strlen("Yao Dengdeng")); if (status < 0) { printf("[CHILD]: write error, errno is %d ", status); return -1; } else { printf("[CHILD]: append logo successfully "); } close(fd); exit(0); } printf("[PARENT]: enter the filename: "); scanf("%s", fname); fd = open(fname, O_RDWR | O_APPEND); if (fd < 0) { printf("[PARENT]: open file error, errno is %d ", errno); return -1; } status = send_fd(sockpair[1], fd); if (status != 0) { printf("[PARENT]: send_fd error, errno is %d ", status); return -1; } close(fd); wait(NULL); return 0; }
monster@monster-Z:~/TEST/c$ touch my.log monster@monster-Z:~/TEST/c$ gcc -o fdpass main.c monster@monster-Z:~/TEST/c$ ./fdpass [PARENT]: enter the filename: my.log [CHILD]: append logo successfully monster@monster-Z:~/TEST/c$ cat my.log Yao DengDeng