iscsi:IO操作流程(四)

系统构建SCSI指令后,将调用scsi_host的queucommand操作,将指令下移到LLD设备层进行处理。

scsi_host在iscsi协议中的角色

scsi_host在系统中启动承上启下的作用。对上接收上层驱动设备转发的命令,对下连接下层的软件硬件,进行SCSI命令的进一步处理。scsi_host逻辑上是scsi指令从scsi协议层到scsi传输层之间的接口。在物理上,scsi_host代表着scsi设备的适配器。SAS协议的适配器是sas控制器,FC协议的适配器是FC HBA卡,可以得出,通常意义上scsi_host接收的到的scsi命令的处理应该转到板卡上处理。

iscsi的可以分成两个实现机制,一是使用通用的网卡,一是使用专用的iscsi HBA卡(类似于FC)。使用专用HBA卡时,iscsi协议的解析处理由HBA卡进行,而scsi_host设置与HBA一对一。使用通用网卡时,物理的HBA卡不存在,换句话说,流程上我们不需要scsi_host这一层进行转换和传递,但考虑到系统概念的统一性、操作的一致性,依然需要在逻辑上存在这个概念。因而,当使用通用网卡实现iscsi协议时,这scsi_host的作用就成为接收上层的SCSI指令并进行处理。

另外一个问题,为什么我们不把网卡看成HBA卡,scsi_host与网卡一一对应呢。此处的设计与iscsi协议有一定关系。在iscsi协议里面,发起端与目标端间在传输数据之前先建立会话(session),协议又规定,在一个会话里面可以有多个连接存在,而为了实现连接之间的独立性,两个不同的链接不应该通过同一个网卡(逻辑上没有对此限制),因而,我们不应该限制会话跨网卡。因此,也不应该使scsi_host与链接一一对应。
scsi_host接收scsi指仅之后IO操作流程便转入了iscsi处理层次。在iscsi层,系统不关心scsi指令的具体含义,只关心scsi指令传输到目标端所面临的问题,最直接的问题就如传输的目的地,分几次传输,用哪个连接进行传输,传输出错如何处理。 这些问题在iscsi协议里均进行了很好的回答。

iscsi中数据传输

在iscsi层面,每个scsi命令将作为一个task进行传输,一个task在传输时,可能被分成多个数据段进行传递,在iscsi层次提出task的概念可以较好的进行错误处理。当发生传输故障的那一时刻,整个SCSI指令请求可能传递了一部分,如果是临时性的连接问题,处理方式继续传递即可。如果传输层的错误时间过长导致上层应用超时,这时上层应用要求整条命令进行重试,那么在重视之前必然需要将当前还未传输的数据取消掉。这时就用到了scsi协议定的任务管理相关的功能。

两个实体间交互的最小单位是PDU。scsi的CBD和数据都被封装在PDU里面进行传输。为了传输不同的交互内容,iscsi协议定义了17种PDU格式。具体可以参考rfc协议中每种PDU格式的定义。

回到iscsi IO操作层面,其参考流程在iscsi rfc 参考文档中已经给出。
iscsi读操作流程上如图所示。
这里写图片描述
READ操作请求方不包括数据,因此,发起端只需要发送READ指给目标端即可。目标端接收到命令后,获取对应的数据,并将其返回。从发送请求到收到响应整个过程完成后才表示READ指令执行完成。基于上述流程,我们可以总结以下几点:

  • 一个任务中在传输层可以需要经过1个或者多个PDU才能够完成。
  • 每个任务起始的第一个PDU类型必然是scsi command命令。
  • 在请求读之前,发起端要准备好数据接收环境,比如申请足够的内存等。

与之对应,写操作如下图所示:
这里写图片描述
写操作除了发送SCSI指令外,还要将数据传递给目标端。发起端向目标端传递的PDU的数目至少有1个(SCSI Command可以在PDU中携带数据),PDU的最大数取决于写数据的长度。

写操作除scsi command所携带的数据,每个传输数据之前需要等目标端准备好之后再进行传输。这样避免了目标端因不定传输数据规模而没有准备好传输空间,致命传输数据失效。

queuecommand

iscsi子系统的处理起源于scsi中间层调用LLD层的queuecommand,queuecommandscsi_host_template结构体的一个回调。前面,我们讲到scsi子系统通过scsi_host将scsi命令传递给scsi的传输层进行处理,而iscsi的实现没有真实的scsi_host存在。因此必须生成一个虚拟的。在实现中,虚拟的scis_host创建于会话建立时,当会话释放时,它也根着不存在了。创建过程逻辑相对比较简单,与IO相关的工作主要有两个:
一是:将包括各种回调的scsi_host模板iscsi_sw_tcp_sht注册到scsi子系统,以其通够通过设备描述符找到相应的回调并在相应的调用时机进行调用;
另外:创建协议处理需要的单线程工作队列。

