- 论坛徽章:
- 0
|
前段时间看到大家讨论过进程间传递文件描述符的问题,我本有心说几句,
可是杂务缠身,而且觉得这个问题不是几句话就可以了结的,所以拿出时间
来写了这么一段,欢迎交流。
进程间传递文件描述符是一个很微妙的问题,就像dup系统调用一样,在一个
进程中复制文件描述符,看起来不可思议,可现实中就是有这样的需求,而且
有时候还非此不可,可见,理性有时候确实不能过分依赖。对于进程间传递文件
描述符我们可以看作跨进程的dup调用,也就是同一个核心实体在不同进程间
的映射,这和两个进程打开同一个文件的结果相同,只是接受文件描述符的进程
少了open的步骤而已,而对于网络接口返回的描述符,也只能采取传递文件
描述符的方法。unix 系统中有两个办法来完成这个任务:
BSD sendmsg, recvmsg 方法
SYSV ioctl 方法
我们集中讨论 BSD 的方法,因为这个方法是包含在 socket 机制中的,
在不同版本和平台上基本都有相关的实现,而 ioctl 在外观上更专业一点,
宏定义 I_SEDNFD 和 I_RECVFD 是专为传递描述符而设置,可是它的使用
简单,没有讨论的必要,另外 ioctl 本身功能庞大,各系统对它的支持也
难以追究。
说到 sendmsg 和 recvmsg,就要关注两个结构:struct msghdr struct cmsghdr
其中 struct msghdr 我们要关注 msg_control 和 msg_controllen 两个字段
这个两个字段在 4.3 bsd 中是 msg_accright 和 msg_accrightlen 在
4.4 bsd 以后扩展为前者,struct cmsghdr 结构需要用系统所提供的宏
定义来操作,关键是对齐的需要;
下面给出一个程序,来具体说明过程,测试在三种平台进行:
SCO_SV scounix 3.2 5.0.5 i386:以下标识为 sco
OSF1 btds10 V5.0 1094 alpha:以下标识为 alpha
Linux linux 2.4.18-14 i686 i686 i386 GNU/Linux:以下标识为 linux
注意程序的注释:
- #include <stdio.h>;
- #include <unistd.h>;
- #include <fcntl.h>;
- #include <sys/types.h>;
- #include <sys/uio.h>;
- #define _SOCKADDR_LEN // 这是专为 alpha 平台设置
- #include <sys/socket.h>;
- // 某些平台没有这个宏,需要自己定义
- #ifndef CMSG_LEN
- #define CMSG_LEN( a ) ( sizeof(struct cmsghdr) + a )
- #endif
- main( )
- {
- int rv, i;
- int sp[2];
- int ofd;
- ofd = open( "zzzz", O_RDWR | O_CREAT , 0666 );
- if( ofd == -1 )
- {
- perror( "open file" );
- exit( 1 );
- }
- rv = socketpair( AF_UNIX, SOCK_STREAM, 0, sp );
- if( rv != 0 )
- {
- perror( "socket pair" );
- exit( 1 );
- }
- printf( "sp-1=%d, sp-2=%d\n", sp[0], sp[1] );
- {
- struct msghdr msg;
- struct iovec iov;
- struct cmsghdr *cmsg;
- char buf[500];
- char b =' ';
- memset( buf, 0x00, sizeof(buf) );
- memset( &msg, 0x00, sizeof(struct msghdr) );
- // ID1 在linux 中这是必须的.其他可以忽略
- msg.msg_iov = &
- msg.msg_iovlen = 1;
- iov.iov_base = &
- iov.iov_len = 1;
- // ID1
- msg.msg_control = buf;
- msg.msg_controllen = sizeof(buf);
- cmsg = CMSG_FIRSTHDR( &msg );
- if( cmsg ) // 这里必须做检查;看 REF0001
- {
- int *pfd;
- int fd[2] = { 0, 0 };
- fd[0] = ofd;
- #ifndef SOCKETSYS // 看程序后说明 BUG0001
- cmsg->;cmsg_level = SOL_SOCKET;
- cmsg->;cmsg_type = SCM_RIGHTS;
- cmsg->;cmsg_len = CMSG_LEN( sizeof(int) * 2 );
- #endif
- pfd = (int*)CMSG_DATA( cmsg );
- memcpy( pfd, fd, sizeof(int) * 2 );
- msg.msg_controllen = CMSG_LEN( sizeof(int) * 2);
- rv = sendmsg( sp[0], &msg, 0 );
- if( rv == -1 )
- {
- perror( "send msg" );
- exit( 1 );
- }
- printf( "send len=%d rv=%d\n", msg.msg_controllen, rv );
- }
- else printf( "point is null\n" );
- }
- {
- struct msghdr rmsg;
- struct iovec iov;
- struct cmsghdr *rcm;
- char buf[100];
- char b;
- memset( buf, 0x00, sizeof(buf) );
- memset( &rmsg, 0x00, sizeof(struct msghdr) );
- // ID1 在linux 中这是必须的.其他可以忽略
- rmsg.msg_iov = &
- rmsg.msg_iovlen = 1;
- iov.iov_base = &
- iov.iov_len = 1;
- // ID1
- rmsg.msg_control = buf;
- rmsg.msg_controllen = sizeof(buf);
- rv = recvmsg( sp[1], &rmsg, 0 );
- if( rv == -1 )
- {
- perror( "recv error " );
- exit( 1 );
- }
- printf( "recv rv=%d len=%d\n", rv, rmsg.msg_controllen );
- rcm = CMSG_FIRSTHDR( &rmsg );
- if( rcm )
- {
- int *rfd;
- printf( "cmsg->;len = %d\n", rcm->;cmsg_len );
- printf( "cmsg->;type = %d\n", rcm->;cmsg_type );
- printf( "cmsg->;level = %d\n", rcm->;cmsg_level );
- rfd = (int*) CMSG_DATA( rcm );
- printf( "recv fd=%d\n", *rfd );
- write( *rfd, "fuck you", 8 );
- }
- else printf( "recv data invalid\n" );
- }
- return 0;
- }
复制代码
这个程序在 alpha 和 linux 上都顺利通过,而且也达到目的,为了描述的方便
我们没有在进程之间进行交换,而在同一个进程中进行,可以看到recvmsg接受的
描述符是新的,你可以自己加上 fork 测试一下,没有什么问题,关键是在 sco
平台上,有一个不合理的现象:
BUG0001:
cmsg->;cmsg_level = SOL_SOCKET;
cmsg->;cmsg_type = SCM_RIGHTS;
cmsg->;cmsg_len = CMSG_LEN( sizeof(int) * 2 );
这样的调用参数是没有问题的,也是唯一合理的搭配,可是以这样的参数去
调用 sendmsg 后,却产生 Bad file number 的错误,进一步分析发现,
该错误发生在 sendmsg 调用 ioctl( sp[0], SIOSOCKSYS, p ) 时,sco
解释这个调用是”伪装的socket 系统调用“,p 是下列的结构:
struct socksysreq
{
int args[7];
};
只要cmsg 这三个字段不为0,那么肯定会产生这样的错误,我无心去更深的
追究,太累了,不过,可笑的是,如果将 cmsg 的这三个字段置为0,再调用
sendmsg ,是可以成功的,而且,也能够达到我们的目的,所以我设置了
SOCKETSYS 预编译指令,虽然成功,可是这样的结果并不令人漫意。
REF0001:
对于 cmsghdr 的操作,必须使用系统的宏定义,这样可以保证可移植性,
不同平台的宏定义差别很大,不过这几个宏是通用的:
CMSG_DATA, CMSG_FIRSTHDR, CMSG_NXTHDR, 其他不能保证一定存在,请
自己注意,这里需要注意的是,对于 CMSG_FIRSTHDR 返回的值,检查是
必要的,因为再参数不适当的时候,莫些系统可能返回 NULL 指针。
对于unix的编程,关注不同平台的差异,是十分有必要,也是极繁琐的,
如果你的代码再更换平台后需要大动干戈,那我认为不是一个合格的程序
员应该遇到的。china unix 把论坛分得很细,可是却没有一个讨论各自
差异的地方,不能不说是一个遗憾,正像哲学理论所表述的过程,正,
反,合,这个论坛的出现应该是一个必然结果。 [code][/code] |
|