iscsi:IO操作流程(五)——IO完成处理

概述

前面讲到,iscsi initiator实现过程采用了多级的异步模式,通过异步模式使IO操作阶段能够批量处理。这种异步机制的存在主要为了提升系统的吞吐量。从设计的角度,考虑采用异步操作机制的任务往往至少满足以下几个条件:

  • 并发能明显提升系统的IO吞吐。作为iscsi服务,其瓶颈点往往在磁盘上。但是多块磁盘可以并行进行读写,因此,IO的并发可以充分利用多个磁盘的带宽。并发可以避免由于某个IO操作,而使其它本来可以不需要等待的IO被hang住了。

  • 各任务之间顺序无关。由于并发会使得顺序传递到系统的任务可能被打乱顺序执行。因此,这项必须保证。就iscsi服务而言,我们iscsi task任务的视角。由于前面已经保证了同一conn任务是顺序执行的,那么不同conn上面的任务必然顺序无关,即可以乱序执行。

  • 任务在执行过程中需要等待某个事件的达成。所谓异步,其概念与同步相对,表示任务被执行过程中,不能立该得到结果,需要等待某个状态或者事件发生。在等待的过程中,并发处理其他任务。也就是,单纯的异步操作不会产生价值。只有并发异步才会增大吐吞(此概念可以共同探讨)

采用异步IO的目的是充分利用各类资源(CPU、内存、磁盘IO),从而提升系统的吞吐,降低IO平均时间。如下图所示。如图所示:

iscsi:IO操作流程(五)——IO完成处理_第1张图片

假设4个任务,顺序的使用网络、CPU、硬盘三种硬件资源,使用的时间别为T1、T2、T3,为了分析方便,我们假设他们 $ T1=T2=T3=T $ ,当4个任务为串行时,其平均时间为

a v g ( 4 ) = 4 ∗ 3 T 4 = 3 T avg(4)=\frac {4*3T}{4}=3T avg(4)=443T=3T

我们可以看到,任务平均执行时间为:

a v g ( 4 ) = 6 T 4 = 1.5 T avg(4) = \frac{6T}{4}=1.5T avg(4)=46T=1.5T

通常情况下,当调度合理时,即缩小了任务总体执行时间,也缩小了每个任务的响应时间。基于上述分析,在IO操作的流程中,Linux系统为充分利用所有的资源,通常采用异步操作的方式。

与上述案例不同,实际执行过程中,每个任务使用不同资源的时间并不相等,在程序设计中,我们只要让所有的资源同时在工作,即可使系统吞吐最大化,进而再解决某类资源不足成为瓶颈的问题,具体操作时,可以将一个任务根据使用资源的不同分成不同的阶段,CPU调度各个资源的使用,一旦CPU将任务发送到某类资源之后,继续执行下一个任务。

针对上述IO密集型场景,微软提出实现了IOCP(I/O Completion Port)模型。我们在此不讨论具体是Linux最先实现还是微软最先提出,总体上机制差不多。基本遵循以下流程:

1、CPU处理IO任务发现需要处理IO时,将其任务请求转到某个设备

2、CPU将任务置为等待IO处理完成状态

3、CPU继续处理其它任务

4、当设备处理完IO时,采用某种通知机制,如中断通知CPU

5、CPU接到IO处理完成事件后,继续处理IO中的其它逻辑

iscsi处理基本遵循上述流程。我们进行详细解读。在前面我提到解释了一个上层IO请求,如何封装成bio,放到请求中,进而作为一个scsi任务提交给iscsi层。本章主要分析iscsi处理完成之后的完成处理流程。

iscsi数据传输

iscsi传输层

iscsi设备与本地的设备不同,其scsi命令是通过网络传递到target端进行解析并执行,通常情况下,这些协议层次的处理都是由CPU完成的。如果使用专用的HBA卡,其处理流程有所简化,不在此讨论之列。特别提一句的是,在内核里面,为提供多种类型的iscsi传输层。假如为一个HBA卡写一个驱动,可以调用iscsi_register_transport注册自己的iscsi传输层处理函数。此函数进行一系列初始化后,并将其加到iscsi_transports中。

当一个会话被创建时,系统根据输出参数判断具体的传输层并将其初始化到session结构体的transport成员中,而在为会话创建链接时,再次将其指定到链接结构体的相应成员中。

对于通用网卡情景下,iscsi的transport被定义为iscsi_sw_tcp_transport。其各项内容具体说明如表所示:

