Small Computer Systems Interface (SCSI) 是一组标准集,它定义了与大量设备(主要是与存储相关的设备)通信所需的接口和协议。 Linux® 提供了一种 SCSI 子系统,用于与这些设备通信。Linux 是分层架构的一个很好的例子,它将高层的驱动器(比如磁盘驱动器或光驱)连接到物理接口,比如 Fibre Channel 或 Serial Attached SCSI(SAS)。本文向您介绍了 Linux SCSI 子系统,并且讨论了这些子系统将来的发展方向。
GNU/Linux 和 SCSI 是很好的一个组合,因为二者在各自的环境中具有类似的特征。GNU/Linux 是一种安全可靠的操作系统,可以不间断地运行。SCSI 适合于可靠和高性能存储。二者都是开源的。您可以下载和查阅 International Committee on Information Technology Standards (INCITS) T10 Technical Committee 的各种 SCSI 规范。同样地,您也可以下载 GNU/Linux 源代码以理解其实现。它们在各自的行业都具有主导性,所以相对其他操作系统而言,GNU/Linux 能更好地支持 SCSI,这一点就不足为奇了。
SCSI 是一种很有趣的接口,它是早期的接口之一,如今还在不断演化。第一种 SCSI 标准称为 SCSI-1,是由 Shugart Associates 在 1979 年前后创建的。SCSI-1 定义了一种具有 5MHz 数据时钟的 8-bit 并行接口,能提供最高 5 兆字节每秒(5 MB/s)的数据传输速率。
SCSI-2 标准出现于 1985 年,它的出现使数据速率更快(10MHz),总线也更宽(16 位)。被称为 Fast/Wide 的 SCSI-2 允许的数据传输速率高达 20 MB/s,并具有与 SCSI-1 的向后兼容性,但是速率也会限制到 SCSI-1 的数据速率。
SCSI-3 的开发开始于 1993 年,现已成为了一组标准集,可以定义协议、命令集和信令方法。在 SCSI-3 中,包含一组命名为 Ultra 的并行 SCSI 标准和基于串行 SCSI 的协议,比如 IEEE 1394 (FireWire)、Fibre Channel, 、Internet SCSI (iSCSI) 和新兴的 SAS。这些标准通过引入存储网络技术(比如 FC-AL 或 iSCSI)改变了传统的存储理念,将数据速率扩展到了 1 Gbit/s,将最大的可寻址设备数增加到了 100 以上,并将最大的电缆长度扩展到了 25 米。图 1 展示了从 1986 至 2007 年 SCSI 的数据速率的变化

图 1. SCSI 数据速率的演化
SCSI






SCSI 实现了一种客户机/服务器风格的通信架构。发起者向目标设备发送命令请求。该目标处理此请求并向发起者返回响应。发起者可以是托管计算机中的一个 SCSI 设备,而 SCSI 目标则可以是一个磁盘、光盘和磁带设备或特殊设备(比如箱体设备)。






SCSI 传输所采用的协议已经时过境迁,SCSI 命令却保持了最初的元素。SCSI 命令是在 Command Descriptor Block (CDB) 中定义的。CDB 包含了用来定义要执行的特定操作的操作代码,以及大量特定于操作的参数。
SCSI 命令支持读写数据(各有四个变量)以及很多非数据命令,比如 test-unit-ready(设备是否已就绪)、inquiry(检索有关目标设备的基本信息)、read-capacity(检索目标设备的存储容量)等等。目标设备支持何种命令取决于设备的类型。发起者通过 inquiry 命令识别设备类型。表 1 列出了最常用的 SCSI 命令。

表 1. 常见 SCSI 命令
命令用途
Test unit ready查询设备是否已经准备好进行传输
Inquiry请求设备基本信息
Request sense请求之前命令的错误信息
Read capacity请求存储容量信息
Read从设备读取数据
Write向设备写入数据
Mode sense请求模式页面(设备参数)
Mode select在模式页面配置设备参数

