[请教]线程间数据传递

:question: 如果要在线程间传递几百k的数据,一般用什么方法?

目前使用的是 resmgr_msgwrite( ),但是传递大量数据的时候,这个函数比较慢。 :confused:
为了传递100多k的数据,要用到 7ms 左右的样子。
有没有快一些的呢?
还是说其实这个函数并不慢,是另有原因?

顺便问一下,当一个进程通过资源管理器调用驱动的读写函数后,(比如说,通过使用函数 read( ) 调用USB驱动的函数 usb_read( ) )
然后系统会为了读写操作而新产生一个线程么?如果是这样的话,那么这个新产生的线程属于驱动进程么?

线程间?你的意思是不同进程里的线程吧。

跟你的硬件有密切关系。你可以试试memcpy()同样数量的数据要多少时间。也可以自己写个server/client,看看在你的机器上传递这些数据要多久。

通常不会新开线程,而是预先开好的线程池中的某个线程来响应。

嗯,不同进程间的数据传递。

直接使用memcpy( )复制数据的话,进程会被直接抹杀掉的样子。

单独用一个程序测试memcpy( )的话,也就us级的消耗。

多谢提醒,我都忘了驱动里面有开过进程池了。。。

对,我的意思就是叫你自己试一下,看在你的硬件上 memcpy() 100K要多久。如果如你所说,只有 us 级别的话, resmgr_msgwrite() 用了7ms好象太久了。

你自己写个简单程序试试。

测试情况如下:
1.执行 resmgr_msgwrite() 的话,总共需要时间 11ms。
2.不执行 resmgr_msgwrite() 的话,总共需要时间 4ms。
3.把 resmgr_msgwrite() 换为 memcpy(),进程被kill…
猜测是因为使用方法不对,memcpy() 复制数据的时候超出了进程自己的空间。。。
于是,预先定义了两个 153600bytes 的 buffer,用 memcpy()单纯对这2个进行复制操作,总共需要时间 4ms ~ 4.8ms。在原来的基础上增加了几十到几百 us 的样子。也就是说, memcpy() 只需要 us 级。

结论是使用 在驱动里 resmgr_msgwrite() 传送 153600bytes 数据需要 7ms 左右的时间。远远超过使用 memcpy()。

于是尝试通过使用其它更高效率的方法来缩短时间。

使用资源管理器的话,用户程序和驱动之间的数据交换一般使用什么函数?

譬如说,我目前使用的是

resmgr_msgread( *ctp, *buf, len, offset )
resmgr_msgwrite( *ctp, *buf, len, offset )

有没有其它的方法?
比如说直接用 memcpy() 复制。
如果可以的话, memcpy( *dst, *res, len ) 和 resmgr_msgwrite( *ctp, *buf, len, offset ) 做对照,参数的对应关系是不是下面的样子?

*dst = *ctp + offset
*res = *buf

(接续上面的问题。。。)

具体情况如下:

驱动提供的 read( fd, *buf, len ) 函数,定义为

int usb_io_read( resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb )

注册为

usb->resmgr_io_funcs.read = usb_io_read;

参考官方资料,参数 io_read_t *msg 指向一个共用体

typedef union {
    struct _io_read             i;
/*  unsigned char               data[nbytes];   */
/*  nbytes is returned with MsgReply */
} io_read_t;

根据资料,向着紧接着这个共用体的区域写数据,可以通过资源管理器传到用户程序的样子。。。
于是猜测,可以通过使用 memcpy() 直接向着那个区域复制数据,实现 resmgr_msgwrite( *ctp, *buf, len, offset ) 的作用。
但是,尝试的结果是应户程序无法得到数据。
到底是哪里出了问题呢?

定义函数 devctl() 的时候也遇到了同样的问题。

int usb_io_devctl( resmgr_context_t *ctp, io_devctl_t *msg, RESMGR_OCB_T *ocb )

官方资料说,参数 io_devctl_t *msg 指向的共用体之后的空间既可以用作从用户程序向驱动传输数据,又可以用作从驱动向用户程序传输数据。
但是不论我怎么尝试都没有成功。。。 更糟糕的是,即使使用 resmgr_msgwrite( *ctp, *buf, len, offset ) 也得不到数据:cry:

请求达人指点。谢谢。

我不记得你有没有提过你的CPU是什么了。

如果memcpy()真如你所说,在你的硬件上只有几十到几百us的话,resmgr_msgwrite()要花费7ms一定有问题。或者是时间测量的方法不对,或者有别的原因。

你可以另外写一个最简单的服务器/客户端,试试从服务器端向客户端返回150K数据,我觉得肯定不会要7ms那样长。

你的其它方法, memcpy()不能直接在你的驱动与你的用户程序里使用,(它们是两个不同进程)devctl()可以传数据,但理论上它消耗的时间与resmgr_msgwrite()的效果是一样的。所以,解决你的7ms的问题应该是主要方向。做个kernel trace也可以更好地理解为什么是7ms了。

感谢回复。

相关的测试正在做。

另外, devctl() 是为了解决一些命令的问题,并不是用来传输大量数据的,所以也不担心时间。
关于使用 devctl() 传输数据(几个字节大小),还望赐教。

参考了xtang的教程,测试如下:

server.c

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>

_uint8		buf[ 153600 ];

int main( int argc, char **argv )
{
	int chid, rcvid, status;

	if( ( chid = ChannelCreate( 0 ) ) == -1 ) {
		perror ( "ChannelCreate" );
		return -1;
	}

	printf( "Server is ready, pid = %d, chid = %d\n", getpid( ), chid );

	for( ; ; ) {
		if( ( rcvid = MsgReceive( chid, buf, sizeof(buf), NULL ) ) == -1 ) {
			perror( "MsgReceive" );
			continue;
		}

		MsgReply( rcvid, 0, 0, 0 );
	}

	ChannelDestroy( chid );

	return 0;
}

client.c

#include <stdio.h>
#include <string.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

_uint8		buf[ 153600 ];

int main( int argc, char **argv )
{
	pid_t			spid;
	int			chid, coid;
	_uint32		i;
	_uint8		reply_buf[ 64 ];

	double		us;
	_uint64		cps, cycle1, cycle2, ncycles;
	cps = SYSPAGE_ENTRY( qtime ) -> cycles_per_sec;
	printf( "this system has %lld cycles/s.\n", cps );

	if( argc < 3 ) {
		fprintf( stderr, "Usage: client <pid> <chid>\n" );
		return -1;
	}

	spid = atoi(argv[ 1 ]);
	chid = atoi(argv[ 2 ]);

	if( ( coid = ConnectAttach( 0, spid, chid, 0, 0 ) ) == -1 ) {
		perror( "ConnectAttach" );
		return -1;
	}

	for( i = 0; i < 153600; i += 2 ) {
		buf[ i ] = 0xAA;
		buf[ i + 1 ] = 0x55;
	}

	cycle1 = ClockCycles( );
	for( i = 0; i < 100; i ++ ) {
		if( MsgSend( coid, buf, sizeof(buf), reply_buf, sizeof(reply_buf) ) != 0 ) {
			perror( "MsgSend" );
			return -1;
		}
	}
	cycle2 = ClockCycles( );
	ncycles = cycle2 - cycle1;
	us = ( float )ncycles * 1000000 / cps / 100;
	printf( "time to send 153600-byte-sized data: %lf us.\n", us );

	ConnectDetach( coid );

	return 0;
}

结果是从客户端向服务器端传送 153600byte 的数据,耗时 35us 左右。

实际情况是,自制的USB驱动注册到资源管理器,用户程序调用驱动从外部设备读取数据,USB驱动调用函数 resmgr_msgwrite() 将得到的 153600byte数据传送到用户程序的缓存,耗时 7ms。
参考下面的驱动,实际上就是 resmgr_msgwrite() 这个函数耗时间。

USB驱动的 read() 函数大致如下:

int USB_io_read( resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb )
{
	USB_device_t	*USB_device;
	uint32_t		pos;
	uint8_t		*ibuf;
	uint32_t		nbytes;
	uint32_t		status;
	uint32_t		alen;

	USB_device	= (USB_device_t *)ocb->attr;
	pos		= 0;
	ibuf		= USB_device->buffer;
	nbytes		= msg->i.nbytes;

                /*一些设置步骤略*/

	while( 1 ) {
		if( status = USB_io( USB_device, USB_device->ep_in, URB_DIR_IN, ibuf, min( nbytes, MAX_BUFFER ) ) ) {
			break;
		}

		usbd_urb_status( USB_device->urb, NULL, &alen );
		resmgr_msgwrite( ctp, ibuf, alen, pos );      // 就是这里
		nbytes	-= alen;
		pos	+= alen;

		if( !nbytes || !alen ) {
			atomic_clr_value( &USB_device->status, USB_READING );
			_IO_SET_READ_NBYTES( ctp, pos );
			return( EOK );
		}
	}

	atomic_clr_value( &USB_device->status, USB_READING );
	return( status );
}

简单说明,用户程序调用 read( fd, &buf, 153600 );
而驱动里面的 MAX_BUFFER 也设置为 153600 了。
所以,resmgr_msgwrite() 仅仅被调用一次。现在想要知道如何缩短这个函数的时间。

我不知道 具体这个7ms是怎么算的,是计算有resmgr_msgwrite()和没有时,整个while循环所消耗的时间,对吗?

你能肯定那个循环只循环了一次吗?USB_io()确保返回 MAX_BUFFER字节的东西吗?

如果你能肯定只循环了一次的话,可以分别量量 USB_io(),usbd_urb_status(),resmgr_msgwrite()各消耗了多少时间吗?

如果循环超过一次,下面这样的程序效率会好些:

int USB_io_read( resmgr_context_t *ctp, io_read_t *msg, RESMGR_OCB_T *ocb )
{
	USB_device_t	*USB_device;
	uint32_t		pos;
	uint8_t		*ibuf;
	uint32_t		nbytes;
	uint32_t		status;
	uint32_t		alen;

	USB_device	= (USB_device_t *)ocb->attr;
	pos		= 0;
	ibuf		= USB_device->buffer;
	nbytes		= min(msg->i.nbytes, MAX_BUFFER);

                /*一些设置步骤略*/

	while( nbytes ) {
		if( status = USB_io( USB_device, USB_device->ep_in, URB_DIR_IN, &ibuf[pos], min( nbytes, MAX_BUFFER ) ) ) {
	            atomic_clr_value( &USB_device->status, USB_READING );
	            return( status );
		}

		usbd_urb_status( USB_device->urb, NULL, &alen );
		nbytes	-= alen;
		pos	+= alen;
        }
	atomic_clr_value( &USB_device->status, USB_READING );
	_IO_SET_READ_NBYTES( ctp, pos );
	return( _RESMGR_PTR(ctp, ibuf, pos ));
}

十分感谢多次帮助。

是的。用了2种方法来计算。(以下结果为多次运行的平均值)
第一种就是干脆屏蔽掉 resmgr_msgwrite() ,整个过程的运行时间由 11.4ms 减少到 4.45ms
第二种是在函数 resmgr_msgwrite() 前后使用函数 ClockCycles() 得到系统时间,计算得到 7.04ms。这时的总运行时间为 11.58ms

可以肯定。
在函数 usbd_urb_status() 后面添加了打印语句打印 alen 的值,即实际得到的数据长度值。
结果是只打印了一次,打印值为 153600

分别在以上三个函数前后使用函数 ClockCycles() 得到系统时间,多次运行的平均值分别为

USB_io() 4.4ms
usbd_urb_status() 0.5us
resmgr_msgwrite() 7.0ms



看了程序的建议,比原来的要好的样子。
但是,刚才作了测试,如果 MAX_BUFFER 仍然设定为 153600 的话,总的运行时间基本没有变化,仍然是 11ms 以上;如果设定为 512(或者其他比 153600 小的数字)的话,时间会增加,最大要 30、40ms。
虽然没有直接的证据,但是个人猜测原因在于 USB传输的问题。因为USB擅长一次性大量数据的传输,分为少量数据分段传输的话反而会增加时间消耗。

最后,本人虽然也在看官方关于资源管理器的资料(英文版和日文版,话说没有中文版么?),但是速度比较慢。。。希望能解释一下使用函数 devctl() 传输数据的问题,谢谢!

看看resmgr_msgwrite()的源码,这函数直接调 MsgWrite()。

