中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > C/C++
再论传递文件描述符
作者:未知 时间:2005-09-13 19:23 出处:ChinaUnix.net 责编:chinaitpower
              摘要:再论传递文件描述符

前段时间看到大家讨论过进程间传递文件描述符的问题,我本有心说几句,
可是杂务缠身,而且觉得这个问题不是几句话就可以了结的,所以拿出时间
来写了这么一段,欢迎交流。
进程间传递文件描述符是一个很微妙的问题,就像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

注意程序的注释:

[code:1:ef0382326e]
#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;
}[/code:1:ef0382326e]
这个程序在 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:1:ef0382326e][/code:1:ef0382326e]

 icedust 回复于:2003-07-23 19:54:50
好像那里见过的?
是楼主原创么?

 无双 回复于:2003-07-23 20:09:27


以后有时间的话大家一起总结一下这个问题

因为传文件描述符在不同平台上的实现不同

并且不是通用的 就是说有的平台可能要用ioctl方法
还有的平台可能需要sendmsg方法 反过来就不行

PS 是不是原创的 如是是的话先加个精先

 li2002 回复于:2003-07-23 20:17:48
不错的文章,先收下再仔细看,tks!!

 jsean 回复于:2003-07-24 02:20:10
各位大虾,能否专门讲一下ioctl呀?

 jobman 回复于:2003-07-24 07:56:06
原创不原创的问题,我不想讨论,如果谁有时间,可以在网上
实现一个 strcmp 来进行鉴别, 还真想抽时间说说 ioctl,
可惜时间太少

 蓝色键盘 回复于:2003-07-24 10:42:19
是不是原创根本不重要,我们是个技术论坛,为什么非要顶着别人是不是原创,如果不是原创是不是就没有人看了???

对于楼主描述的问题,偶仔细看了,整体描述的合理。需要补充的是sendmsg和recvmsg的方式可能将来回得到更多系统的支持。对于ioctl一些系统为了兼容性继续保留。同时,偶还有个疑问,就SCO UNIX系统来说,楼主的描述:
[code:1:a1836e9551]
进一步分析发现, 
该错误发生在 sendmsg 调用 ioctl( sp[0], SIOSOCKSYS, p ) 时,
[/code:1:a1836e9551]
是否可以给大家提供这个sendmsg调用ioctl的相关资料。说实话,偶没有仔细推敲过是不是sendmsg传递描述字调用了ioctl。希望楼主能给一些相关的资料或者资源看看。

楼主在最后总结到:
[code:1:a1836e9551]
对于unix的编程,关注不同平台的差异,是十分有必要,也是极繁琐的, 
如果你的代码再更换平台后需要大动干戈,那我认为不是一个合格的程序 
员应该遇到的。
[/code:1:a1836e9551]

这段话说得很好,偶给你鼓掌。

说实话,偶觉得在一个论坛中回答问题是一件吃力不讨好的事情。因为希望得到答案的人一旦觉得你的解释和他的系统不一样的时候,想当然的认为你的回答是错的,或者误人子弟。可是,问题的提出者并不解释自己的环境。

能回答问题的人不可能什么都懂,可能问题的提出者能够得到一些启示,偶觉得已经足够了。因此,如果一个问题就算你觉得你能够确定的时候,也不能坚持到底。如果一个问题你觉得不是特别了解的时候,那么最好不要发言。

偶说这么多,是因为突然想起的,并没有针对任何人的意思。当然,这些话出现在楼主的帖子里。希望能够理解。

 jobman 回复于:2003-07-24 11:47:18
对于 sendmsg 调用 ioctl 的情况, 这是确定的,具体请关注 trace命令,看一下man 页就明白了,在 linux 下可以使用 ltrace 和
strace , 可以结合man 页自己进行尝试,呵呵,键盘同志斑竹不是
白当的,果然能够抓住重点。

 蓝色键盘 回复于:2003-07-24 13:36:15
偶想知道SCO是怎么实现的。在网上没有看到类似的信息。

你说linux是用sendmsg调用ioctl。那么我们一块来看看。linux中是怎么调用的。