在这个过程中,将scsi_host_templatequeuecommand回调指向了iscsi_queuecommand函数,可以这个函数为起点研究iscsi层次处理的主要逻辑。

iscsi_queuecommand原型如下:

int iscsi_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *sc)

有两个参数。host指向当向需要处理的scsi指令的指针。sc指向当前需要处理的指令。当此函数能够正确的指scsi指令进行处理时,返回0,如果在处理过程中发现错误,则尝试结束这个任务,如果不能正常结束任务,则返回SCSI_MLQUEUE_TARGET_BUSY

在分析协议相关的处理时,我们需要严格区别两种类型的异常。一种是程序逻辑的异常,比如由于传递函数指针为空等。这种调用性的错误将通过错误返回进行处理。另外一种异常是协议级的异常。这类异常因素比较复杂,有网络问题,兼容性问题等等,而对这种异常的程序逻辑必须是没有错的。

iscsi_queuecommand执行流程主要有以下几步:
- 检查会话、链接等上下文状态是否可以处理scsi指令。
检查会话通过iscsi_session_chkready进行。当会话状态不是ISCSI_SESSION_LOGGED_IN时,不适合处理scsi指令。
链接检查通过链接是否存在、链接状态、链接可接收的命令窗口是否达到最大值。这几个方面判断。

    conn = session->leadconn;
    if (!conn) {
        reason = FAILURE_SESSION_FREED;
        sc->result = DID_NO_CONNECT << 16;
        goto fault;
    }

    if (test_bit(ISCSI_SUSPEND_BIT, &conn->suspend_tx)) {
        reason = FAILURE_SESSION_IN_RECOVERY;
        sc->result = DID_REQUEUE << 16;
        goto fault;
    }

    if (iscsi_check_cmdsn_window_closed(conn)) {
        reason = FAILURE_WINDOW_CLOSED;
        goto reject;
    }

  • iscsi_alloc_task分配并构建task对象。
  • 将链接插入到链接的命令队列中,待处理。

数据发送

iscsi数据收发由单独的线程进行。iscsi子系统为每个scsi_host初始化了一个工作队列用于收发数据。当某个链接有需要传输的命令时,
数据发送函数调用iscsi_xmitworker进行。这个函数遍历conn中各个task队列,并进行处理。每个conn有三个队列,分别称作mgmtqueuecmdqueuerequeue。数据路径中的task都放在cmdqueue
针对特定的task,系统先在conn的task域中记录这个任务,然后再执行发送。一个conntask被串行发送。当一个task未被发送完成前,下一次task不会开始。如果当前的task需要等待客户端的PDU RESPONSE后才能继续时,工作线程将会持续重试,直到达到要求时才会进一步处理。以数据写操作为例,在写操作这个task中,发起端首先发送写SCSI指令,接下来等待,直到目标端返回R2T响应后,发送下一次PDU。

针对某一个具体的task,iscsi子系统将调用iscsi_transportxmit_task接口进行处理。iscsi_transport定义了会话处理、链接处理、数据发送等一系列操作具体实现。我们之前讲到每种iscsi子系统的实现将会有不同的操作方式,比如在某厂商的HBA卡的实现中,此类操作真正的逻辑由HBA具体实现和管理,而驱动层通过寄存器或者总线相互传递状态和指令即可,而纯软iscsi的实现确要实现会话、链接、数据传输的协议规范细节。纯软iscsi实现中,定了iscsi_sw_tcp_transport结构。

iscsi_sw_tcp_transport结构的xmit_task定义为iscsi_tcp_task_xmit。该函数就是一个将task拆分成PDU,然后将PDU发送到目标端的过程。对于IO操作的命令。这个函数在调用之前,已经通过iscsi_prep_scsi_cmd_pdu初始化含有scsi指令的PDU。我们上面说过,这个是每个任务需要处理的第一个PDU。接下来,如果有数据需要发送的话,进一步调用iscsi_prep_data_out_pdu构建data type类型的PDU,然后按同样的试进行发送。当然,上面我们也提到过,在发送数据PDU之前,需要先收到目标端发过来的R2T响应,以表示目标端已经准备好了接收命令。

针对每个PDU的处理操作就是通用的socket发送逻辑了。我们不详细分析。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值