关于尚观
最新课程体系
企业服务
零起点咨询
开放课程
报名咨询
免费资源申请
授权加盟
学员专区&薪资
认证课程
  • 尚观首页
  • 尚观简介
  • 师资力量
  • 尚观文化
  • 尚观动态
  • 加入尚观
  • 联系我们
  • 珍贵瞬间
  • Linux系统与集群
  • 数据库集群架构师
  • 嵌入式内核驱动开发
  • 服务器开发架构师
  • 研发型测试架构师
  • RHCE/LPI认证体系
  • Oracle OCP认证
  • 企业高级技术培训
  • 批量人才供应及研发团队建设
  • 软件外包及研发项目合作
  • 人力外包及人力供应
  • 猎头服务
  • 开启职业生涯
  • 如何入门
  • Unix/Linux职业方向
  • 嵌入式职业方向
  • 集群(Cluster)Oracle(DBA)方向
  • 新手专栏
  • Unix/Linux新手专栏
  • Unix/Linux管理
  • 嵌入式及POSIX开发
  • Linux开源文化及市场
  • 最近网络课堂
  • P2P教学视频
  • 精彩Linux培训视频
  • 零起点数据库集群(长期)
  • 嵌入式驱动(长期)
  • Linux金牌三证(RHCE级)
  • Linux架构师(RHCA级)
  • 嵌入式/驱动加速课程
  • 其他咨询
  • Linux免费网络安全公开课
  • 免费Linux资料申请
  • 免费Linux安全&DDOS防御课程
  • 免费嵌入式Linux开发课程
  • 免费Linux网络安全机制
  • 授权加盟&报名点申请
  • 如何加盟
  • 我们的优势
  • 成功案例
  • 行业市场分析
  • 授权加盟中心
  • 学员毕业薪资(不断更新中)
  • 加入尚观猎头服务(仅限老学员)
  • 学员交流论坛
  • OCP认证
  • Intel认证
  • RHCE认证
  • RHCE+OCP
  • RHCE+LPI


    您现在的位置:尚观Linux培训 - 嵌入式Linux培训专家尚观Linux培训 » UNIX/Linux新手专栏 » 探索 Linux 通用 SCSI 驱动器

探索 Linux 通用 SCSI 驱动器

 
咨询热线
  • 免费咨询电话:
    400-700-0056
    800-810-0056

    教学就业监督电话:
    400-810-3016
  • QQ在线咨询:
    517589021 在线咨询
    82555789 在线咨询
    962895738 在线咨询


  • MSN在线咨询:
    upadv05@hotmail.com
    upadv20@hotmail.com
免费资源
  • -3月13日
  • [沈阳]Linux嵌入式-3月20日
  • [上海]DDos攻防-3月20日
  • [上海]Linux嵌入式-3月13日
  • [深圳]DDos攻防-3月20日
  • [深圳]Linux嵌入式-3月13日
  • [北京]DDos攻防-3月13日
五万年薪就业保证课程
  • UTA-高级研发型测试架构师
  • UDA-Linux服务器开发架构师
  • UEA-嵌入式内核驱动开发者
  • UOA-Oracle数据库集群架构
  • ULA-Linux系统与集群架构师
八万年薪就业保证课程
  • UOM- Oracle集群大师
  • UEM-嵌入式开发大师
Linux培训●最新开班
  • [沈阳]UEA课程3月班热招!
  • [沈阳]ULA课程3月班热招!
  • [北京]UEA课程3月班热报!
  • [北京]UOA/ULA课程1月班!
  • [上海]UEA课程3月班热招!
  • [上海]ULA/UOA课程1月班!
  • [深圳]UEA课程3月班热招!
  • [深圳]ULA/UOA课程1月班!
 
