探索 Linux 通用 SCSI 驱动器
2015-07-30 22:00
549 查看
探索 Linux 通用 SCSI 驱动器
研究Linux 通用 SCSI 驱动器 API 及其用例
通过 SCSI 命令管理计算机上的数据,并将数据传输到 SCSI 设备。在本文中,作者介绍了一些 SCSI 命令,以及在 Linux® 中使用 SCSI API 时执行 SCSI 命令的方法。他介绍了 SCSI 客户机/服务器模型和存储 SCSI 命令的背景。接下来解释 Linux 通用 SCSI 驱动器 API,并提供一个示例,讨论使用通用驱动器执行 inquiry 命令的系统。
1
评论:
Mao
Tao (taomaoy@cn.ibm.com),
软件工程师, EMC
2009 年 3 月 23 日
内容
在 IBM Bluemix 云平台上开发并部署您的下一个应用。
开始您的试用
SCSI 客户机/服务器模型
在主机和存储介质进行通信期间,主机通常充当 SCSI 启动程序。在计算机存储中,SCSI 启动程序是启动 SCSI 会话的端点,这意味着它会发送 SCSI 命令。存储介质通常充当 SCSI 目标,它接收和处理 SCSI 命令。SCSI 目标等待启动程序的命令,然后提供请求的输入/输出数据转换。SCSI 目标通常为启动程序提供一个或多个逻辑单元号(LUN)。在计算机存储介质上,LUN 仅是分配给逻辑单元的号码。逻辑单元是一个 SCSI 协议实体,实际的 I/O 操作只处理这种实体。每个 SCSI 目标可以提供一个或多个逻辑单元;它本身不执行 I/O,但代替特定的逻辑单元执行。
在存储区域中,LUN 通常表示一个主机能够执行读写操作的 SCSI 磁盘。图 1 显示 SCSI 客户机/服务器模型是如何工作的。
图 1. SCSI 客户机/服务器模型
启动程序首先向目标发送命令,然后目标解码命令并向启动程序请求数据,或将数据发送给启动程序。在这之后,目标将状态发送给启动程序。如果状态损坏,启动程序将向目标发送一个请求检测(sense)指令。目标将返回检测数据,告知启动程序哪里出错。
现在我们研究与存储相关的 SCSI 命令。
回页首
与存储相关的 SCSI 命令
与存储相关的 SCSI 命令一般是在 SCSI Architecture Model (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 设备的配置参数的非数据命令。表 1 列出了最常使用的命令。
表 1. 最常用的 SCSI 命令
命令 | 描述 |
---|---|
Inquiry | 请求目标设备的摘要信息 |
Test/Unit/Ready | 检测目标设备是否准备好进行传输 |
READ | 从 SCSI 目标设备传输数据 |
WRITE | 向 SCSI 目标设备传输数据 |
Request Sense | 请求最后一个命令的检测数据 |
Read Capacity | 请求存储容量信息 |
现在开始探索通用 SCSI 驱动器。
回页首
Linux 通用 SCSI 驱动器
Linux 中的 SCSI 设备的命名方式能够帮助用户识别设备。例如,第一个 SCSI CD-ROM 是 /dev/scd0。SCSI 磁盘的标签为 /dev/sda、/dev/sdb 和 /dev/sdc 等。当设备初始化完成时,Linux SCSI 磁盘驱动器接口仅发送 SCSI READ和
WRITE命令。
这些 SCSI 设备可能具有通用的名称和接口,比如 /dev/sg0、/dev/sg1 或 /dev/sga、/dev/sgb 等。通过这些通用的 驱动器接口,您就可以将 SCSI 命令直接发送到 SCSI 设备,而不需要经过在 SCSI 磁盘上创建(并装载到某个目录)的文件系统。在图 2 中,您可以看到不同的应用程序如何与 SCSI 设备通信。
图 2. 与 SCSI 设备通信的各种方式
通过 Linux 通用驱动器接口,您可以构建能够向 SCSI 设备发送更多 SCSI 命令的应用程序。也就是说您又多了一种选择。要确定哪个 SCSI 设备表示某个 sg 接口,您可以使用
sg_map命令列出所有映射:
[root@taomaoy ~]# sg_map -i /dev/sg0 /dev/sda ATA ST3160812AS 3.AA /dev/sg1 /dev/scd0 HL-DT-ST RW/DVD GCC-4244N 1.02
如何使用 Red Hat 或 Fedora,则要安装
sg3_utils。现在我们看看如何执行典型的
SCSI 系统调用命令。
回页首
典型的 SCSI 通用驱动器命令
对于字符设备,SCSI 通用驱动器支持许多典型的系统调用,比如 open()、
close()、
read()、
write、
poll()和
ioctl()。向特定的
SCSI 设备发送 SCSI 命令的步骤也非常简单:
打开 SCSI 通用设备文件(比如 sg1)获取 SCSI 设备的文件描述符。
准备好 SCSI 命令。
设置相关的内存缓冲区。
调用
ioctl()函数执行
SCSI 命令。
关闭设备文件。
典型的
ioctl()函数类似于:
ioctl(fd,SG_IO,p_io_hdr);。
这里的
ioctl()函数必须具有
3 个参数:
fd是设备文件的文件描述符。通过调用
open()成功打开设备文件之后,将需要获取这个参数。
SG_IO表明将
sg_io_hdr对象作为
ioctl()函数的第三个参数提交,并且在
SCSI 命令结束时返回。
p_io_hdr是指向
sg_io_hdr对象的指针,该对象包含
SCSI 命令和其他设置。
SCSI 通用驱动器的最重要数据结构是
struct sg_io_hdr,它在 scsi/sg.h 中定义,并且包含如何使用 SCSI 命令的信息。清单 1 给出了这个结构的定义。
清单 1. sg_io_hdr 结构的定义
typedef struct sg_io_hdr { int interface_id; /* [i] 'S' (required) */ int dxfer_direction; /* [i] */ unsigned char cmd_len; /* [i] */ unsigned char mx_sb_len; /* [i] */ unsigned short iovec_count; /* [i] */ unsigned int dxfer_len; /* [i] */ void * dxferp; /* [i], [*io] */ unsigned char * cmdp; /* [i], [*i] */ unsigned char * sbp; /* [i], [*o] */ unsigned int timeout; /* [i] unit: millisecs */ unsigned int flags; /* [i] */ int pack_id; /* [i->o] */ void * usr_ptr; /* [i->o] */ unsigned char status; /* [o] */ unsigned char masked_status; /* [o] */ unsigned char msg_status; /* [o] */ unsigned char sb_len_wr; /* [o] */ unsigned short host_status; /* [o] */ unsigned short driver_status; /* [o] */ int resid; /* [o] */ unsigned int duration; /* [o] */ unsigned int info; /* [o] */ } sg_io_hdr_t; /* 64 bytes long (on i386) */
不需要用到这个结构中的所有字段,因此这?仅列出最常用的字段:
interface_id:一般应该设置为
S。
dxfer_direction:用于确定数据传输的方向;常常使用以下值之一:
SG_DXFER_NONE:不需要传输数据。比如
SCSI Test Unit Ready 命令。
SG_DXFER_TO_DEV:将数据传输到设备。使用
SCSI WRITE 命令。
SG_DXFER_FROM_DEV:从设备输出数据。使用
SCSI READ 命令。
SG_DXFER_TO_FROM_DEV:双向传输数据。
SG_DXFER_UNKNOWN:数据的传输方向未知。
cmd_len:指向
SCSI 命令的
cmdp的字节长度。
mx_sb_len:当
sense_buffer为输出时,可以写回到
sbp的最大大小。
dxfer_len:数据传输的用户内存的长度。
dxferp:指向数据传输时长度至少为
dxfer_len字节的用户内存的指针。
cmdp:指向将要执行的
SCSI 命令的指针。
sbp:缓冲检测指针。
timeout:用于使特定命令超时。
status:由
SCSI 标准定义的 SCSI 状态字节。
总而言之,当用这种方法传输数据时,
cmdp必须指向其长度存储在
cmd_len中的
SCSI CDB;
sbp指向最大长度为
mx_sb_len的用户内存。如果出现错误,将把检测数据写回到这个位置。
dxferp指向内存;数据将根据
dxfer_direction传输到
SCSI 设备或从中传输出来。
最后,我们看看 inquiry 命令,以及如何使用通用驱动器执行它。
回页首
例子:执行一个 inquiry 命令
inquiry 命令是所有 SCSI 设备实现的最常用的 SCSI 命令。这个命令用于请求 SCSI 设备的基本信息,并且常常用作 ping操作,以测试
SCSI 设备是否在线。表 2 显示如何定义 SCSI 标准。
表 2. inquiry 命令格式定义
位 7 | 位 6 | 位 5 | 位 4 | 位 3 | 位 2 | 位 1 | 位 0 | |
---|---|---|---|---|---|---|---|---|
字节 0 | Operation code = 12h | |||||||
字节 1 | LUN | Reserved | EVPD | |||||
字节 2 | Page code | |||||||
字节 3 | Reserved | |||||||
字节 4 | Allocation length | |||||||
字节 5 | Control |
清单 2 显示了使用 SCSI 通用 API 的源代码片段。我们先看看设置
sg_io_hdr的示例。
清单 2. 设置 sg_io_hdr
struct sg_io_hdr * init_io_hdr() { struct sg_io_hdr * p_scsi_hdr = (struct sg_io_hdr *)malloc(sizeof(struct sg_io_hdr)); memset(p_scsi_hdr, 0, sizeof(struct sg_io_hdr)); if (p_scsi_hdr) { p_scsi_hdr->interface_id = 'S'; /* this is the only choice we have! */ /* this would put the LUN to 2nd byte of cdb*/ p_scsi_hdr->flags = SG_FLAG_LUN_INHIBIT; } return p_scsi_hdr; } void destroy_io_hdr(struct sg_io_hdr * p_hdr) { if (p_hdr) { free(p_hdr); } } void set_xfer_data(struct sg_io_hdr * p_hdr, void * data, unsigned int length) { if (p_hdr) { p_hdr->dxferp = data; p_hdr->dxfer_len = length; } } void set_sense_data(struct sg_io_hdr * p_hdr, unsigned char * data, unsigned int length) { if (p_hdr) { p_hdr->sbp = data; p_hdr->mx_sb_len = length; } }
这些函数还用于设置
sg_io_hdr对象。其中的一些字段指向用户空间内存;当执行完毕时,来自
SCSI 命令的 inquiry 输出数据将复制到
dxferp指向的内存。如果出现错误并且需要检测数据,检测数据将复制到
sbp指向的位置。清单
3 显示了一个向 SCSI 目标发送 inquiry 命令的示例。
清单 3. 向 SCSI 目标发送 inquiry 命令
int execute_Inquiry(int fd, int page_code, int evpd, struct sg_io_hdr * p_hdr) { unsigned char cdb[6]; /* set the cdb format */ cdb[0] = 0x12; /*This is for Inquery*/ cdb[1] = evpd & 1; cdb[2] = page_code & 0xff; cdb[3] = 0; cdb[4] = 0xff; cdb[5] = 0; /*For control filed, just use 0 */ p_hdr->dxfer_direction = SG_DXFER_FROM_DEV; p_hdr->cmdp = cdb; p_hdr->cmd_len = 6; int ret = ioctl(fd, SG_IO, p_hdr); if (ret<0) { printf("Sending SCSI Command failed.\n"); close(fd); exit(1); } return p_hdr->status; }
因此,这个函数首先根据 inquiry 标准格式准备 CDB,然后调用
ioctl()函数,提交文件描述符
SG_IO和
sg_io_hdr对象;返回的状态存储在
sg_io_hdr对象的
status字段中。
现在我们看看应用程序如何使用这个函数执行 inquiry 命令,如清单 4 所示:
清单 4. 应用程序执行 inquiry 命令
unsigned char sense_buffer[SENSE_LEN]; unsigned char data_buffer[BLOCK_LEN*256]; void test_execute_Inquiry(char * path, int evpd, int page_code) { struct sg_io_hdr * p_hdr = init_io_hdr(); set_xfer_data(p_hdr, data_buffer, BLOCK_LEN*256); set_sense_data(p_hdr, sense_buffer, SENSE_LEN); int status = 0; int fd = open(path, O_RDWR); if (fd>0) { status = execute_Inquiry(fd, page_code, evpd, p_hdr); printf("the return status is %d\n", status); if (status!=0) { show_sense_buffer(p_hdr); } else{ show_vendor(p_hdr); show_product(p_hdr); show_product_rev(p_hdr); } } else { printf("failed to open sg file %s\n", path); } close(fd); destroy_io_hdr(p_hdr); }
发送 SCSI 命令的步骤非常简单。首先必须分配用户空间数据缓冲区和检测缓冲区,并将它们指向
sg_io_hdr对象。然后打开设备驱动器并获取文件描述符。有了这些参数之后,就可以将
SCSI 命令发送到目标设备。当这个命令完成时,SCSI 目标的输出将被复制到用户空间缓冲区。
清单 5. 使用参数将 SCSI 命令发送到目标设备
void show_vendor(struct sg_io_hdr * hdr) { unsigned char * buffer = hdr->dxferp; int i; printf("vendor id:"); for (i=8; i<16; ++i) { putchar(buffer[i]); } putchar('\n'); } void show_product(struct sg_io_hdr * hdr) { unsigned char * buffer = hdr->dxferp; int i; printf("product id:"); for (i=16; i<32; ++i) { putchar(buffer[i]); } putchar('\n'); } void show_product_rev(struct sg_io_hdr * hdr) { unsigned char * buffer = hdr->dxferp; int i; printf("product ver:"); for (i=32; i<36; ++i) { putchar(buffer[i]); } putchar('\n'); } int main(int argc, char * argv[]) { test_execute_Inquiry(argv[1], 0, 0); return EXIT_SUCCESS; }
SCSI Inquiry Command(Page Code 和 EVPD 字段皆设置为 0)的标准响应很复杂。根据标准,供应商 ID 从第 8 字节扩展到第 15 字节,产品 ID 从第 16 字节扩展到第 31 字节,产品版本从第 32 字节扩展到第 35 字节。必须获取这些信息,以检查命令是否成功执行。
在构建这个简单的示例之后,可以在 /dev/sg0 上运行它,这通常是本地硬盘。您将得到以下结果:
[root@taomaoy scsi_test]# ./scsi_test /dev/sg0 the return status is 0 vendor id:ATA product id:ST3160812AS product ver:3.AA
结果和
sg_map工具报告的一样。
回页首
结束语
Linux 提供一个 SCSI 设备通用驱动器和一个应用程序编程接口,您可以通过它们构建能够将 SCSI 命令直接发送到 SCSI 设备的应用程序。您可以手动发送 SCSI 命令并在 sg_io_hdr中设置其他相关参数,然后调用
ioctl()执行
SCSI 命令并从同一个
sg_io_hdr对象中获取输出。
回页首
下载
描述 | 名字 | 大小 |
---|---|---|
本文的样例代码 | scsi_test.zip | 3KB |
参考资料
学习
您可以参阅本文在 developerWorks 全球网站上的 英文原文。您想更多地了解 Linux 和 SCSI 子系统吗?请尝试阅读:
“Linux SCSI 子系统剖析”(developerWorks,2007
年 11 月)介绍了 Linux SCSI 子系统,并讨论了这个子系统的发展方向。
“Linux 同步方法剖析”(developerWorks,2007
年 10 月)概述了原子式同步操作(常用于 SCSI 驱动器)。
“GCC hacks in the Linux kernel”(developerWorks,2008
年 11 月)介绍了 GNU Compiler Collection 套件;您将从中找到用于 SCSI 交换块的范围扩展示例(清单
2)。
TC T10 SCSI Storage Interfaces 是介绍 I/O 接口的优秀资源库,尤其是 SCSI-3 和 SAS。
这里提供介绍 Linux SCSI 通用驱动器 的出色资源库,它包含版本更新、背景介绍、特性列表、设备驱动器下载、实用程序和相关的
Web 站点。
相关文章推荐
- Linux 内核 SCSI IO 子系统分析
- Linux SCSI 子系统剖析
- cenyos7安装telnet服务器
- Linux_历史
- Linux或者Windows系统中鼠标点击偶尔无反应?
- Linux 内核的同步机制
- 基于linux下的变量声明declare的用法
- 如何在CentOS6.5中进行PPPOE拨号上网
- linux下利用PPPOE实现拨号上网
- (大数据工程师学习路径)第一步 Linux 基础入门----目录结构及文件基本操作
- CentOS简介
- 虚拟机中centos7实现nat静态ip上网
- Linux系统目录树结构分析(Ubuntu 14.10 LTS)
- Linux,socket,非阻塞,fcntl
- linux CGI编程
- Linux网卡bond的七种模式详解
- (大数据工程师学习路径)第一步 Linux 基础入门----用户及文件权限管理
- RHEL二十(管理SELINUX的安全性)
- Linux服务器运行环境搭建(三)——MySQL数据库安装
- Linux服务器运行环境搭建(三)——MySQL数据库安装