1、在次之前,先说明一下ptrace

Ptrace()是一个系统调用,让父进程控制子进程的执行,它主要用于断点跟踪调试。当子进程接受到父进程的跟踪信号后,转入暂停状态,等待父进程对它的操作;同时回送一信号给父进程。在子进程处在暂停状态时,父进程可以通过ptrace()对其内核映像空间读写,可以命令子进程结束运行或继续运行。

如果是应用程序调用的话,那么第一个参数的含义如下:
request 系统调用要做的具体操作要求,包括:
PTRACE_TRACEME 当前进程主动要求被其父进程跟踪;
PTRACE_PEEKTEXT 在指定地址上读取一条指令;
PTRACE_PEEKDATA  在指定地址上读取一个字长的数据;
PTRACE_PEEKUSR 根据指定地址在USER结构区域中读寄存器;
PTRACE_POKETEXT 在指定地址上改写一条指令;
PTRACE_POKEDATA  在指定地址上改写一个字长的数据;
PTRACE_POKEUSR  根据指定地址在USER结构区域中写寄存器;
PTRACE_CONT  重新开始子进程;
PTRACE_SYSCALL 重新开始子进程;同PTRACE_CONT不同的是,PTRACE_SYSCALL可以使进程在下一次系统调用开始或结束时中止运行;
PTRACE_KILL  给子进程发送SIGKILL信号结束子进程;
PTRACE_SINGLESTEP  在处理器状态字(PSW)中设置Trace bit位,让子进程单步执行;
PTRACE_ATTACH  连接被跟踪的子进程;
PTRACE_DETACH 取消原来的连接。 
pid 被跟踪的子进程的进程标志号。需要注意的是,1号进程不能使用该系统调用,1号进程不能被跟踪。
data 要写入字的数值。

当request等于PTRACE_PEEKTEXT和PTRACE_PEEKDATA时,sys_ptrace()完成的功能是根据指定的地址读取一个字长的指令和数据。由于Linux中指令和数据空间不分离,因此它们的操作是相同的,主要是调用read_long()函数实现。

在看看啊才能输PTRACE_POKETEXT和PTRACE-POKEDATA。
当request等于PTRACE_POKETEXT和PTRACE-POKEDATA时,根据指定的地址向内存写一个字长。数据放在sys_ptrace()的data参数中。它主要是调用write_long()函数实现。

跟进一步的,看看ptrace对于期存器的操作:
为支持提供四个调试断点,在80386中增加了八个寄存器,编号为DR0至DR7。这八个寄存器中由四个用于断点,两个用于控制,另两个保留未用。对这八个寄存器的访问,只能在0级特权级进行。在其它任何特权级对这八个寄存器中的任意一个寄存器进行读或写访问,都将产生无效操作码异常。此外,这八个寄存器还可用DR6及DR7中的BD位和GD位进行进一步的保护,使其即使是在0级也不能进行读出或写入。

对这些寄存器的访问使用通常的MOV指令:
MOV reg Dri 该指令将调试寄存器i中的内容读至通用寄存器reg中;
MOV Dri reg 该指令将通用寄存器reg中的内容写至调试寄存器i中。此处i的取值可以为0至7中的任意值。
 
这些寄存器的功能如下:
DR0—DR3 寄存器DR0—DR3包含有与四个断点条件的每一个相联系的线性地址(断点条件则在DR7中)。因为这里使用的是线性地址,所以,断点设施的操作,无论分页机制是否启用,都是相同的。
DR4—DR5 保留。
DR6是调试状态寄存器。当一个调试异常产生时,处理器设置DR6的相应位,用于指示调试异常发生的原因,帮助调试异常处理程序分析、判断,以及作出相应处理。
DR7是调试控制寄存器。分别对应四个断点寄存器的控制位,对断点的启用及断点类型的选择进行控制。所有断点寄存器的保护也在此寄存器中规定。