文章内容
  • 通过 SCSI 命令管理计算机上的数据,并将数据传输到 SCSI 设备。在本文中,作者介绍了一些 SCSI 命令,以及在 Linux® 中使用 SCSI API 时执行 SCSI 命令的方法。他介绍了 SCSI 客户机/服务器模型和存储 SCSI 命令的背景。接下来解释 Linux 通用 SCSI 驱动器 API,并提供一个示例,讨论使用通用驱动器执行 inquiry 命令的系统。

    SCSI 客户机/服务器模型

    在主机和存储介质进行通信期间,主机通常充当 SCSI 启动程序。在计算机存储中,SCSI 启动程序是启动 SCSI 会话的端点,这意味着它会发送 SCSI 命令。存储介质通常充当 SCSI 目标,它接收和处理 SCSI 命令。SCSI 目标等待启动程序的命令,然后提供请求的输入/输出数据转换。

    SCSI 目标通常为启动程序提供一个或多个逻辑单元号(LUN)。在计算机存储介质上,LUN 仅是分配给逻辑单元的号码。逻辑单元是一个 SCSI 协议实体,实际的 I/O 操作只处理这种实体。每个 SCSI 目标可以提供一个或多个逻辑单元;它本身不执行 I/O,但代替特定的逻辑单元执行。

    在存储区域中,LUN 通常表示一个主机能够执行读写操作的 SCSI 磁盘。图 1 显示 SCSI 客户机/服务器模型是如何工作的。


    图 1. SCSI 客户机/服务器模型
    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 设备通信的各种方式
    与 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 命令的步骤也非常简单:

    1. 打开 SCSI 通用设备文件(比如 sg1)获取 SCSI 设备的文件描述符。
    2. 准备好 SCSI 命令。
    3. 设置相关的内存缓冲区。
    4. 调用 ioctl() 函数执行 SCSI 命令。
    5. 关闭设备文件。

    典型的 ioctl() 函数类似于:ioctl(fd,SG_IO,p_io_hdr);。

    这里的 ioctl() 函数必须具有 3 个参数:

    1. fd 是设备文件的文件描述符。通过调用 open() 成功打开设备文件之后,将需要获取这个参数。
    2. SG_IO 表明将 sg_io_hdr 对象作为 ioctl() 函数的第三个参数提交,并且在 SCSI 命令结束时返回。
    3. 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
    字节 0Operation code = 12h
    字节 1LUNReservedEVPD
    字节 2Page code
    字节 3Reserved
    字节 4Allocation length
    字节 5Control

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




    精彩Linux视频,免费先学!

    查看:尚观Linux培训学员最新就业薪资待遇统计报告!

    查看:UTA-高级研发型测试架构师大纲
    查看:UDA-Linux服务器开发架构师大纲
    查看:UEA-嵌入式内核驱动开发者大纲
    查看:UOA-Oracle数据库集群架构大纲
    查看:ULA-Linux系统与集群架构师大纲

    填表获取 Linux、嵌入式、Oracle 技术资料

  • 姓    名:
  • 固    话:
  • 手    机:
  • E-Mail :
  • 所在地:
  •         

北京(Beijing)

电子地图
电话:(010)62113016/17 68949060
地址:北京市 海淀区中关村南大街 甲10号 银海大厦南区410-418

上海(Shanghai)

电子地图
电话:(021)54510580 54070058
报名:徐汇区 华山路2088号 汇银广场南楼1307室
校区:漕河泾开发区 钦江路333号38号3楼

深圳(Shenzhen)

电子地图
电话:(0755)82995128 82995168
地址:深圳市 福田区 彩田路 中深花园B2005室

沈阳(Shenyang)

电子地图
电话:(024)31500816 31500817
地址:沈阳市和平区三好街54号物产科贸大厦2号楼201室

成都(Chengdu)

电子地图
电话:(028)66677820 66677828 66677829
地址:成都市青羊区上西顺城街252号顺吉大厦8楼A3


2005-2009高新技术企业

LPI授权培训考试中心

国家紧缺人才培养工程合作伙伴

CSDN战略伙伴

51Job战略伙伴

Oracle WDP授权培训与考试中心

Intel软件服务培训供应商

Redhat授权培训与考试中心

国家高新技术企业认证
 

友情链接:   LPI  RedHat  CSDN  ChinaUnix  ITPub  Eygle.Com  PHP中国  嵌入式世界  Linux培训  嵌入式培训  嵌入式在线  oklinux 
Linux联盟  Linux中国  Unix爱好者家园  ZOL服务器  php爱好者  看看Linux  嵌入式技术网  虚拟主机评测  大功率LED网   搜电网   深圳公交查询   杭州写字楼网  互联网百科  苏州人才网  Linux技术中心  泉州人才网  广州招聘网  天天健康  上海商铺  北京赶集网  SVN中文技术网  中国别墅网  网罗天下  工具软件下载  Oracle视频教程  北京法律咨询  天津赶集网  嵌入式资讯网  天极导航  Fedora中文爱好者 (申请友情链接请发信至:info#uplooking.com)


COPYRIGHT © 2000-2009 北京尚观科技有限公司 - Linux培训及嵌入式培训专家 ALL RIGHTS RESERVED

咨询热线:400-700-0056    800-810-0056
京ICP备05058027号 | Linux就业信息 | 免责声明 |