如果这也要7.0ms,是不是USB_device->buffer (ibus) 是non-cache的?有没有可能map成Cache的?看看 cache_init()/CACHE_FLUSH()/CACHE_INVAL()的说明。

这个真没有。 :slight_smile:

我估计你不能收数据,是因为DCMD_的定义。看看/usr/include/sys/dcmd_*.h里的定义,devctl()的命令字,一定要用 _DIOT/_DIOF/_DIOTF 宏。这些宏代表了该命令的数据流向。(to/from/tofrom)

像你,是希望从资源管理器取得数据的,那么要用_DIOF。另外,传递数据的大小也编码在命令你,所以最大也只能传递 65536字节。

感谢回复。
说句题外话,加拿大现在已经是深夜了吧。在加班么?要注意身体。

我刚才看了 cache_init()/CACHE_FLUSH()/CACHE_INVAL() 的资料,这个是关于内存和 CPU 自带的缓存间数据同步问题吧。还真的没有考虑过。。。
仔细想想, USB 传输应该也是 DMA 吧,只不过系统把相关的设定都搞定了,平常觉察不到?
关于 cache_init()/CACHE_FLUSH()/CACHE_INVAL() 的使用,正在寻找相关资料中。。。

USB驱动使用的空间,是通过函数 usbd_alloc() 申请的,交换的数据也都是存在里面。语句如下:

USB_device->buffer = usbd_alloc( BUFFER_SIZE )

网上关于 cache_init() 的资料很少。
只看到“Foundry27”上有人问到使用 PCI 传输速度慢的问题http://community.qnx.com/sf/discussion/do/listPosts/projects.core_os/discussion.buildbugs.topc4820,原因是使用函数 mmap() 时用了参数 PROT_NOCACHE.

目前还没有找到怎么做相关设置。。。 :cry:

感谢对于 devctl() 的提示,确实是那个问题导致驱动与用户程序之间无法交换数据。

在官网对函数 devctl() 的解释中,提到:
use these macros to set up the device-control commands:

  • __DIOF
    __DION
    __DIOT
    __DIOTF

我真傻,真的。。。当初就没把这句话当回事。。。

资料上说,在 MsgRead*() MsgWrite*() 函数使用后,还需要使用 MsgReply() 来结束消息的传输。
而函数 resmgr_msgread() / resmgr_nsgwrite() 实际上只是直接调用 MsgRead() / MsgWrite()。那么,这两个函数后面也需要使用 MsgReply() 来结束传输么?

因为资料上没有说明,而且实际使用时也不需要 MsgReply() 的样子。
所以我猜测是系统帮忙搞定的,不知道对不对,该怎么解释呢?

是的。你的函数是resmgr回调的,所以你的函数的return值会由resmgr帮你MsgReply()回去。

在特殊的情况下,如果你不希望系统帮你MsgReply(),可以返回特殊的返回值 _RESMGR_NOREPLY

关于使用 resmgr_msg() 时间过长的问题,从xtang那里得到了可能和 cache 有关的提示。

但是。。。一直无处下手。 :confused:
而且相关资料也很少。

不知道还有没有什么提示和意见,谢谢!

驱动:

fd = ( "/usb_mem", O_RDWR, 0777 );
ftruncate( fd, sizeof(USB_buf_t) );
addr = (usb_buf_t *) mmap( 0, sizeof(usb_buf_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );
memcpy( addr, buf, 153600 );

其中 addr 指向共享空间, buf 指向驱动由函数 usbd_alloc( ) 得到的缓存。

用户程序:

fd = ( "/usb_mem", O_RDWR, 0777 );
ftruncate( fd, sizeof(USB_buf_t) );
addr = (usb_buf_t *) mmap( 0, sizeof(usb_buf_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 );

结果是, memcpy( ) 这个函数居然也需要 7ms …
而且,虽然驱动和用户程序得到的共享空间的地址相同,驱动也确实把数据复制到了共享空间,但是,用户程序得到的数据却都是 0 …

我第一次用共享空间,不知道是哪里出问题了呢?

假设两个fd都是shm_open()得到的,用户程序里不用ftruncate()了,直接mmap()就可以了。

如果usb_buf_t有153600那么大的话,你这相程序应该可以了。不过,这个memcpy()花的时间也太长了吧。

你这是什么板子?ARM?