DR6各位的功能
B0—B3 当断点线性地址寄存器规定的条件被检测到时,将对应的B0—B3位置1。置位B0—B3与断点条件是否被启用无关。即B0—B3的某位被置1,并不表示要进行对应的断点异常处理。
BD 如下一条指令要对八个调试寄存器之一进行读或写时,则在指令的边界BD位置1。在一条指令内,每当即将读写调试寄存器时,也BD位置1。BD位置1与DR7中GD位启用与否无关。
BS 如果单步异常发生时,BS位被置1。单步条件由EFLAGS寄存器中的TF位启用。如果程序由于单步条件进入调试处理程序,则BS位被置1。与DR6中的其它位不同的是,BS位只在单步陷阱实际发生时才置位,而不是检测到单步条件就置位。
BT BT位对任务切换导致TSS中的调试陷阱位被启用而造成的调试异常,指示其原因。对这一条件,在DR7中没有启用位。

DR6中的各个标志位,在处理机的各种清除操作中不受影响,因此,调试异常处理程序在运行以前,应清除DR6,以避免下一次检测到异常条件时,受到原来的DR6中状态位的影响。

DR7各位的功能
LEN LEN为一个两位的字段,用以指示断点的长度。每一断点寄存器对应一个这样的字段,所以共有四个这样的字段分别对应四个断点寄存器。LEN的四种译码状态对应的断点长度如下

LEN 说明
0 0 断点为一字节
0 1 断点为两字节
1 0 保留
1 1 断点为四字节

这里,如果断点是多字节长度,则必须按对应多字节边界进行对齐。如果对应断点是一个指令地址,则LEN必须为00
RWE RWE也是两位的字段,用以指示引起断点异常的访问类型。共有四个RWE字段分别对应四个断点寄存器,RWE的四种译码状态对应的访问类型如下

RWE 说明
0 0 指令
0 1 数据写
1 0 保留
1 1 数据读和写

GE/LE GE/LE为分别指示准确的全局/局部数据断点。如果GE或LE被置位,则处理器将放慢执行速度,使得数据断点准确地把产生断点的指令报告出来。如果这些位没有置位,则处理器在执行数据写的指令接近执行结束稍前一点报告断点条件。建议读者每当启用数据断点时,启用LE或GE。降低处理机执行速度除稍微降低一点性能以外,不会引起别的问题。但是,对速度要求严格的代码区域除外。这时,必须禁用GE及LE,并且必须容许某些不太精确的调试异常报告。
L0—L3/G0—G3 L0—L3及G0—G3位
分别为四个断点寄存器的局部及全局启用信号。如果有任一个局部或全局启用位被置位,则由对应断点寄存器DRi规定的断点被启用。
GD GD位启用调试寄存器保护条件。注意,处理程序在每次转入调试异常处理程序入口处清除GD位,从而使处理程序可以不受限制地访问调试寄存器。

前述的各个L位(即LE,L0—L3)是有关任务的局部位,使调试条件只在特定的任务启用。而各个G位(即GD,G0—G3)是全局的,调试条件对系统中的所有任务皆有效。在每次任务切换时,处理器都要清除L位。

此处对应的函数主题如下:
[code:1:8b14978a3a]
/* 当request为对寄存器的读时 */
case PTRACE_PEEKUSR: {
unsigned long tmp;
ret = -EIO;
/* 地址越界或者地址未对齐 */
if ((addr & 3) || addr < 0 || 
    addr > sizeof(struct user) - 3)
goto out;
tmp = 0;  /* Default return condition */
/* 如果是对17个用户基本寄存器进行读操作 */
if(addr < 17*sizeof(long))
/* 调用getreg() */
tmp = getreg(child, addr);
/* 如果是对8个调试寄存器进行读操作 */
if(addr >= (long) &dummy->u_debugreg[0] &&
   addr <= (long) &dummy->u_debugreg[7]){
addr -= (long) &dummy->u_debugreg[0];
/* 求出地址偏移量 */
addr = addr >> 2;
/*  读取调试寄存器值,它在进程控制块的tss结构中  */
tmp = child->tss.debugreg[addr];
};
/*  返回结果  */
ret = put_user(tmp,(unsigned long *) data);
goto out;
}
/* 如果对寄存器进行写操作 */
case PTRACE_POKEUSR: 
ret = -EIO;
/*  越界或字节未对齐出错  */
if ((addr & 3) || addr < 0 ||  
    addr > sizeof(struct user) - 3)
