发表时间:2010-01-19   最后修改:2010-01-19
先来两个函数: unix_send_fd 和 unix_recv_fd
int unix_send_fd(int fd, int sendfd)
{
struct msghdr msg;
struct iovec iov[1];

/*
* Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
* Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE; the
* latter breaks on LP64 systems.
*/
#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
union {
struct cmsghdr just_for_alignment;
char control[CMSG_SPACE(sizeof(sendfd))];
} control_un;
struct cmsghdr *cmptr;

memset((char *) &msg, 0, sizeof(msg)); /* Fix 200512 */
msg.msg_control = control_un.control;
msg.msg_controllen = CMSG_LEN(sizeof(sendfd)); /* Fix 200506 */

cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(sendfd));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*(int *) CMSG_DATA(cmptr) = sendfd;
#else
msg.msg_accrights = (char *) &sendfd;
msg.msg_accrightslen = sizeof(sendfd);
#endif

msg.msg_name = 0;
msg.msg_namelen = 0;

/*
* XXX We don't want to pass any data, just a file descriptor. However,
* setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble. See the
* comments in the unix_recv_fd() routine.
*/
iov->iov_base = (void *)"";
iov->iov_len = 1;
msg.msg_iov = iov;
msg.msg_iovlen = 1;

return (sendmsg(fd, &msg, 0));
}

/* unix_recv_fd - receive file descriptor */
int unix_recv_fd(int fd)
{
const char *myname = "unix_recv_fd";

struct msghdr msg;
int newfd;
struct iovec iov[1];
char buf[1];

/*
* Adapted from: W. Richard Stevens, UNIX Network Programming, Volume 1,
* Second edition. Except that we use CMSG_LEN instead of CMSG_SPACE, for
* portability to LP64 environments.
*/
#if defined(CMSG_SPACE) && !defined(NO_MSGHDR_MSG_CONTROL)
union {
struct cmsghdr just_for_alignment;
char control[CMSG_SPACE(sizeof(newfd))];
} control_un;
struct cmsghdr *cmptr;

memset((char *) &msg, 0, sizeof(msg)); /* Fix 200512 */
msg.msg_control = control_un.control;
msg.msg_controllen = CMSG_LEN(sizeof(newfd)); /* Fix 200506 */
#else
msg.msg_accrights = (char *) &newfd;
msg.msg_accrightslen = sizeof(newfd);
#endif

msg.msg_name = 0;
msg.msg_namelen = 0;

/*
* XXX We don't want to pass any data, just a file descriptor. However,
* setting msg.msg_iov = 0 and msg.msg_iovlen = 0 causes trouble: we need
* to read_wait() before we can receive the descriptor, and the code
* fails after the first descriptor when we attempt to receive a sequence
* of descriptors.
*/
iov->iov_base = buf;
iov->iov_len = sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;

if (recvmsg(fd, &msg, 0) cmsg_len == CMSG_LEN(sizeof(newfd))) {
if (cmptr->cmsg_level != SOL_SOCKET)
printf("%s: control level %d != SOL_SOCKET",
myname, cmptr->cmsg_level);
if (cmptr->cmsg_type != SCM_RIGHTS)
printf("%s: control type %d != SCM_RIGHTS",
myname, cmptr->cmsg_type);
return (*(int *) CMSG_DATA(cmptr));
} else
return (-1);
#else
if (msg.msg_accrightslen == sizeof(newfd))
return (newfd);
else
return (-1);
#endif
}

这两个函数来自 postfix。其中NO_MSGHDR_MSG_CONTROL应该是针对Tru64平台特有的定义。我们知道UNIX的文件描述符是一个整数,这个整 数是由内核维护的一个数组的下标。在进程内部,对文件描述的操作有: open,close, dup 和 dup2。dup和dup2是将文件描述符复制一份,带SOL_SOCKET的sendmsg是不是也是这样的,它又是如何在进程间复制文件描述符的呢?
先看sendmsg在Linux内核的实现:
net/socket.c: sendmsg
net/socket.c: --> sock_sendmsg
net/socket.c: --> __sock_sendmsg
--> security_socket_sendmsg
--> sock->ops->sendmsg
security_socket_sendmsg 是一个和安全相关的函数,暂时忽略。
从代码可以看出,unix_send_fd必须使用UNIX域socket,所以sock->ops->sendmsg指向unix_stream_sendmsg 函数。
net/unix/af_unix.c: unix_stream_sendmsg
include/net/scm.h: --> scm_send
net/core/scm.c: --> __scm_send
net/core/scm.c: --> scm_fp_copy
在scm_fp_copy中,内核将要发送的文件描述符对应的file引用计数加一,发送端关闭文件时并不会真正关闭文件描述符。当然,此时发送端也可一直打开该文件描述符。

从接收端看,recvmsg堆栈和sendmsg差不多,sock->ops_recvmsg指向unix_stream_recvmsg。
在unix_stream_recvmsg又调用 scm_recv --> scm_detach_fds。
在scm_detach_fds为发送时保存的文件分配一个未使用的文件描述符,并分配file结构,然后将该文件描述符保存到用户态地址中。