转载:https://www.ibm.com/developerworks/cn/linux/l-scsi-api/#ibm-pcon

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 命令都要以操作代码的第一个字节为开端,以表明它所代表的操作。并且所有 SCSI 命令都要包含一个控制字节。这个字节通常是该命令的最后一个字节,用于表示与供应商相关的信息等等。

现在开始探索通用 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

如果 EVPD 参数位(用于启用关键产品数据)为 0 并且 Page Code 参数字节为 0,那么目标将返回标准 inquiry 数据。如果 EVPD 参数为 1,那么目标将返回对应 page code 字段的特定于供应商的数据。

清单 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 对象中获取输出。

demo:

scsi_test.zip

linux中scsi驱动程序,探索 Linux 通用 SCSI 驱动器相关推荐

  1. linux中文件链接,关于Linux中文件,链接的一些思考

    在Unix系统中,操作系统为磁盘上的文本与图像,鼠标键盘操作,网络交互等IO操作设计了一组通用API. 使他们被处理的时候可统一用字节流的方式.所以说,除了进程之外,其他的一切均可看做文件. Linu ...

  2. linux中original_如何在 Linux 中整理磁盘碎片

    如何在 Linux 中整理磁盘碎片 转载自: 如何在 Linux 中整理磁盘碎片​linux.cn 有一个神话是 linux 的磁盘从来不需要整理碎片.在大多数情况下这是真的,大多数因为是使用的是优秀 ...

  3. 在linux中的sort命令,linux中sort命令

    功能说明:将文本文件内容加以排序,sort可针对文本文件的内容,以行为单位来排序. 参 数: -b 忽略每行前面开始出的空格字符. -c 检查文件是否已经按照顺序排序. -d 排序时,处理英文字母.数 ...

  4. linux中用户组和用户,linux中用户和用户组

    一.用户和组原理 一个用户可以属于多个组,一个组有多个用户 在Linux中操作系统必须依赖组和用户进行管理 二.与用户和组相关的配置文件 1.组相关配置文件 1)/etc/group :管理用户组信息 ...

  5. 网页修改linux命令行,linux中文本修改操作命令linux网页制作 -电脑资料

    在命令模式下可以使用 vi 提供的各种有关命令对文本进行修改,包括对文本内容的删除.复制.取代和替换等, 1. 文本删除/移动 在编辑文本时 ,经常需要删除一些不需要的文本,我们可以用键将输错或不需要 ...

  6. linux中make命令大全,Linux中的命令 make -f 是什么意思

    二.Makefile的文件名 默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"."makef ile"."Make ...

  7. linux中磁盘的iused,Linux 磁盘与文件系统管理

    前言: [1]主分区+扩展分区<=4. [2]扩展分区不能直接使用,要分成逻辑分区才能使用. [3]逻辑分区数量没有限制. [1]IDE硬盘驱动器标识符为"hd".SCSI硬 ...

  8. linux 图像采集卡驱动程序,基于Linux操作系统的视频采集卡驱动程序设计

    DMA结构: struct saa7146_video_dma { u32 base_odd; u32 base_even; u32 prot_addr; u32 pitch; u32 base_pa ...

  9. linux 科学计算器_探索Linux作为科学计算平台

    linux 科学计算器 科学界的Linux Linux在科学界中占有举足轻重的地位也就不足为奇了. 从高性能计算集群到可视化软件的解决方案比比皆是. 甚至还有一个完整的基于Red Hat Enterp ...

最新文章

  1. HashMap 散列初体验
  2. 感受学生考勤“智慧化”变革 签到荚让校园更智慧
  3. Linux下ntpdate时间同步
  4. LiveVideoStackCon 2020北京站 | 参会指南
  5. python可以写安卓应用吗_python可以编写android程序吗?
  6. 安装配置OSA运维管理平台
  7. char数组转string_String类和其它数据类型的相互转换
  8. 做网页很实用代码集合和CSS制作网页小技巧整理
  9. 这是我的第一个博客,以后遇到问题一起解决
  10. 无法从elasticsearch节点检索版本信息_【Elasticsearch 7 搜索之路】(一)什么是 Elasticsearch?...
  11. 清除定时器 和 vue 中遇到的定时器setTimeout setInterval问题
  12. linux创建自定义组件qt,QT中的元对象系统:创建自定义的QT类型
  13. centos时间同步方案
  14. ABP框架的理解和总结
  15. 分享Silverlight/WPF/Windows Phone一周学习导读(06月06日-06月11日)
  16. Onenote实用笔记
  17. 单片机系统的电磁兼容性设计
  18. 为什么 1 KB = 1024 B? 1 MB = 1024 KB?
  19. Ubuntu 16.04中安装OpenCV 2.4.11
  20. IBM小型机AIX操作系统总结02--软件安装

热门文章

  1. MySQL修改最大连接数限制
  2. 苹果在官网发布两款新品,让 Apple Pencil 成为所有 iPad 标配...
  3. Ad域控之Ldaps以及Python操作
  4. win7搭建python环境
  5. Nike的加密时尚潮牌RTFKT宣布下一个IP:Project Animus
  6. 坑爹的MySql update in subquery
  7. Windows中WSL2 配置运行GNOME桌面版 Ubuntu
  8. 本人亲测竹鼠活下去到底好不好玩?你能活几天呢?
  9. 2021-7-20-KVM虚拟化基础
  10. [PSQL] 自连接的用法