转自:
在一些数据采集或量测应用中,必须快速且可靠的把大量数据记录到储存装置,以便做事后的分析或处理。然而对应用程序开发者来说,大量且实时地纪录数据一直是一项极大的挑战。因为这个需求,有一些公司提供高速数据记录装置,让数据能不经系统的内存,由数据采集卡通过直接内存存取 (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 libraryhASPI32DLL = LoadLibrary( "wnaspi32" );// Get the procedure addresslpfnSendCommand = ( DWORD( __cdecl *)(LPSRB) )GetProcAddress(hASPI32DLL, "SendASPI32Command" ) ;lpfnGetInfo = ( DWORD ( __cdecl *)(void) )GetProcAddress(hASPI32DLL, "GetASPI32SupportInfo" );// Identify the ASPI drivers and SCSI adapter installed correctlyDWORD 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 TypeSRB_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 Descriptionunsigned 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 CapacitySRB_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的信息。以下的程序代码展示如何读取资料及写入资料// ReadingSRB_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) |