goto out;
/*  如果是写17个用户基本寄存器值  */
if (addr < 17*sizeof(long)) {
/* 调用putreg()函数 */
ret = putreg(child, addr, data);
goto out;
}
  if(addr >= (long) &dummy->u_debugreg[0] &&
     addr <= (long) &dummy->u_debugreg[7]){
  /* DR4,DR5寄存器不可写 */
  if(addr == (long) &dummy->u_debugreg[4]) return -EIO;  
  if(addr == (long) &dummy->u_debugreg[5]) return -EIO; 
  /* 写DR0~DR3断点地址,越界出错,
 因为不能对3G到4G的系统空间设置断点 */
  if(addr < (long) &dummy->u_debugreg[4] &&
     ((unsigned long) data) >= TASK_SIZE-3)
 return -EIO;
  /*  写DR7  */
  if(addr == (long) &dummy->u_debugreg[7]) { 
  data &= ~DR_CONTROL_RESERVED;
  for(i=0; i<4; i++)
  /*  LEN RWE非法出错  */
  if ((0x5f54 >> ((data >> (16 + 4*i)) & 0xf)) & 1)
  goto out;
  };
  /* 求出地址偏移量 */
  addr -= (long) &dummy->u_debugreg;
  addr = addr >> 2;
  /*  写调试寄存器值,它在进程控制块的tss结构中 */
  child->tss.debugreg[addr] = data;
  ret = 0;
  goto out;
  };
  ret = -EIO;
  goto out;

[/code:1:8b14978a3a]

[code:1:8b14978a3a]
putreg()和getreg()分别调用了get_stack_long()和 put_stack_long()函数,这两个函数的功能是读写进程堆栈的寄存器的值。

static inline int get_stack_long(struct task_struct *task, int offset)
{
unsigned char *stack;
/*  获得ESP0寄存器值,esp0寄存器为进程堆栈的头指针 */
stack = (unsigned char *)task->tss.esp0;
/*  加偏移量  */
stack += offset;
/* 返回寄存器的值 */
return (*((int *)stack));
}

static inline int put_stack_long(struct task_struct *task, int offset,
unsigned long data)
{
unsigned char * stack;
/*  获得ESP0寄存器值  */
stack = (unsigned char *) task->tss.esp0;
/*  加偏移量  */
stack += offset;
/* 写寄存器 */
*(unsigned long *) stack = data;
return 0;
}

static int putreg(struct task_struct *child,
unsigned long regno, unsigned long value)
{
/* 不是以字节计算地址,而是以字长计算,所以偏移两位*/
switch (regno >> 2) {
/*  不能读写EAX  */
case ORIG_EAX:
/* 返回错误信息,跟踪要求不合法 */
return -EIO;
case FS:
/*  优先级不为3,出错;
因为只有特权级进程才能对其进行写操作  
*/
if (value && (value & 3) != 3)
return -EIO;
/*  修改子进程tss结构的fs寄存器 */
child->tss.fs = value;
return 0;
case GS:
/*  优先级不为3,出错;
因为只有特权级进程才能对其进行写操作  
*/
if (value && (value & 3) != 3)
return -EIO;
/*  修改子进程tss结构的gs寄存器 */
child->tss.gs = value;
return 0;
case DS:
case ES:
/*  优先级不为3,出错;
因为只有特权级进程才能对其进行写操作  
*/
if (value && (value & 3) != 3)
return -EIO;
/*  ds es为16位 */
value &= 0xffff;
break;
case SS:
case CS:
/*  优先级不为3,出错;
因为只有特权级进程才能对其进行写操作  
*/
if ((value & 3) != 3)
return -EIO;
/*  ss cs为16位 */
value &= 0xffff;
break;
case EFL:
/*  EFLAG访问权限设定  */
value &= FLAG_MASK;
value |= get_stack_long(child, EFL_OFFSET) & ~FLAG_MASK;
}
/*  修正偏移量  */
if (regno > GS*4)
regno -= 2*4;
/* 调用put_stack_long()函数,写进程堆栈寄存器的值 */
put_stack_long(child, regno - sizeof(struct pt_regs), value);
return 0;
}

