随着计算机网络技术和计算机图形图像处理技术的飞速发展,影视行业对计算机技术的依赖程度越来越大。影视数字化编辑播出系统是一个信息量巨大、精度高、实时性强的高速复杂的分布式多媒体网络信息系统。它所需要的网络服务器的规模很大,因此对服务器存储技术要求非常高。目前服务器的存储技术主要分为直接连接存储技术DAS和附网存储技术NAS。为了建立影视数字化与空间数据组织及管理的原型实验室,作为其先期的工作,需要对SCSI技术有深入的研究与分析,以期实现大容量数据的高效分布式存储及调度。本文对SCSI接口和Linux下的SCSI API进行了详细的分析,并运用此API作了与数据存储相关的实现。
1 SCSI接口
SCSI-小型计算机系统接口,是种较为特殊的接口总线,具备与多种类型的外设进行通信。SCSI采用ASPI的标准软件接口使驱动器和计算机内部安装的SCSI适配器进行通信。SCSI接口是一种广泛应用于小型机上的高速数据传输技术。SCSI接口具有应用范围广、多任务、带宽大、CPU占用率低,以及热插拔等优点。
SCSI接口为光存储产品提供了强大、灵活的连接方式,还提供了很高的性能,可以有7个或更多的驱动器连接在一个SCSI适配器上,其缺点就在于昂贵的价格。SCSI接口的光驱需要配合价格不菲的SCSI卡一起使用,而且SCSI接口的光驱在安装、设置时比较麻烦,所以SCSI接口的光驱远不如IDE接口光驱使用广泛。SCSI接口的光存储产品更多的是应用于有特殊需求的专业领域,家用产品几乎没有采用此类接口的。
SCSI是一种连结主机和外围设备的接口,支持包括磁盘驱动器、磁带机、光驱、扫描仪在内的多种设备。它由SCSI控制器进行数据操作,SCSI控制器相当于一块小型CPU,有自己的命令集和缓存。
SCSI接口从技术和性能上始终拥有顶级设备的特征。SCSI接口与IDE接口相比具有以下特点:(1)适应面广。使用IDE接口时,会受到系统IRQ(中断号)及IDE通道的限制,一般情况下每个IDE通道使用一个IRQ,所接的IDE设备最多不能超过15个。而使用SCSI时,所接的设备可以超过15个,且所有这些设备只占用一个IRQ。(2)多任务。SCSI允许在对一个设备传输数据的同时对另一个设备进行数据查找,这对于服务器来说是非常重要的。因为服务器常常需要同时处理几个进程,并且对服务器硬盘的操作也经常是既有读数据,又有写数据。(3)宽带宽。理论上,目前最快的SCSI总线带宽为160Mbps,这意味着硬盘的传输速率最高可达160Mbps(这是理论值,实际应用中会降低)。而目前最快的IDE接口硬盘的外部速度为100Mbps(即ATA-100,这也是理论值),而且由于各方面的限制,现在所能用的最高速度只有33Mbps。(4)较少的CPU占用率。使用传统IDE接口时,CPU需要全程控制数据的传输操作,所以在IDE传输数据的过程中,CPU不能执行任何操作,直到传输结束才可执行后续的指令。而SCSI在进行数据传输时,CPU在将传输指令传给SCSI后就可立即处理后续的指令,传输的工作则交给SCSI卡上的处理芯片自行负责,直到SCSI处理完毕、发出信号通知CPU后,CPU再进行后续处理,因此占用CPU资源较少。
2 Linux下的SCSI API
由于SCSI具有多任务的特点,因此它在多任务操作系统中可以获得更高的性能。为了能够更好地对Linux的SCSI子系统进行管理,本文先对SCSI体系结构和与系统相关的应用接口进行分析,然后再运用API对数据存储相关方面进行实现。
在linux中,用户编程接口API遵循了UNIX中最流行的应用编程界面标准---POSIX标准。POSIX标准是由IEEE和ISO/IEC共同开发的标准系统。该标准基于当时现有的UNIX实践和经验,描述了操作系统的系统调用编程接口API,用于保证应用程序可以在源程序一级上在多种操作系统上移植运行。这些系统调用编程接口主要是通过C库(LIBC)来实现的。
2.1 SCSI体系结构
从图1中可以看出,上层支持用户-内核接口,SD属于磁盘类,SR属于CD-ROM子系统,它们都有一个中断设备接口。ST是用来读写磁带的字符驱动器,SG是用一个字符设备接口连接设备的命令,它们都有一个字符设备接口。SCSI中间层定义了内部接口,为上层和下层驱动器提供服务。
通过SG驱动器的请求可以分为三个阶段:(1)从用户接受请求,预留所需要的资源。如果需要,在用户区的数据先被传送到内核缓冲区,然后将请求提交给SCSI中间层(然后给适配器)去执行。(2)假设SCSI适配器支持中断,当请求完成时中断就被接收。当中断到达时,数据传输即完成。也就是说,如果SCSI命令是READ发的,则数据就在内核缓冲区或者在用户缓冲区。(3)用户通过一个调用取出请求的结果。如果需要,内核缓冲区中的数据会被传送到用户层,此时与这个请求相关的内核资源被释放。通常write()调用就是第一阶段的发送请求,之后read()调用取得数据或者出错信息。另外SCSI中间层自动维护一个队列以支持多个用户请求。
2.2 主要数据结构
当前通用SCSI-3驱动器的最主要数据结构是struct sg_io_hdr。其余的一些数据结构是关于特定驱动器信息(如struct sg_scsi_id,struct sg_req_info等)的。另外,老版本的SCSI接口struct sg_header是SCSI-2的主要控制结构。
sg_io_hdr中的主要成员有:interface_id,一般情况下它被置为‘S’,标志接口的版本;dxfer_direction标志数据的传输模式;SG_SXFER_NONE标记测试设备是否就绪;SG_SXFER_TO_DEV标记一个写命令;SG_SXFER_FROM_DEV标记一个读命令;SG_SXFER_UNKNOWN用在当前应用程序不清楚具体传输模式的情况下;cmdp指向一个SCSI命令的指针;cmd_len代表了命令的长度;dxferp表示用户内存缓冲空间,存放被传输数据;dxfer_len表示缓冲空间的大小;sbp表示错误信息的存放地点。其余一些数据成员是辅助性的成员,本文不再介绍。此外,有关命令操作码可以参看scsi.h文件。
2.3 系统调用
成功地打开一个sg设备文件名,就在文件描述符和连接的SCSI设备间建立了一个连接。sg设备保存着在SCSI设备和文件描述符层(如保留缓冲区)中的状态信息和资源。即使SCSI设备被拔掉,一个应用仍然可以拥有该设备的文件描述符。如USB这样的热拔插设备就可以被拔掉,但它的文件描述符仍然存在。此时大多数试图访问该设备的系统调用会生成ENODEV错误,poll()调用会产生一个POLLHUP错误,但close()调用会正常完成。如对这个文件进行open()操作,则也会产生一个ENODEV错误。
(1)open()调用
调用的形式为open(constchar*filename,int flags)。
filename为sg设备文件名。flags为以下标记或者为以下标记的组合:
O_RDONLY:只能用于read()和ioclt()操作中。
O_RDWR:允许所有的系统调用执行。
O_EXCL:在处理之前等待与SCSI设备相关的其他打开关闭。当其他人打开了一个SCSI设备,如果设置了O_NONBLOCK就会产生EBUSY。该标记不允许和O_RDONLY、O_EXCL一起用。
O_NONBLOCK:设置non_blocking模式。这个标记被ioclt(SG_IO)忽略。
在以上的标记中,O_RDONLY和O_RDWR必需设置,其他标记可以任选。
(2)write()调用
调用形式为write(int sg_fd,const void*buffer,size_t count)。
buffer应该指向数据类型为sg_io_hdr_t的对象,count为(sg_io_hdr_t)的大小。如果write()调用成功,count就作为结果返回,命令请求就被发送给SCSI设备。
(3)read()调用
调用形式为read(int sg_d,void*buffer,size_t count)。
buffer应该指向数据类型为sg_io_hdr_t的对象,count为(sg_io_hdr_t)的大小。如果read()调用成功,则count作为结果返回。通常read()返回的是最早被完成的命令请求。
(4)poll()调用
调用的形式为poll(struct pollfd*ufds,unsigned int nfds,int timeout)。这个调用用于测试sg文件的当前状态。POLLIN说明可以进行read()调用,POLLOUT则代表可以发送write()命令,POLLHUP意味一个设备被分离需要进行清理工作,POLLERR则表示出错。
(5)close()调用
调用的形式为close(int sg_fd)。close()调用应该在所有write()及read()调用完成之后进行。返回0代表成功,-1表示错误。
3 SCSI API在数据存储中的应用
与存储相关的 SCSI 命令一般是在 SCSI ArchitectureModel(SAM)、SCSI Primary Commands (SPC) 和 SCSI Block Commands (SBC) 中定义的:
* SAM 定义 SCSI 系统模型、SCSI 标准集的功能性分区,以及适用于所有 SCSI 实现和实现标准的需求。
* SPC 定义对所有 SCSI 设备模型通用的行为。
* SBC 定义命令集扩展,以方便操作 SCSI 直接访问块设备。
每个 SCSI 命令都由 Command Descriptor Block (CDB) 描述,它定义 SCSI 设备执行的操作。SCSI 命令涉及到用于向 SCSI 设备传输数据(或从中输出数据)的数据命令,以及用于设置 SCSI 设备的配置参数的非数据命令。
在一台双CPU HP服务器(512MB)上挂载了二个Ultra3 Lvd SCSI硬盘,系统平台为RedHat Linux 7.3,其内核为Linux2.4.18。往内存中随机写入200MB数据,然后按一定的策略将其分布储存到二个SCSI硬盘,其算法流程见图2。
读操作所涉及到函数是int sg_read(int sg_fd,unsigned char*buff,int blocks,int from_block,int bs,int*diop)。其中sg_fd代表数据源所在设备的句柄,buff是获得数据的存储区,这个函数从sg_fd的from_block处开始读取blocks*bs大小的数据到buff中去。diop标志是否使用DMA方式。下面是用C语言编写的读操源程序的主要部分,略去了一些完整性控制。
按照上述的算法,将200MB的数据分布地写到二个SCSI硬盘上,同时也将200MB的数据写到一个SCSI硬盘上进行对比测试。测试时使用Linux系统下的time函数获得运行时间,图3的纵坐标表示传输200MB数据所需要的总时间。从图3中可以得到单硬盘传输200MB数据所需要的平均时间为12.4112s,平均的传输速率为16.115Mbps,这和系统提供的拷贝命令的传输速率差不多。按上述算法存储200MB数据所花的平均时间为8.8314s,平均的传输速率是22.647Mbps。不难发现分布储存的效率比一个硬盘储存要高40.5%,具体数据参见图3。