借助大约 60 种可用命令,SCSI 可适用于许多设备(包括随机存取设备,比如磁盘和像磁带这样的顺序存储设备)。SCSI 也提供了专门的命令以访问箱体服务(比如存储箱体内部当前的传感和温度)。更多信息,请参见 参考资料 部分。






图 2 显示了 SCSI 子系统在 Linux 内核中的位置。内核的顶部是系统调用接口,处理用户空间调用到内核中合适的目的地的路由(例如 open、read 或 write)。而虚拟文件系统(VFS) 是内核中支持的大多数文件系统的抽象层。它负责将请求路由到合适的文件系统。大多数文件系统都通过缓冲区缓存来相互通信,这种缓存通过缓存最近使用的数据来优化对物理设备的访问。接下来是块设备驱动器层,它包括针对底层设备的各种块驱动器。SCSI 子系统是这种块设备驱动器之一。

图 2. SCSI 子系统在 Linux 内核中的位置
SCSI
与 Linux 内核中的其他主流子系统不同,SCSI 子系统是一种分层的架构,共分为三层。顶部的那层叫做较高层,代表的是内核针对 SCSI 和主要设备类型的驱动器的最高接口。接下来的是中间层,也称为公共层或统一层。在这一层包含 SCSI 堆栈的较高层和较低层的一些公共服务。最后是较低层,代表的是适用于 SCSI 的物理接口的实际驱动器(参见图 3)。

图 3. Linux SCSI 子系统的分层架构
Linux SCSI 子系统的分层架构
在 ./linux/drivers/scsi 可以找到 SCSI 子系统(SCSI 较高层、中间层和各种驱动器)的源代码。SCSI 数据结构则位于 SCSI 源目录,在 ./linux/include/scsi 也可以找到。






SCSI 子系统的较高层代表的是内核(设备级)最高级别的接口。它由一组驱动器组成,比如块设备(SCSI 磁盘和 SCSI CD-ROM)和字符设备(SCSI 磁带和 SCSI generic)。较高层接受来自上层(比如 VFS)的请求并将其转换成 SCSI 请求。较高层负责完成 SCSI 命令并将状态信息通知上层。
SCSI 磁盘驱动器在 ./linux/drivers/scsi/sd.c 内实现。SCSI 磁盘驱动器通过调用 register_blkdev(作为块驱动器)进行自初始化并通过 scsi_register_driver 提供一组函数以表示所有 SCSI 设备。其中 sd_probesd_init_command 这两个函数很重要。只要有新的 SCSI 设备附加到系统, SCSI 中间层就会调用 sd_probe 函数。 sd_probe 函数可决定此设备是否由 SCSI 磁盘驱动器管理,如果是,就创建新的 scsi_disk 结构来表示它。 sd_init_command 函数将来自文件系统层的请求转变成 SCSI 读或写命令(为完成这个 I/O 请求, sd_rw_intr 会被调用)。
SCSI 磁带驱动器在 ./linux/drivers/scsi/st.c 内实现。磁带驱动器是顺序存取设备,会通过 register_chrdev_region 将自身注册为字符设备。SCSI 磁带驱动器还提供了一个 probe 函数,称为 st_probe。该函数会创建一种新磁带设备并将其添加到称为 scsi_tapes 的向量。SCSI 磁带驱动器的独特之处在于,如果可能,它可以直接从用户空间执行 I/O 传输。否则,数据会通过驱动器缓冲被分段。
SCSI CD-ROM 驱动器在 ./linux/drivers/scsi/sr.c 内实现。CD-ROM 驱动器是另一种块设备并为 SCSI 磁盘驱动器提供类似的函数集。 sr_probe 函数可用来创建 scsi_sd 结构以表示 CD-ROM 设备,并用 register_cdrom 注册此 CD-ROM。SCSI 磁带驱动器还会导出 sr_init_command,以将请求转换成 SCSI CD-ROM 读或写请求。
SCSI generic 驱动器在 ./linux/drivers/scsi/sg.c 内实现。该驱动器允许用户应用程序向设备发送 SCSI 命令(比如格式化、模式感知或诊断命令)。通过 sg3utils 包还可以从用户空间利用 SCSI generic 驱动器。这个用户空间包包括多种实用工具,可用来发送 SCSI 命令和解析这些命令的响应。






