精华内容
下载资源
问答
  • 2021-05-11 00:48:21

    Linux 内核 SCSI IO 子系统分析

    概述

    LINUX 内核中 SCSI 子系统由 SCSI 上层,中间层和底层驱动模块 [1] 三部分组成,主要负责管理 SCSI 资源和处理其他子系统,如文件系统,提交到 SCSI 子系统中的 IO 请求。因此,理解 SCSI 子系统的 IO 处理机制对理解整个 SCSI 子系统就显的十分重要,同时也有助于理解整个 LINUX 内核的 IO 处理机制。本文从 SCSI 设备访问请求的提交,SCSI 子系统对访问请求的处理和 SCSI 子系统错误处理三个方面,阐述了 SCSI 子系统的 IO 处理机制。

    SCSI设备访问请求的提交

    SCSI 设备访问请求的提交分为两个步骤:用户空间提交访问请求到通用块层以及通用块层提交块访问请求到 SCSI 子系统。

    用户空间提交访问请求到通用块层

    在 LINUX 用户空间,有三种方式提交对 SCSI 设备的访问请求到通用块层:

    通过文件系统提供的文件访问接口进行访问。对建立在 SCSI 设备上的 LINUX 文件系统中的文件读写操作,就属于这种访问方式;RAW 设备访问方式。这种访问方式比较常见的应用就是dd命令。 RAW 设备访问方式和通过文件系统提供的文件访问接口进行访问的最大区别在于前者对 SCSI 设备直接进行线性地址访问,不需要由文件系统进行地址映射;SCSI PASSTHROUGH 方式。通过 LINUX 提供的 SG 进行访问,就属于这种方式,用户可以直接发 CDB[2] 命令给 SCSI 设备。所以,通过该接口,用户可以做一些 SCSI 管理操作,如 SES 管理等。

    图 1 显示了 LINUX 内核对于三种请求提交方式的处理过程。

    图 1. LINUX 内核处理三种访问请求的方式

    经由文件系统或 RAW 设备方式提交的请求,会通过底层块设备访问层(ll_rw_block()),由其生成块 IO 请求(BIO),并提交给通用块层 [3] ;而通过 SG 接口提交的访问请求,会调用 SCSI 中间层提供的接口,将请求直接交由通用块层进行处理。

    通用块层提交块访问请求到SCSI子系统

    为什么要通过通用块层呢?这是因为首先通用块层会根据磁盘访问的特性对请求进行优化操作;其次,通用块层提供了调度功能,能够对请求进行调度;再次,通用块层可扩展的结构,使各种设备的块驱动都能比较容易的和其集成。

    当请求提交到通用块层后,通用块层需要完成准备,调度并交付块访问请求给 SCSI 中间层的操作。块访问请求可以理解为描述了块访问区域,访问方式和关联的 BIO 的请求,在内核中用 'struct request'结构表示。块设备会有对应的块访问请求设备队列,用于记录需要该设备处理的访问请求,新生成的块访问请求会被加入到对应设备的块访问请求队列中。 SCSI 子系统对 IO 的处理,实际上是处理块访问请求队列上的块访问请求。

    通用块层提供了两种方式调度处理块访问请求队列:直接调度和通过 LINUX 内核工作队列机制调度执行。两种方式,最后都会调用块访问请求队列处理函数进行处理,而 SCSI 设备在初始化时会向通用块层注册 SCSI 子系统定义的块访问请求队列处理函数。清单 1[4] 显示了这个过程。这样当通用块层处理 SCSI 设备的块访问请求队列时,调用的就是 SCSI 中间层定义的这些处理函数。通过这种方式,通用块层就将块访问请求的处理交给了 SCSI 子系统。

    清单 1. 处理函数

    struct request_queue *scsi_alloc_queue(struct scsi_device *sdev)

    {……

    q = blk_init_queue(scsi_request_fn, NULL);

    //request generate block layer allocate a request queue

    ……

    blk_queue_prep_rq(q, scsi_prep_fn); //Prepare a scsi request blk_queue_max_hw_segments(q, shost->sg_tablesize);

    //define sg table size

    ……

    blk_queue_softirq_done(q, scsi_softirq_done);

    }

    SCSI子系统处理块访问请求

    当 SCSI 子系统的请求队列处理函数被通用块层调用后,SCSI 中间层会根据块访问请求的内容,生成、初始并提交 SCSI 命令 (struct scsi_cmd) 到 SCSI TARGET 端。

    SCSI命令初始化和提交

    SCSI 命令记录了命令描述块 (CDB),感测数据缓存 (SENSE BUFFER),IO 超时时间等 SCSI 相关的信息和 SCSI 子系统处理命令需要的一些其他信息,如回调函数等。清单 2 显示了这个命令的主要结构。

    清单 2. 主要结构

    struct scsi_cmnd {

    ……

    void (*done) (struct scsi_cmnd *); /* Mid-level done function */

    ……

    int retries; /*retried time*/

    int timeout_per_command; /*timeout define*/

    ……

    enum dma_data_direction sc_data_direction; /*data transfer direction*/

    ……

    unsigned char cmnd[MAX_COMMAND_SIZE]; /*cdb*/

    void *request_buffer; /* Actual requested buffer */

    struct request *request; /* The command we are working on */

    ……

    unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE];

    /* obtained by REQUEST SENSE when

    * CHECK CONDITION is received on original

    * command (auto-sense) */

    /* Low-level done function - can be used by */

    /*low-level driver to point  to completion function. */

    void (*scsi_done) (struct scsi_cmnd *);

    ……

    };

    初始化的过程首先按照电梯调度算法,从块设备的请求队列上取出一个块访问请求,根据块访问请求的信息,定义 SCSI 命令中数据传输的方向,长度和地址。其次,定义 CDB,SCSI 中间层的回调函数等。

    在完成初始化后,SCSI 中间层通过调用scsi_host_template[5]结构中定义的queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动部分。queuecommand函数,是一个 SCSI 命令队列处理函数,在 SCSI 底层驱动中,定义了queuecommand函数的具体实现。因此,SCSI 中间层,调用queuecommand函数实际上就是调用了底层驱动定义的queuecommand函数的处理实体,将 SCSI 命令提交给了各个厂家定义的 SCSI 底层驱动进行处理。这个过程和通用块设备层调用 SCSI 中间层的处理函数进行块请求处理的机制很相似,这也体现了 LINUX 内核代码具有很好的扩展性。底层驱动接受到请求后,就要开始处理 SCSI 命令了,这一层和硬件关系紧密,所以这块代码一般都是由各个厂家自己实现。基本流程可概括为:从底层驱动维护的队列中,取出一个 SCSI 命令,封装成厂家自定义的请求格式,然后采用 DMA 或者其他方式,将请求提交给 SCSI TARGET 端,由 SCSI TARGET 端对请求处理,并返回执行结果给 SCSI 底层驱动层。

    SCSI命令执行结果的处理

    当 SCSI 底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,SCSI 子系统主要通过两次回调过程完成对命令执行结果的处理。 SCSI 底层驱动在接受到 SCSI TARGET 端返回的命令执行结果后,会调用 SCSI 中间层定义的回调函数,将处理结果交付给 SCSI 中间层进行处理,这是第一次回调过程。 SCSI 中间层处理完成后,将调用 SCSI 上层定义的回调函数,结束 IO 在整个 SCSI 子系统中的处理,这为第二次回调过程。

    第一次回调:

    SCSI 中间层在调用queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动的同时,也将回调函数指针传给了 SCSI 底层驱动。底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,会调用该回调函数,产生一个中断号为 BLOCK_SOFTIRQ 的软中断进行第一次回调处理。在这次回调处理过程中,SCSI 中间层首先会根据 SCSI 底层驱动处理的结果判断请求处理是否成功。处理成功,并不意味着处理没有错误,而是返回的信息,能够让 SCSI 中间层很明确的知道,对于这个命令,中间层已经没有必要继续进行处理了。所以,对于处理成功的 SCSI 命令,SCSI 中间层会调用第二次回调函数进入到第二次回调过程。清单 3 显示了 SCSI 中间层定义的该软中断的处理函数。

    清单 3. 该软中断的处理函数

    static void scsi_softirq_done(struct request *rq)

    {

    ……

    disposition = scsi_decide_disposition(cmd);

    ……

    switch (disposition) {

    case SUCCESS:

    scsi_finish_command(cmd);

    //enter to second callback process

    break;

    case NEEDS_RETRY:

    scsi_retry_command(cmd);

    break;

    case ADD_TO_MLQUEUE:

    scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY);

    break;

    default:

    if (!scsi_eh_scmd_add(cmd, 0))

    scsi_finish_command(cmd);

    }

    }相关阅读:

    正版Win7错误代码:0XC004F061解决方案

    js location.replace与location.reload的区别

    Vista中加密文件或文件夹

    Asp中通过简单的例子理解下ByVal和ByRef的用法

    DZ X2.0 教你快速了解Discuz!程序文件功能,修改文件不求人

    rephactor 优秀的PHP的重构工具

    jQuery控制图片的hover效果(smartRollover.js)

    JavaScript入门教程(10) 认识其他对象

    php中获取指定IP的物理地址的代码(正则表达式)

    php download.php实现代码 跳转到下载文件(response.redirect)

    Linux网络连接分析命令

    一些Asp技巧和实用解决方法

    关于Javascript 的 prototype问题。

    Oracle数据库ODU的几种恢复场景

    更多相关内容
  • scsi系统

    目录

    深入浅出SCSI子系统(一)Linux 内核中的 SCSI 架构

    1.Linux 内核中的 SCSI 架构描述

    2.SCSI 较高层

    3.SCSI 中间层

    4.SCSI 较低层


    深入浅出SCSI子系统(一)Linux 内核中的 SCSI 架构

    1.Linux 内核中的 SCSI 架构描述

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

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

     在 ./linux/drivers/scsi 可以找到 SCSI 子系统(SCSI 较高层、中间层和各种驱动器)的源代码。SCSI 数据结构则位于 SCSI 源目录,在 ./linux/include/scsi 也可以找到。比如sd.c sg.c等文件。

    Linux SCSI模型基于传统的并行SCSI。主机适配器连接主机I/O总线(通常是PCI总线)和存储I/O总线(这里是SCSI总线)。一台计算机可以有多个主机适配器,而主机适配器可以控制一个(即所谓的单通道适配器)或多个(即所谓的多通道适配器)SCSI总线,一条总线可以有多个目标节点与之相连,并且一个目标节点可以有多个逻辑单元。

     在Linux语义中,用SCSI设备描述符表示具体的逻辑单元,这和我们通常谈到的SCSI设备(例如SCSI磁盘)是不一样的。就SCSI磁盘整体而言,它应该是目标节点。在SCSI磁盘内可以有多个逻辑单元,统一由磁盘控制器控制,这些逻辑单元才是真正作为I/O终点的存储设备。但是,为简单起见,现在大多数厂商生产的磁盘只做了基本实现,即实现了LUN0。

     

    在Linux中,使用四元组<host:channel:id:lun>来唯一定位一个SCSI设备。

    其中,host为主机适配器在计算机内部的编号,一般的实现方式是,在系统中定义一个全局变量,每发现一个主机适配器,将这个全局变量值赋值给host编号,同时递增该全局变量;channel则为SCSI通道编号,或者为SCSI总线编号,这是相对主机适配器来说的,应该由主机适配器的固件来维护;id为目标节点标识符;lun为在目标节点内的逻辑单元编号。

    对于并行SCSI总线,其所能挂接的设备数目是受物理层,即总线宽度,限制的。

    SCSI子系统(包括高层、中间层和低层)的源代码位于目录drivers/scsi/下,头文件在include/scsi/目录下。SCSI子系统的主要功能是:

    1.探测SCSI设备,在内存建立可供设备驱动使用的核心结构;

    2.在sysfs文件系统中构造SCSI子系统的目录树;

    3.SCSI高层驱动绑定SCSI设备,在内存中构建对应的核心结构;

    4.提供错误恢复API,在SCSI命令错误和超时后被调用。

    2.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_probe 和 sd_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 命令和解析这些命令的响应。

    3.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 内实现。

    4.SCSI 较低层

    在最低层的是一组驱动器,称为 SCSI 低层驱动器。它们是一些可与物理设备(比如 HBA)链接的特定驱动器。LLD 提供了自公共中间层到特定于设备的 HBA 的一种抽象。每个 LLD 都提供了到特定底层硬件的接口,但所使用的到中间层的接口却是一组标准接口。

    较低层包含大量代码,原因是它要负责处理各种不同的 SCSI 适配器类型。例如,Fibre Channel 协议包含了针对 Emulex 和 QLogic 的各种适配器的 LLD。面向 Adaptec 和 LSI 的 SAS 适配器的 LLD 也包括在内。

    展开全文
  • 对计算机硬件有一定了解之后,对理解Linux内核中的设备和驱动模型非常有帮助。如图1是常规计算机的硬件架构简图。图1 计算机硬件架构简图这里面需要重点理解的概念包括:总线、PCI桥和设备三个概念。我们下面大概...

    efd42145474adb5faf2cf13db2b5dadf.png

    关于硬件架构

    想要了解Linux操作系统的内核设备和驱动模型,***先了解一下现在计算机硬件的架构。对计算机硬件有一定了解之后,对理解Linux内核中的设备和驱动模型非常有帮助。如图1是常规计算机的硬件架构简图。

    cfa09435d6e2e2c8231d52e7b8584839.png

    图1 计算机硬件架构简图

    这里面需要重点理解的概念包括:总线、PCI桥和设备三个概念。我们下面大概介绍一下这几个概念的含义:

    总线: 我们知道计算机通常包括几大件,CPU、内存、输入设备和输出设备等。这些设备之间进行通信需要依赖一种通道,这个通道就是总线。说的直白写,总线就是传输数据的通道,可以类比日常生活中的马路,各个不同的城市通过马路来交换物资。总线有很多种,比如常见的PCI总线,ISA总线和I2C总线等等,我们这里就不相信介绍。

    PCI桥: PCI桥是连接PCI总线的纽带,其作用与网络领域的网桥类似。其实我们平时说的北桥,就包含PCI桥。PCI桥主要分3种,3种桥的具体含义如下:

    HOST/PCI桥:提供CPU和PCI设备相互访问的通道,实现CPU空间和PCI空间的映射。

    PCI-PCI桥:实现PCI设备的级联。

    PCI/ISA或LPC桥:实现对ISA设备的兼容。

    设备:设备就是具体的设备了,比如网卡、键盘和鼠标等等。

    Linux中的设备软件模型

    为了降低设备多样性带来的Linux驱动开发的复杂度,以及设备热拔插处理、电源管理等,Linux内核提出了设备模型(也称作Driver Model)的概念。设备模型将硬件设备归纳、分类,然后抽象出一套标准的数据结构和接口。驱动的开发,就简化为对内核所规定的数据结构的填充和实现。Linux中的软件概念与实际物理的概念有一个大致的对应关系,在内核中相关的概念主要包括Bus、Device、Device Driver和Class等。下面是Linux对上述概念的介绍:

    Bus(总线):Linux认为(可以参考include/linux/device.h中struct bus_type的注释)总线是CPU和一个或多个设备之间信息交互的通道。而为了方便设备模型的抽象,所有的设备都应连接到总线上。Linux总线是在上述物理总线基础上做的抽象,它可以对应物理总线,也可以没有对应物理总线。

    Device(设备):抽象系统中所有的硬件设备,描述它的名字、属性、从属的Bus、从属的Class等信息。

    Device Driver(驱动):Linux设备模型用Driver抽象硬件设备的驱动程序,它包含设备初始化、电源管理相关的接口实现。而Linux内核中的驱动开发,基本都围绕该抽象进行(实现所规定的接口函数)。

    Class(分类):在Linux设备模型中,Class的概念非常类似面向对象程序设计中的Class(类),它主要是集合具有相似功能或属性的设备,这样就可以抽象出一套可以在多个设备之间共用的数据结构和接口函数。因而从属于相同Class的设备的驱动程序,就不再需要重复定义这些公共资源,直接从Class中继承即可。

    设备模型的核心思想

    前面介绍了Linux的设备软件模型相关的概念,下面介绍一下各种概念间的关系。对于Linux来说,其软件层面的模型与硬件基本是一致的。由图1, 如果把CPU和内存开成一个树根的话,整个计算机的设备间的关系其实类似一个树,总线类似于树枝。Linux内核在具体实现的时候也是按照此规律进行的,***层的是根总线(bus),然后是各种具体类型的总线(bus_type),而其下则是设备(device)。

    d66a0215214d0e8da13bb2f1cb2d3729.png

    图2 Linux内核驱动关键数据结构

    如图2所示,Linux内核针对上面介绍的概念,实现了具体的数据结构。数据结构的名称基本与硬件类型名称一致。比如bus_type表示某种类型的总线,device表示一个物理设备等。

    设备和驱动: 用Device(struct device)和Device Driver(struct device_driver)两个数据结构,分别从“有什么用”和“怎么用”两个角度描述硬件设备。这样就统一了编写设备驱动的格式,使驱动开发从论述题变为填空体,从而简化了设备驱动的开发。

    总线与设备: 通过"Bus-->Device”类型的树状结构解决设备之间的依赖,而这种依赖在开关机、电源管理等过程中尤为重要。

    试想,一个设备挂载在一条总线上,要启动这个设备,必须先启动它所挂载的总线。很显然,如果系统中设备非常多、依赖关系非常复杂的时候,无论是内核还是驱动的开发人员,都无力维护这种关系。

    而设备模型中的这种树状结构,可以自动处理这种依赖关系。启动某一个设备前,内核会检查该设备是否依赖其它设备或者总线,如果依赖,则检查所依赖的对象是否已经启动,如果没有,则会先启动它们,直到启动该设备的条件具备为止。而驱动开发人员需要做的,就是在编写设备驱动时,告知内核该设备的依赖关系即可。

    类: 使用Class结构,在设备模型中引入面向对象的概念,这样可以***限度地抽象共性,减少驱动开发过程中的重复劳动,降低工作量。在Linux内核驱动中,类是对具有共性的设备的抽象,比如显示设备类,音频设备类和SCSI设备类等等。比如SCSI设备类包括磁盘设备、光驱设备和USB设备等。

    即插即用: 在现代操作系统中即插即用成为常态,我们普通PC的U盘、光驱等都是即插即用的。而对于企业级的服务器甚至要求CPU和内存等组件都是可以即插即用的。

    即插即用的实现同样借用Device和Device Driver两个数据结构。在Linux内核中,只要任何Device和Device Driver具有相同的名字,内核就会执行Device Driver结构中的初始化函数(probe),该函数会初始化设备,使其为可用状态。

    而对大多数热拔插设备而言,它们的Device Driver一直存在内核中。当设备没有插入时,其Device结构不存在,因而其Driver也就不执行初始化操作。当设备插入时,内核会创建一个Device结构(名称和Driver相同),此时就会触发Driver的执行。这就是即插即用的概念。

    SCSI设备示例

    SCSI设备是Linux内核中支持的众多设备中的一种。SCSI设备也遵循上面介绍的设备、驱动和总线的结构,但略有不同。Linux内核中抽象了一个称谓SCSI总线的虚拟总线。而在SCSI总线上又包含SCSI的驱动和设备。

    b9cd52ced12a3143f08f13a26fb141c5.png

    图3 SCSI体系结构

    SCSI整个架构分为3层,其中中间是中间层,用于实现SCSI的公共功能,比如错误处理等。而上面一层称谓高层,它代表各种scsi设备类型的驱动,如scsi磁盘驱动,scsi磁带驱动,高层驱动认领低层驱动发现的scsi设备,为这些设备分配名称,将对设备的IO转换为scsi命令,交由低层驱动处理。而最下面的称谓底层,它代表与SCSI的物理接口的实际驱动器,主要为各个厂商为其特定的主机适配器(Host Bus Adapter, HBA)驱动,例如: FC卡驱动、SAS卡驱动和iSCSI(iSCSI可以使硬件HBA卡或者基于普通网卡的软件实现)等。

    在图3中,Disk Driver就是一个SCSI磁盘驱动,通过该驱动对用户呈现一个普通的磁盘。中间层的驱动是必须***个被内核加载的,如果编译成内核模块的话,该内核模块为scsi_mod。然后是上层的驱动和底层的驱动。以SCSI磁盘为例,加载的模块是sd_mod。

    在SCSI中实现对应上述概念的结构体包括scsi_driver、scsi_device和SCSI类型的总线(bus)。其中SCSI类型的总线并没有定义一个特别的数据结构体,而是对bus_type数据结构的实例化。

    需要说明的是对于SCSI设备,其实现又是比较复杂的。我们以光纤适配卡为例,其中一个适配卡又包含多个通路,而每个通路同网络的方式可以跟多个存储设备连接。因此,对于SCSI设备来说,实现上要复杂很多。

    4be070f3aca52143f5d7ed612cb2a58f.png

    图4 光纤适配卡

    在内核中通过Scsi_Host、scsi_target等结构体表示上述概念。具体细节本文不再详述,后面我们再详细介绍SCSI体系架构、FC相关流程和iSCSI相关流程。

    【编辑推荐】

    【责任编辑:武晓燕 TEL:(010)68476606】

    点赞 0

    展开全文
  • 本文基于 LINUX2.6.18 内核,从 SCSI 设备访问请求的提交,SCSI 命令的处理、错误恢复几个方面浅析了 LINUX 内核中 SCSI 子系统的 IO 的处理机制。 概述 LINUX 内核中 SCSI

    Linux 内核 SCSI IO 子系统分析

    研究 LINUX 内核中 SCSI 子系统处理 IO 的过程

    本文基于 LINUX2.6.18 内核,从 SCSI 设备访问请求的提交,SCSI 命令的处理、错误恢复几个方面浅析了 LINUX 内核中 SCSI 子系统的 IO 的处理机制。

    概述

    LINUX 内核中 SCSI 子系统由 SCSI 上层,中间层和底层驱动模块 [1] 三部分组成,主要负责管理 SCSI 资源和处理其他子系统,如文件系统,提交到 SCSI 子系统中的 IO 请求。因此,理解 SCSI 子系统的 IO 处理机制对理解整个 SCSI 子系统就显的十分重要,同时也有助于理解整个 LINUX 内核的 IO 处理机制。本文从 SCSI 设备访问请求的提交,SCSI 子系统对访问请求的处理和 SCSI 子系统错误处理三个方面,阐述了 SCSI 子系统的 IO 处理机制。

    SCSI 设备访问请求的提交

    SCSI 设备访问请求的提交分为两个步骤:用户空间提交访问请求到通用块层以及通用块层提交块访问请求到 SCSI 子系统。

    用户空间提交访问请求到通用块层

    在 LINUX 用户空间,有三种方式提交对 SCSI 设备的访问请求到通用块层:

    • 通过文件系统提供的文件访问接口进行访问。对建立在 SCSI 设备上的 LINUX 文件系统中的文件读写操作,就属于这种访问方式;
    • RAW 设备访问方式。这种访问方式比较常见的应用就是dd命令。 RAW 设备访问方式和通过文件系统提供的文件访问接口进行访问的最大区别在于前者对 SCSI 设备直接进行线性地址访问,不需要由文件系统进行地址映射;
    • SCSI PASSTHROUGH 方式。通过 LINUX 提供的 SG 进行访问,就属于这种方式,用户可以直接发 CDB[2] 命令给 SCSI 设备。所以,通过该接口,用户可以做一些 SCSI 管理操作,如 SES 管理等。

    图 1 显示了 LINUX 内核对于三种请求提交方式的处理过程。

    图 1. LINUX 内核处理三种访问请求的方式
    LINUX 内核处理三种访问请求的方式

    经由文件系统或 RAW 设备方式提交的请求,会通过底层块设备访问层(ll_rw_block()),由其生成块 IO 请求(BIO),并提交给通用块层 [3] ;而通过 SG 接口提交的访问请求,会调用 SCSI 中间层提供的接口,将请求直接交由通用块层进行处理。

    通用块层提交块访问请求到 SCSI 子系统

    为什么要通过通用块层呢?这是因为首先通用块层会根据磁盘访问的特性对请求进行优化操作;其次,通用块层提供了调度功能,能够对请求进行调度;再次,通用块层可扩展的结构,使各种设备的块驱动都能比较容易的和其集成。

    当请求提交到通用块层后,通用块层需要完成准备,调度并交付块访问请求给 SCSI 中间层的操作。块访问请求可以理解为描述了块访问区域,访问方式和关联的 BIO 的请求,在内核中用 'struct request'结构表示。块设备会有对应的块访问请求设备队列,用于记录需要该设备处理的访问请求,新生成的块访问请求会被加入到对应设备的块访问请求队列中。 SCSI 子系统对 IO 的处理,实际上是处理块访问请求队列上的块访问请求。

    通用块层提供了两种方式调度处理块访问请求队列:直接调度和通过 LINUX 内核工作队列机制调度执行。两种方式,最后都会调用块访问请求队列处理函数进行处理,而 SCSI 设备在初始化时会向通用块层注册 SCSI 子系统定义的块访问请求队列处理函数。清单 1[4] 显示了这个过程。这样当通用块层处理 SCSI 设备的块访问请求队列时,调用的就是 SCSI 中间层定义的这些处理函数。通过这种方式,通用块层就将块访问请求的处理交给了 SCSI 子系统。

    清单 1. 处理函数
    struct request_queue *scsi_alloc_queue(struct scsi_device *sdev) 
     {   ……
        q = blk_init_queue(scsi_request_fn, NULL);
         //request generate block layer allocate a request queue 
        ……
        blk_queue_prep_rq(q, scsi_prep_fn); //Prepare a scsi request 
        blk_queue_max_hw_segments(q, shost->sg_tablesize); 
        //define sg table size 
        ……
        blk_queue_softirq_done(q, scsi_softirq_done); 
     }

    SCSI 子系统处理块访问请求

    当 SCSI 子系统的请求队列处理函数被通用块层调用后,SCSI 中间层会根据块访问请求的内容,生成、初始并提交 SCSI 命令 (struct scsi_cmd) 到 SCSI TARGET 端。

    SCSI 命令初始化和提交

    SCSI 命令记录了命令描述块 (CDB),感测数据缓存 (SENSE BUFFER),IO 超时时间等 SCSI 相关的信息和 SCSI 子系统处理命令需要的一些其他信息,如回调函数等。清单 2 显示了这个命令的主要结构。

    清单 2. 主要结构
    struct scsi_cmnd { 
        ……
        void (*done) (struct scsi_cmnd *); 	 /* Mid-level done function */ 
        ……
        int retries;                /*retried time*/ 
        int timeout_per_command;   /*timeout define*/ 
        ……
        enum dma_data_direction sc_data_direction;  /*data transfer direction*/ 
        ……
        unsigned char cmnd[MAX_COMMAND_SIZE];   /*cdb*/ 
        void *request_buffer; 		 /* Actual requested buffer */ 
        struct request *request; 	 /* The command we are working on */ 
        ……
        unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE]; 
                                   /* obtained by REQUEST SENSE when 
                                   * CHECK CONDITION is received on original 
                                   * command (auto-sense) */ 
        /* Low-level done function - can be used by */
        /*low-level driver to point  to completion function. */ 
        void (*scsi_done) (struct scsi_cmnd *); 
        ……
     };

    初始化的过程首先按照电梯调度算法,从块设备的请求队列上取出一个块访问请求,根据块访问请求的信息,定义 SCSI 命令中数据传输的方向,长度和地址。其次,定义 CDB,SCSI 中间层的回调函数等。

    在完成初始化后,SCSI 中间层通过调用scsi_host_template[5]结构中定义queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动部分。queuecommand函数,是一个 SCSI 命令队列处理函数,在 SCSI 底层驱动中,定义了queuecommand函数的具体实现。因此,SCSI 中间层,调用queuecommand函数实际上就是调用了底层驱动定义的queuecommand函数的处理实体,将 SCSI 命令提交给了各个厂家定义的 SCSI 底层驱动进行处理。这个过程和通用块设备层调用 SCSI 中间层的处理函数进行块请求处理的机制很相似,这也体现了 LINUX 内核代码具有很好的扩展性。底层驱动接受到请求后,就要开始处理 SCSI 命令了,这一层和硬件关系紧密,所以这块代码一般都是由各个厂家自己实现。基本流程可概括为:从底层驱动维护的队列中,取出一个 SCSI 命令,封装成厂家自定义的请求格式,然后采用 DMA 或者其他方式,将请求提交给 SCSI TARGET 端,由 SCSI TARGET 端对请求处理,并返回执行结果给 SCSI 底层驱动层。

    SCSI 命令执行结果的处理

    当 SCSI 底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,SCSI 子系统主要通过两次回调过程完成对命令执行结果的处理。 SCSI 底层驱动在接受到 SCSI TARGET 端返回的命令执行结果后,会调用 SCSI 中间层定义的回调函数,将处理结果交付给 SCSI 中间层进行处理,这是第一次回调过程。 SCSI 中间层处理完成后,将调用 SCSI 上层定义的回调函数,结束 IO 在整个 SCSI 子系统中的处理,这为第二次回调过程。

    第一次回调:

    SCSI 中间层在调用queuecommand函数将 SCSI 命令提交给 SCSI 底层驱动的同时,也将回调函数指针传给了 SCSI 底层驱动。底层驱动接受到 SCSI TARGET 端返回的命令执行结果后,会调用该回调函数,产生一个中断号为 BLOCK_SOFTIRQ 的软中断进行第一次回调处理。在这次回调处理过程中,SCSI 中间层首先会根据 SCSI 底层驱动处理的结果判断请求处理是否成功。处理成功,并不意味着处理没有错误,而是返回的信息,能够让 SCSI 中间层很明确的知道,对于这个命令,中间层已经没有必要继续进行处理了。所以,对于处理成功的 SCSI 命令,SCSI 中间层会调用第二次回调函数进入到第二次回调过程。清单 3 显示了 SCSI 中间层定义的该软中断的处理函数。

    清单 3. 该软中断的处理函数
    static void scsi_softirq_done(struct request *rq) 
     { 
        ……
        disposition = scsi_decide_disposition(cmd); 
        ……
        switch (disposition) { 
          case SUCCESS: 
            scsi_finish_command(cmd);   
            //enter to second callback process 
            break; 
          case NEEDS_RETRY: 
            scsi_retry_command(cmd); 
            break; 
          case ADD_TO_MLQUEUE: 
            scsi_queue_insert(cmd, SCSI_MLQUEUE_DEVICE_BUSY); 
             break; 
           default: 
             if (!scsi_eh_scmd_add(cmd, 0)) 
                scsi_finish_command(cmd); 
        } 
     }

    第二次回调:

    不同的 SCSI 上层模块会定义自己不同的第二次回调函数,如 SD 模块,会在sd_init_command函数中,定义自己的第二次回调函数sd_rw_intr,这个回调函数会根据 SD 模块的需要,对 SCSI 命令执行的结果做进一步的处理。清单 4 显示了 SD 模块注册第二次回调的代码。虽然各个 SCSI 上层模块可以定义自己的第二次回调函数,但是这些回调函数最终都会结束 SCSI 子系统对这个块访问请求的处理。

    清单 4. SD 模块注册第二次回调的代码
    static int sd_init_command(struct scsi_cmnd * SCpnt) 
     { 
        ……
        SCpnt->done = sd_rw_intr; 
        return 1; 
     }

    SCSI 子系统的错误处理

    由于 SCSI 底层驱动是由厂商自己实现的,在此就不予讨论。除此之外,SCSI 子系统的出错处理,主要是由 SCSI 中间层完成。在第一次回调过程中,SCSI 底层驱动将 SCSI 命令的处理结果以及获取的 SCSI 状态信息返回给 SCSI 中间层,SCSI 中间层先对 SCSI 底层驱动返回的 SCSI 命令执行的结果进行判断,若无法得到明确的结论,则对 SCSI 底层驱动返回的 SCSI 状态、感测数据等进行判断。对于判断结论为处理成功的 SCSI 命令,SCSI 中间层会直接进行第二次回调;对于判断结论为需要重试的命令,则会被加入块设备请求对列,重新被处理。这个过程可称为 SCSI 中间层对 SCSI 命令执行结果的基本判断方法。

    一切看起来似乎是这么简单,但是实际上并非如此,有些错误是没有明确的判断依据的,如感测数据错误或 TIMEOUT 错误。为了解决这个问题,LINUX 内核中 SCSI 子系统引入了一个专门进行错误处理的线程,对于无法判断错误原因的 SCSI 命令,都会交由该线程进行处理。线程处理过程和两个队列密切相关,一个是错误处理队列(eh_work_q),一个是错误处理完成队列 (done_q) 。错误处理队列记录了需要进行错误处理的 SCSI 命令,错误处理完成队列记录了在错误处理过程中被处理完成的 SCSI 命令。清单 5 显示了线程对错误处理队列上记录的命令进行错误处理的过程。

    清单 5. 错误处理的过程
    scsi_unjam_host{ 
        ……
        if (!scsi_eh_get_sense(&eh_work_q, &eh_done_q))  
         //get sense data 
            if (!scsi_eh_abort_cmds(&eh_work_q, &eh_done_q))   
            //abort command 
    	    scsi_eh_ready_devs(shost, &eh_work_q, &eh_done_q);   
    	    //reset 
        scsi_eh_flush_done_q(&eh_done_q);   
        //complete error io on done_q 
        ……
     }

    整个处理过程可归纳为四个阶段:

    • 感测数据查询阶段

      通过查询感测数据,为处理 SCSI 命令重新提供判断依据,并按照前述基本判断方法进行判断。如果判断结果为成功或者重试,则可将该命令从错误处理队列移到错误处理完成队列。若判断失败,则命令将会继续保留在 SCSI 错误处理队列中,错误处理进入到 ABORT 阶段。

    • ABORT阶段

      在这个阶段中,错误处理队列上的 SCSI 命令会被主动 ABORT 掉。被 ABORT 的命令,会被加入到错误处理完成队列。若 ABORT 过程结束,错误处理队列上还存在未能被处理的命令,则需进入 START STOP UNIT 阶段进行处理。

    • START STOP UNIT阶段

      在这个阶段,START STOP UNIT[6] 命令会被发送到与错误处理队列上的命令相关的 SCSI DEVICE 上,去试图恢复 SCSI DEVICE,如果在 START STOP UNIT 阶段结束后,依旧有命令在错误处理队列上,则需要进入 RESET 阶段进行处理。

    • RESET阶段

      RESET 阶段的处理过程分三个层次:DEVICE RESET,BUS RESET 和 HOST RESET 。首先对与错误队列上的命令相关的 SCSI DEVICE,进行 RESET 操作,如果 DEVICE RESET 后,SCSI 设备能处于正常状态,则和该设备相关的错误处理队列上的错误命令,会被加入到错误处理完成队列中。若通过 DEVICE RESET 不能处理所有的错误命令,则需进入到 BUS RESET 阶段,BUS RESET 会对与错误处理队列上的命令相关的 BUS,进行 RESET 操作。若 BUS RESET 还不能成功处理所有错误处理队列上的 SCSI 命令,则会进入到 HOST RESET 阶段,HOST RESET 会对与错误处理队列上的命令相关的 HOST 进行 RESET 操作。当然,很有可能 HOST RESET 也不能成功处理所有错误命令,则只能认为错误处理队列上错误命令相关的 SCSI 设备不能被使用了。这些不能被使用的设备会被标记为不能使用状态,同时相关的错误命令都会被加入到错误处理完成队列中。

    对于被加入到错误处理完成队列上的请求,若是在设备状态正确,命令重试次数小于允许次数的情况下,这些命令将被重新加入到块访问请求队列中,进行重新处理;否则,直接进行第二次回调处理,完成 SCSI 子系统对块访问请求的处理。这样,SCSI 子系统就完成了 SCSI 命令错误处理的整个过程。

    结束语

    本文浅析了 SCSI 子系统中的 IO 处理机制,希望对大家理解 SCSI 子系统和块设备驱动能有所帮助。

    展开全文
  • Drivers:这个目录是内核中最庞大的一个目录,显卡、网卡、SCSI适配器、PCI总线、USB总线和其他任何Linux支持的外围设备或总线的驱动程序都可以在这里找到,驱动一般分成字符设备和块设备,但是现在内核把驱动做成了...
  • Linux内核主要由五个子系统组成: 进程调度内存管理虚拟文件系统网络接口进程间通信 (1)进程调度(SCHED) 控制系统多个进程对CPU的访问。当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可...
  • 文章目录内核源码结构Linux Makfile分析决定编译那些文件obj-y用来定义那些文件被编进(built-in)内核。例1obj-m用来定义那些文件被编译成可加载模块(Loadable module)例2例3lib-y用来定义那些文件被编成库文件...
  • Linux SCSI子系统实现了各种类型的高层驱动,如SCSI磁盘驱动、SCSI磁带驱动和SCSI CDROM驱动等。 SCSI总线类型scsi_bus_type SCSI设备和SCSI驱动
  • LinuxSCSI模型及iSCSI示例

    千次阅读 2019-04-10 20:31:52
    在Linux内核中SCSI驱动应该是最为复杂的驱动,没有之一。因为对于整个SCSI系统来说,不只包含一种类型的设备,而是一类设备。前面文章我们简单介绍了Linux操作系统内核中SCSI子系统的整体架构,我们这里简单回忆一下...
  • SCSI 协议命令字 中文含义

    千次阅读 2020-07-09 08:44:41
    Scsi架构主机上的scsi接口卡称为initiator,与其相连接的scsi 磁盘等设备称为target,在逻辑上,initiator和target之间通信的工作模式,与两个网络设备之间的模式相似,他们之间采用client-server的“请求-响应”...
  • 目录 SCSI子系统对象 1 scsi_host_template:SCSI主机适配器模板 2 Scsi_Host:SCSI主机...Scsi_Host、scsi_target和scsi_device分别描述的是Linux SCSI模型的主机适配器、目标节点和逻辑单元,而scsi_host_templa
  • linux内核源代码位于/usr/src/linux/目录下include/ 子目录包含了建立...arch/ 子目录包含了所有硬件结构特定的内核代码drivers/ 目录包含了内核中所有设备驱动程序,如块设备fs/ 目录包含所有文件系统的代码,如ext...
  • Linux2.6.34源码目录结构 1、 arch 体系结构相关的代码,例如arm, x86等等。 arch/mach- ---- 具体的machine/board相关的代码。...该目录每个子目录都与某种体系结构对应,用于存放系统结构相关代码,向平台无...
  • 1. /proc目录Linux 内核提供了一种通过 /proc 文件系统,在运行时访问内核内部数据结构、改变内核设置的机制。proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统...
  •  每个驱动对象代表一个已加载的内核驱动程序,指向驱动对象结构的指针常常作为DriverEntry,AddDevice, Unload等函数的参数。驱动对象结构式半透明的。其中公开的域包括DeviceObject,DriverExtensio
  • 内核源码目录结构

    2014-03-10 21:17:53
    浏览内核代码之前,有必要知道内核源码的整体分布情况,按照惯例,内核代码安装在/usr/src/linux目录下,该目录下的每一个子目录都代表了一个特定的内核功能性子集,下面针对2.6.23版本进行简单描述。 (1)...
  • 本发明涉配usb设备识别技术领域,特别是涉及一种在linux内核中识别特定usb大容量存储设备的方法及系统。背景技术:在linux系统下对usb设备进行管控,一般而言有两种方法,一种是阻断新插入设备为主,辅之以禁用已...
  • arch/子目录包含了linux支持的所有硬件结构内核代码。areh/子目录下有x86、ARM和Alpha等针对不同体系结构的代码 。 divers/子目录包含了内核中所有的设备驱动程序,如字符设备、块设备。scsi设备 驱动程序等。 fs/...
  • 一、Linux内核源代码的结构Linux 内核源代码位于/usr/src/linux 目录下。include/目录包含了建立内核代码时所需的大部分包含文件,这个模块利用其他模块重建内核。init/ 子目录包含了内核的初始化代码,这是内核开始...
  • 文件系统是操作系统用于明确存储设备(常见的是磁盘,也有基于NAND Flash的固态硬盘)或分区上的文件的方法和数据结构;即在存储设备上组织文件的方法。操作系统负责管理和存储文件信息的软件机构称为文件管理系统...
  • 详解:Linux内核源代码

    千次阅读 2021-05-10 06:54:19
    前面总结了关于Linux内核结构的学习,接下来我们一起学习Linux内核源代码。第一:多版本的内核源代码对不同的内核版本,系统调用一般是相同的。新版本也许可以增加一个新的系统调用,但旧的系统调用将依然不变,这...
  • linux内核结构详解

    千次阅读 2013-01-24 20:49:40
    Linux内核主要由五个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。   1.进程调度(SCHED):控制多个进程对CPU的访问,使得多个进程能在CPU微观串行运行,看起来却像是并行运行。驱动...
  • Linux内核移植介绍

    千次阅读 2021-01-11 19:17:41
    LINUX 内核移植 一、内核移植概述 二、Linux内核的目录 三、 内核配置 四、Kbuild Makefile 五、编译连接内核 六、内核启动过程 七、系统环境变量的设置方法 八、实验步奏 与其它操作系统相比,Linux最大的特点:它...
  • 2)对SCSI协议支持比较简单,一些cluster的特性比如PR等都不支持,所以基于stgt的方案不能在cluster使用; 3)对于多initiator访问同一target的场景,性能表现不好,这个主要是因为tgt的实现所致,即它目前的...
  • Linux内核源代码目录结构

    千次阅读 2017-08-02 12:05:12
    Linux2.6.35.7 内核源代码文件的目录结构

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 20,727
精华内容 8,290
关键字:

内核中scsi的结构