Chinaunix首页 | 论坛 | 博客
  • 博客访问: 319471
  • 博文数量: 80
  • 博客积分: 773
  • 博客等级: 军士长
  • 技术积分: 799
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-24 23:52
个人简介

victory for prepared

文章分类

全部博文(80)

文章存档

2024年(2)

2023年(1)

2022年(2)

2021年(1)

2018年(8)

2017年(3)

2016年(20)

2015年(12)

2013年(5)

2012年(25)

2011年(1)

我的朋友

分类: WINDOWS

2012-04-24 18:23:28

转自:

在一些数据采集或量测应用中,必须快速且可靠的把大量数据记录到储存装置,以便做事后的分析或处理。然而对应用程序开发者来说,大量且实时地纪录数据一直是一项极大的挑战。因为这个需求,有一些公司提供高速数据记录装置,让数据能不经系统的内存,由数据采集卡通过直接内存存取 (Direct Memory Access, DMA) 的方式,经过PCI总线直接储存到硬盘,其架构如图一所示,但是这种装置通常很昂贵。



图二显示在没有以上所提到的高速数据记录装置的标准PC上,一般做数据记录的流程及架构。我们可以看到数据经PCI总线先进到主存储器后,再经过PCI总线存到硬盘。这两个数据流都要占用PCI总线的频宽,所以能达到的实时数据记录速度只有图一架构的一半,以32-bit 33MHz的PCI总线来说,大约可以达到40Mbytes/sec以上的速度。所以对记录速度的需求在40Mbytes/sec以下的应用,这样的架构仍然可以适用。这个架构下,数据记录的瓶颈是从主存储器到硬盘这个过程,一般这个程序是经由档案系统 (file system) 来处理。但是,单纯地将数据记录在档案系统内,则必然受限于原本操作系统与档案系统的限制(例如:MS-DOS对 2-Gbytes的限制、Win32的快取系统)。这些先进的档案系统反而会对高速存盘造成累赘。若硬盘为IDE硬盘,通常储存速度低于2Mbytes/sec,即使使用SCSI硬盘大概也只能达到10Mbytes/sec左右的速度。


然而如果可以跳过档案系统直接存取SCSI装置,储存的速度将会有非常大的改进。以下我们要提出的就是一个经济而且简单的软件解决方法,这个方法藉由直接存取SCSI装置而可以达到40Mbytes/sec的数据记录速度。凌华科技的DAQCreator数据采集工具软件即运用此方法以实现40Mbytes/sec的实时数据记录功能,提供客户一个经济的解决方案。

这个方法的基本观念就是跳过档案系统,直接控制SCSI适配卡。然而,撰写各种SCSI适配卡的驱动程序来完成这工作是十分耗时的,选择被SCSI装置所接受的共通协议才能使应用程序能兼容于各种SCSI适配卡。

SCSI2标准规格对SCSI2装置定义了共通的指令集 (command set),透过这些指令,程序设计师能直接的控制SCSI装置。然而,要正确的操作SCSI装置,程序设计师必须熟悉各类型的SCSI装置,并了解相关的指令,SCSI2指令是低阶的指令,复杂且不容易使用。幸运的是,由Adaptec公司所提供的ASPI (Advanced SCSI Programming Interface) 为程序设计师解决了这些困扰。程序设计师能使用ASPI高阶的函式及指令,透过ASPI driver代为传送正确的SCSI2指令。

利用ASPI函式能控制各种SCSI的外围装置,而且程序可以兼容于各种SCSI适配卡。就因为它的方便性,使得ASPI在DOS, Windows以及OS2上成为通用的SCSI程序设计接口。而在Win32的环境下,ASPI Manager是以动态连结函式库 (Dynamic Link Library, DLL) 的型态安装于系统内,提供并管理ASPI函式的功能。藉由ASPI的函式,程序设计师能查询到SCSI卡的信息和所有安装于SCSI卡上的外围装置,也能透过ASPI的函式,执行各种SCSI的输出输入指令。

下图显示使用ASPI控制SCSI外围装置的流程图。


在Win32系统下,ASPI Manager (wnaspi32.dll) 提供下列五个函式:

DWORD GetASPI32SupportInfo (void);
DWORD SendASPI32Command( LPSRB );
BOOL GetASPI32Buffer( PASPI32BUFF );
BOOL FreeASPI32Buffer( PASPI32BUFF );
BOOL TranslateASPI32Address( PDWORD, PDWORD ); 

在我们的应用程序中,我们将使用GetASPI32SupportInfo() 与SendASPI32Command() 这两个函式。我们藉由呼叫GetASPI32SupportInfo(),检查ASPI driver与SCSI卡是否安装正确;而SendASPI32Command() 则是用来传递我们所需要的ASPI指令。以下是加载wnaspi32.dll的程序代码:

// Load the ASPI32 dynamic linked library
hASPI32DLL = LoadLibrary( "wnaspi32" );

// Get the procedure address
lpfnSendCommand = 
( DWORD( __cdecl *)(LPSRB) )GetProcAddress(hASPI32DLL, "SendASPI32Command" ) ;
lpfnGetInfo = 
( DWORD ( __cdecl *)(void) )GetProcAddress(hASPI32DLL, "GetASPI32SupportInfo" );

// Identify the ASPI drivers and SCSI adapter installed correctly
DWORD ASPIStatus;
ASPIStatus = (*(lpfnGetInfo))(); 

注:这篇文章中的程序所用到的数据结构列在文章后的附录中,所有的数据结构及相关的常数是采用Alvise Valsecchi所建的网站 ()上的标头档案 (header file) wnaspi32.h与scsidefs.h里的信息。

ASPI Manager的SendASPI32Command() 函式可以接受以下八个指令:


??????

Inquiry: 取得安装的SCSI适配卡的信息 
Get device type: 取得指定的SCSI地址的装置型式(device type) 
Abort SRB: 取消之前所下的ASPI指令 
Get disk info: 取得SCSI2硬盘的BIOS信息 (只适用于Windows 95系统) 
Rescan SCSI2 bus: 对SCSI2总线重新扫描 (rescan) 
Get/set timeouts: 取得或设定指定装置的SRB逾时值 (timeout) 
Exec SCSI2 command: 送一个SCSI2 指令到某个SCSI2装置 
Reset device: 送Bus Device Reset 讯息到某个SCSI2装置

Inquiry 指令能收集特定SCSI卡上所安装的SCSI外围装置的信息。所有的SCSI装置将以host adapter number,SCSI ID及LUN number来识别。以下是取得SCSI卡的host adapter number信息的程序范例:

SRB_HAInquiry srbHAInquiry;
srbHAInquiry.SRB_Cmd = SC_HA_INQUIRY;
srbHAInquiry.SRB_HaId = 0;
// Get the number of host adapters installed
(lpfnSendCommand)((LPSRB)&srbHAInquiry);
< Host Adapter Number > = srbHAInquiry.HA_Count; 

以下的程序则可以用来取得每个SCSI外围装置的Device Type、Device Description以及Device Capacity。

// Get the Device Type
SRB_GDEVBlock srbGDEVBlock;
srbGDEVBlock.SRB_Cmd = SC_GET_DEV_TYPE;
srbGDEVBlock.SRB_HaId = < Host Adapter ID >;
srbGDEVBlock.SRB_Target = < SCSI ID >;
srbGDEVBlock.SRB_Lun = < SCSI LUN >;
(lpfnSendCommand)((LPSRB)&srbGDEVBlock);
< Device Type > = srbGDEVBlock.SRB_DeviceType 

// Get the Device Description
unsigned char szBuffer[37]; 
SRB_ExecSCSICmd srbExec;
srbExec.SRB_Cmd = SC_EXEC_SCSI_CMD;
srbExec.SRB_HaId = < Host Adapter ID >;
srbExec.SRB_Flags = SRB_DIR_IN;
srbExec.SRB_Target = < SCSI ID >;
srbExec.SRB_Lun = < SCSI LUN >;
srbExec.SRB_BufLen = 36;
srbExec.SRB_BufPointer = szBuffer;
srbExec.SRB_SenseLen = SENSE_LEN;
srbExec.SRB_CDBLen = 6;
srbExec.CDBByte [ 0 ] = SCSI_INQUIRY;
srbExec.CDBByte [ 4 ] = 36
(lpfnSendCommand)((LPSRB)&srbExec);
< Device Description > = srbGDEVBlock.SRB_DeviceType 