static unsigned long getreg(struct task_struct *child,
unsigned long regno)
{
unsigned long retval = ~0UL;
switch (regno >> 2) {
case FS:
/*  通过TSS读寄存器gs  */
retval = child->tss.fs;
break;
case GS:
/*  通过TSS读寄存器gs  */
retval = child->tss.gs;
break;
/*  ds es ss cs为16位 */
case DS:
case ES:
case SS:
case CS:
retval = 0xffff;
default:
/*  修正偏移量  */
if (regno > GS*4)
regno -= 2*4;
regno = regno - sizeof(struct pt_regs);
/* 调用get_stack_long()读进程堆栈的值 */
retval &= get_stack_long(child, regno);
}
return retval;
[/code:1:8b14978a3a]

看了代码你应该知道具体过程,我想问你的是,我怎么关注trace命令才能达到了解sendmsg调用ioclt的情况。

2、对于sendmsg。

内核中用户调用sendmsg是由总的sock结构传递下来的,函数指针如何传递的过程,在此不作描述了。
针对楼主的问题,重点看看sendmsg处理过程。内核最终处理sendmsg控制描述字的部分关联的函数如下。

[code:1:8b14978a3a]int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p)
{
        struct cmsghdr *cmsg;
        int err;

        for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg))
        {
                err = -EINVAL;

                /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */
                /* The first check was omitted in <= 2.2.5. The reasoning was
                   that parser checks cmsg_len in any case, so that
                   additional check would be work duplication.
                   But if cmsg_level is not SOL_SOCKET, we do not check
                   for too short ancillary data object at all! Oops.
                   OK, let's add it...
                 */
                if (cmsg->cmsg_len < sizeof(struct cmsghdr) ||
                    (unsigned long)(((char*)cmsg - (char*)msg->msg_control)
                                    + cmsg->cmsg_len) > msg->msg_controllen)
                        goto error;

                if (cmsg->cmsg_level != SOL_SOCKET)
                        continue;

                switch (cmsg->cmsg_type)
                {
                case SCM_RIGHTS:
                        err=scm_fp_copy(cmsg, &p->fp);
                        if (err<0)
                                goto error;
                        break;
                case SCM_CREDENTIALS:
                        if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred)))
                                goto error;
                        memcpy(&p->creds, CMSG_DATA(cmsg), sizeof(struct ucred))
;
                        err = scm_check_creds(&p->creds);
                        if (err)
                                goto error;
                        break;
                default:
                        goto error;
                }
                                                              152,3-17      51%
        }

        if (p->fp && !p->fp->count)
        {
                kfree(p->fp);
                p->fp = NULL;
        }
        return 0;

error:
        scm_destroy(p);
        return err;
}

[/code:1:8b14978a3a]

[code:1:8b14978a3a]

int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data)
{
        struct cmsghdr *cm = (struct cmsghdr*)msg->msg_control;
        struct cmsghdr cmhdr;
        int cmlen = CMSG_LEN(len);
        int err;

        if (cm==NULL || msg->msg_controllen < sizeof(*cm)) {
                msg->msg_flags |= MSG_CTRUNC;
                return 0; /* XXX: return error? check spec. */
        }
        if (msg->msg_controllen < cmlen) {
                msg->msg_flags |= MSG_CTRUNC;
                cmlen = msg->msg_controllen;
        }
        cmhdr.cmsg_level = level;
                                                              182,2-9       63%
        cmhdr.cmsg_type = type;
        cmhdr.cmsg_len = cmlen;

        err = -EFAULT;
        if (copy_to_user(cm, &cmhdr, sizeof cmhdr))
                goto out;
        if (copy_to_user(CMSG_DATA(cm), data, cmlen - sizeof(struct cmsghdr)))
                goto out;
        cmlen = CMSG_SPACE(len);
        msg->msg_control += cmlen;
        msg->msg_controllen -= cmlen;
        err = 0;
out:
        return err;
}
[/code:1:8b14978a3a]