成员 说明
owner THIS MODULE 指定该传递层是由谁来实现的
name “tcp” 指定传层的可读名称
create_session iscsi_sw_tcp_session_create 该回调在创建会话时被调用。
destroy_session iscsi_sw_tcp_session_destroy 该回调在删除会话时被调用
create_conn iscsi_sw_tcp_conn_create 该回调创建连接时被调用
bind_conn iscsi_sw_tcp_conn_bind 该回调处理连接绑定相关的操作,系统将传输前的初始化放在此回调中
destroy_conn iscsi_sw_tcp_conn_destroy 当连接被删除时调用
attr_is_visible iscsi_sw_tcp_attr_is_visible 用于设定sysfs文件相关属于访问权限
set_param iscsi_sw_tcp_conn_set_param 用于设定iscsi相关参数
get_conn_param iscsi_sw_tcp_conn_get_param 用于获取iscsi相关参数
start_conn iscsi_conn_start 该回调在连接过程中被调用
stop_conn iscsi_sw_tcp_conn_stop 该函数在连接终止时被调用
get_host_param iscsi_sw_tcp_host_get_param 用于获取与主机相关的参数
set_host_param iscsi_host_set_param 用于设定与主机关的参数
send_pdu iscsi_conn_send_pdu 用于将PDU发送给target
get_stats iscsi_sw_tcp_conn_get_stats 用于获取统计信息
init_task iscsi_tcp_task_init 在iscsi任务初化时被调用,主要用于SCSI_READ和SCSI_WRITE相关的任务的初始化工作
xmit_task iscsi_tcp_task_xmit 当传输任时被调用
cleanup_task iscsi_tcp_cleanup_task 清理任务时被调用
xmit_pdu iscsi_sw_tcp_pdu_xmit 传输PDU给target
init_pdu iscsi_sw_tcp_pdu_init 初始化PDU
alloc_pdu iscsi_sw_tcp_pdu_alloc 申请PDU内容
session_recovery_timedout iscsi_session_recovery_timeout 会话恢复相关操作

数据接收

iscsi连接是在用户空间执行的。用户空间接收连接请求时,通过netlink调用传递消息给内核模块,进而在内核进行绑定处理。处理完成之后,内核将接管连接所使用的socket的事件处理和信息传递。在open-iscsi的设计,系统将target发现、登录、参数商定等的处理均放在用户空间进行,而将数据传输阶段的处理放在内核层进行。此类设计的笔者认为主要是在性能和设计原则方面的权衡。一方面,内核空间不宜处理过多的非设核心处理;另一方面,iscsi将LUN呈现为一个虚拟的块设备,需要在内核空间实现。如若,将所有的网络处理都放在用户空间,势必需要进行不必要的内核空间到用户空间的通信。

内核接管操作主要由函数iscsi_sw_tcp_conn_bind完成,主要工作为:

  • 获取sock对象

在用户空间,创建socket时,返回的文件描述符是fd,且在进程范围内有效。而内核空间socket操作是使用struct socket结构体的实例。因此,内核接管socket首先做的是将通过fd找到对应的struct socket结构体。

  • 进行必要的赋值

iscsi实现中,在内核里维护了iscsi操作所必须的结构,比如iscsi通用的session、conn等结构,同时也维护了针对软件实现iscsi所必须的数据结构iscsi_sw_tcp_conn

  • 设置socket参数

在此步骤中,主要对socket接收前一些参数进行设置

  1. 将socket设置为可重用

  2. 设置发送超时时间为12s

  3. 设置内存申请标志GFP_ATOMIC|__GFP_MEMALLOC

4.调用sk_set_memalloc

  • 修改socket的回调函数,接管socket数据传递

  • 初始化iscsi接收数据状态机,以便进行处理

那么,数据如何传递给iscsi模块,并进行处理呢。关健点就是上述流程中对回调函数的设置,实现中,此操作是由iscsi_sw_tcp_conn_set_callbacks完成的,主要设定了sk_data_readysk_state_changesk_write_space三个回调。当有数据到达该连接时,sk_data_ready就会被激发。

sk_data_ready是在软中断上下文中被执行的。当数据到达网卡后,会给CPU产生一个硬中断。我们知道中断处理,通常设计中断处理分为上半部和下半部。上半部在中断上下文环境,为避免影响后续中断的产生,需要快速处理,而把更大量的工作放在下半部完成 [^1] 。下半部实现可以采用软中断或work_queue等模式。Linux网络设计中使用软中断的模式,即TCP/IP协议栈的处理在软中断中进行,直到将数据通过socket交付给用户层。在iscsi实现中,由于我们重设了sk_data_ready回调函数,数据不会交给用户层,而是直接在软中断上下文中处理iscsi数据接收的一些逻辑。iscsi中sk_data_ready函数设置为iscsi_sw_tcp_data_ready。当数据到达时,此函数直接在中断上下文中被执行。

[^1] https://blog.packagecloud.io/eng/2016/06/22/monitoring-tuning-linux-networking-stack-receiving-data/#irqs

你可能感兴趣的:(块存储,协议,linux)