// Get the Device Capacity
SRB_ExecSCSICmd SRB;
unsigned char buf[10];
SRB.SRB_Cmd = SC_EXEC_SCSI_CMD;
SRB.SRB_HaId = < Host Adapter ID >;
SRB.SRB_Target = < SCSI ID >;
SRB.SRB_Lun = 0;
SRB.SRB_Flags = SRB_DIR_IN;
SRB.SRB_BufLen = 8;
SRB.SRB_BufPointer = (unsigned char *)buf;
SRB.SRB_SenseLen = SENSE_LEN;
SRB.SRB_CDBLen = 10;
SRB.SRB_PostProc = NULL;
SRB.CDBByte[0] = SCSI_RD_CAPAC;
SRB.CDBByte[1] = 0;
SRB.CDBByte[2] = 0;
SRB.CDBByte[3] = 0;
SRB.CDBByte[4] = 0;
SRB.CDBByte[5] = 0;
SRB.CDBByte[6] = 0;
SRB.CDBByte[7] = 0;
SRB.CDBByte[8] = 0;
SRB.CDBByte[9] = 0;
(lpfnSendCommand)((LPSRB)&SRB);
Then, the block number for the specific device can be gained from the following data:
< block number > = buf[0] * 0x01000000 + buf[1] * 0x010000 + buf[2] * 0x0100 + buf[3];
And the sector size is:
< sector size > = buf[4] * 0x01000000 + buf[5] * 0x010000 + buf[6] * 0x0100 + buf[7]; 

硬盘存取是以传送Exec SCSI2 command 指令来达成,这个指令的封包必须包括adapter number, SCSI ID以及LUN number的信息。以下的程序代码展示如何读取资料及写入资料

// Reading
SRB_ExecSCSICmd READSRB;

READSRB.SRB_Cmd = SC_EXEC_SCSI_CMD;
READSRB.SRB_Lun = 0;
READSRB.SRB_Flags = SRB_DIR_IN;
READSRB.SRB_SenseLen = SENSE_LEN;
READSRB.SRB_CDBLen = 10;
READSRB.SRB_PostProc = NULL;

READSRB.SRB_BufLen = < Reading Size >;
READSRB.SRB_BufPointer = (BYTE *)< Reading Buffer Address >;
READSRB.CDBByte[0] = SCSI_READ10;
READSRB.CDBByte[1] = 0;
READSRB.CDBByte[2] = HIBYTE(HIWORD(block_idx));
READSRB.CDBByte[3] = LOBYTE(HIWORD(block_idx));
READSRB.CDBByte[4] = HIBYTE(LOWORD(block_idx));
READSRB.CDBByte[5] = LOBYTE(LOWORD(block_idx));
READSRB.CDBByte[6] = 0;
READSRB.CDBByte[7] = HIBYTE(access_blocks);
READSRB.CDBByte[8] = LOBYTE(access_blocks);
READSRB.CDBByte[9] = 0;

(lpfnSendCommand)((LPSRB)&READSRB);

// Writing 
SRB_ExecSCSICmd WRITESRB;

WRITESRB.SRB_Cmd = SC_EXEC_SCSI_CMD;

WRITESRB.SRB_Flags = SRB_DIR_OUT;
WRITESRB.SRB_SenseLen = SENSE_LEN;
WRITESRB.SRB_CDBLen = 10;
WRITESRB.SRB_PostProc = NULL;

WRITESRB.SRB_BufLen = < Writing Size >;
WRITESRB.SRB_BufPointer = (BYTE *)< Writing Buffer Address >;
WRITESRB.CDBByte[0] = SCSI_WRITE10;
WRITESRB.CDBByte[1] = 0;
WRITESRB.CDBByte[2] = HIBYTE(HIWORD(block_idx));
WRITESRB.CDBByte[3] = LOBYTE(HIWORD(block_idx)
阅读(1472) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~