这些代码可能我理解的有问题,想请教楼主,在linux中sendmsg具体是怎么调用ioctl。

还望指教。

 jobman 回复于:2003-07-24 14:34:51
我原贴中提到:
 
  [code:1:eb70c23313]
  关键是在 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 预编译指令,虽然成功,可是这样的结果并不令人漫意。 [/code:1:eb70c23313]

也就是说,在sco 上sendmsg 毫无疑问调用了 ioctl , 而且在这里出现
问题,斑竹贴出这一大段linux 的代码,我不知道为了什么,因为sco
和linux 的具体实现肯定有很大差异。拿linux 的代码去猜测 sco 的行为
,恐怕不太明智。而且我仔细看过,我并没有说linux 下sendmsg 调用
了 ioctl , 可能是我那里描述的不太清晰????我想斑竹可能没有在
sco 环境下实验过这个代码,我对BUG0001的描述明确无误是针对 sco
的,如果你在 sco 下看过 trace 的输出,对于它的实现方式,就能有个
大概框架了。
我突然深刻理解了这段话的含义,可还是彻底晕了。
[code:1:eb70c23313]因此,如果一个问题就算你觉得你能够确定的时候,也不能坚持到底。如果一个问题你觉得不是特别了解的时候,那么最好不要发言。 

偶说这么多,是因为突然想起的,并没有针对任何人的意思。当然,这些话出现在楼主的帖子里。希望能够理解。[/code:1:eb70c23313]

“要我怎么说,我不知道,太多的语言,消失在胸口......."

 蓝色键盘 回复于:2003-07-24 14:43:02
呵呵,SCO中,sendmsg到底是否调用ioctl对我当前的一个系统很重要,因此追问你。

我想得到的是确切的资料,试验看不出来是否调用ioctl。

至于列举linux内核的代码,是因为在linux中,ptrace看不出ioctl的痕迹,就算是,也是一条指令和存贮器,期存器信息而已。
描述linux中sendmsg是:我没有发现调用ioctl的地方,或许我理解错了,所以请教你。

没有别的意思,仅仅是讨论一下。

 蓝色键盘 回复于:2003-07-24 14:48:11
[code:1:58752af0a7]
如果将 cmsg 的这三个字段置为0,再调用 
sendmsg ,是可以成功的,而且,也能够达到我们的目的
[/code:1:58752af0a7]

根据这种情况,sendmsg和ioctl有可能存在一些关系,现在需要找到socksysreq和struct msghdr 之间是不是在内核中存在互访队列和切换的问题。楼主觉得这么去分析是否合理?

 jobman 回复于:2003-07-24 14:50:47
如果你指的是  sco , 那可以肯定,它确实调用了 ioctl ,
看看  trace 的输出就再明白不过了。

 jobman 回复于:2003-07-24 16:37:14
我想,这个问题在外表看来是莫名其妙,既然系统明确设置了
SOL_SOCKET 和SCM_RIGHT ,那么具体实现中就不会忽略,sco 再烂
也不会烂到这个程度,cmsghdr 和 socksysreq 肯定有
某种对应关系,这是不言自明的,其实在 sco 中 
socketpair,socket,sendmsg,recvmsg 
都是库函数,而真正的系统调用就是
ioctl( fd, SIOSOCKSYS, p  
他使用 struct socksysreq 来传递数据,这个结构是7个整数,
他根据这7个整数的值决定是哪个系统调用,
搞清楚这个是关键的。
而且 sendmsg 和 recvmsg 是使用同一个 struct socksysreq 来
调用的,它是堆栈中一个临时变量。

 chenwen 回复于:2003-07-25 09:18:08
只要更换一个.h文件sco上即可ok,sco的人也说这是个bug

 蓝色键盘 回复于:2003-07-25 09:47:55
楼上的请明示具体更换的文件和理由。

感谢你的回复。

jobman分析的有一定的道理。希望大家能够提供更多的信息。

关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有