SCSI 中间层是 SCSI 较高层和较低层的公共服务层(可以在 ./linux/drivers/scsi/scsi.c 内部分地实现)。它提供了很多可供较高层和较低层驱动器使用的函数,因而可以充当这两层间的连接层。中间层很重要,原因是它抽象化了较低层驱动器(LLD)的实现,可以在 ./linux/drivers/scsi/hosts.c 中部分地实现。这意味着可以以同样的方式使用带不同接口的 Fibre Channel 主机总线适配器(HBA)。
低层驱动器注册和错误处理都由 SCSI 中间层提供。中间层还提供了较高层和较低层间的 SCSI 命令排队。SCSI 中间层的一个重要功能是将来自较高层的命令请求转换成 SCSI 请求。它也负责管理特定于 SCSI 的错误恢复。
中间层可以连接 SCSI 子系统的较高层和较低层。它接受对 SCSI 事务的请求并对这些请求进行排队以便处理 (如 ./linux/drivers/scsi/scsi_lib.c 中所示)。当这些命令完成后,它接受来自 LLD 的 SCSI 响应并通知较较高层此请求已经完成。
中间层最重要的职责之一是错误和超时处理。如果 SCSI 命令没有在合理的时间内完成或者 SCSI 请求返回错误,中间层就会管理错误或重新发送此请求。中间层还可管理较高层恢复,比如请求 HBA (LLD) 或 SCSI 设备重置。SCSI 错误和超时处理程序在 ./linux/drivers/scsi/scsi_error.c 内实现。






在最低层的是一组驱动器,称为 SCSI 低层驱动器。它们是一些可与物理设备(比如 HBA)链接的特定驱动器。LLD 提供了自公共中间层到特定于设备的 HBA 的一种抽象。每个 LLD 都提供了到特定底层硬件的接口,但所使用的到中间层的接口却是一组标准接口。
较低层包含大量代码,原因是它要负责处理各种不同的 SCSI 适配器类型。例如,Fibre Channel 协议包含了针对 Emulex 和 QLogic 的各种适配器的 LLD。面向 Adaptec 和 LSI 的 SAS 适配器的 LLD 也包括在内。





毫无疑问,SCSI 的发展前景很好,并且它会与 Linux 紧密相关。随着 SCSI 的演化,Linux 将会一如既往地为不断发展的技术提供支持。Linux 借助面向 HBA 的驱动器为新的 SAS 协议提供支持。随着协议向更快的速度发展(比如 6 Gb SAS 或 8 Gb FC),Linux 必将处在发展和部署的前沿。
您还会发现 Linux 恰恰就是新 SCSI 协议的先进之处。FCoE(Fibre Channel over Ethernet)颇值得一提。FCoE 是全双工 Ethernet 网络(通常是 1Gb 或 10Gb Ethernet)上的一种 Fibre Channel 框架的映射。FCoE 之所以重要,是因为它将主流的网络媒介与主流的企业存储协议连接起来。这种新技术必然受人瞩目,而且 Linux 也将不会例外。
针对 SCSI 的端到端数据保护也在开发中,它源于 T10 的新数据完整性标准。这个标准为每扇区都增加了一个数据完整性字段(DIF)以保护介质上的数据。这个新的 8 字节 DIF 字段包括一个循环冗余代码(CRC)用以保护数据,一个参考标签用以保护数据免遭误导写入,以及一个应用程序标签。应用程序标签特定于应用程序,并且可以定义数据的用途,例如,一个 PDF 文件的一部分。请参见 参考资料 部分以获取更多信息。






Linux 内核是抽象的分层结构的另一个典型示例。它将各种文件系统连接到不同的物理存储介质。若这些存储介质与 SCSI 相关,SCSI 子系统会将公共 Linux 块请求转化为针对特定底层设备的 SCSI 请求。SCSI 子系统本身在过去若干年中经历了很多变化,而且这些变化还在继续。诸如端到端数据保护这样的新技术,与 FCoE 这样的新协议一样,都在想方设法寻找与 Linux 建立关系的途径。