除了之前介绍的正常完成SCSI层IO外,实际上还存在其他情况:IO发到控制器或硬盘后没有返回导致超时(称为IO的超时);硬件返回带错误信息的返回(IO返回异常)。这些情况需要借助SCSI错误处理进行恢复操作。
SCSI层IO完成最终调用函数scsi_complete(),如下图所示,存在下列五种状态完成IO:
- 以成功完成IO:要求传输的数据全部完成处理,返回状态为成功,此时结束IO;
- 完成部分IO:只传输完部分IO,返回状态为成功,此时会将剩下的数据组成新的IO,进行重新排队发送;
- 重试IO: 两种情况,一种为返回状态为重试;另一种为返回状态为成功,但存在错误,部分sense key错误指示可以重试;
- 以失败完成IO:返回状态为成功,但存在错误,且不允许重试(或重试次数用完);
- 进入错误处理恢复
函数scsi_decisde_disposition()根据当前IO完成状态以及可能带的错误信息(如sense key)决定当前IO以上述五种情况中的那种情况进行处理。
这里仅对错误处理做介绍,后续再对其他情况作详细介绍。
1. 进入错误处理的时机
上述IO异常返回FAIL时,会调用函数scsi_eh_scmd_add()尝试进行错误处理,但并不一定就直接进入错误处理,只有当系统中inflight的IO要么超时,要么异常返回,才会真正进入错误处理。
2. 错误处理的操作
错误处理最终唤醒错误处理线程shost->ehandler(),它在分配和初始化SCSI HOST时创建。若驱动定义了eh_strategy_handler()回调,错误处理线程会执行eh_strategy_handler()回调,否则会执行SCSI层错误处理公共函数scsi_unjam_host()。在这里介绍LIBSAS自定义的回调sas_scsi_recover_host()。虽然操作有差异,但错误处理大体思路类似,恢复操作从轻到重,逐步升级。
LIBSAS错误处理过程大体如下:
1) 将带sas_task的IO命令和不带sas_task的IO命令分开处理,其中带sas_task的IO命令实际上是超时IO,不带sas_task的IO命令为带error返回的IO;
2) 对于超时IO,说明IO可能还在硬件侧,首先尝试去abort和query该IO,若成功(DONE和ABORTED),完成该IO,进行下一个IO的处理,若返回TASK_IS_AT_LUN,说明在LUN中,发送sas_recover_lun()尝试abort掉lun中所有IO;若返回TASK_IS_NOT_AT_LUN或ABORT_FAIL,说明链路可能存在,升级到lldd_I_T_nexus_reset(),进行链路的复位,若成功完成链路的IO,否则升级到lldd_clear_nexus_port(),进行port的复位,若成功完成port的所有IO,否则升级到最后的大杀器,进行控制器复位lldd_clear_nexus_ha(),完成整个ha的所有IO;
3) 对于带error的异常返回IO,首先尝试获取sense key,根据sense key决定下一步,若返回SUCESS或NEEDS_RETRY,完成IO,处理下一个IO,否则(包括存在无sense key的异常IO或除了上述两种返回的IO外)调用scsi_eh_ready_devs()逐步升级处理,这里与上面升级类似,不做介绍;
4) 若存在reset操作没有执行,在此处处理reset(函数sas_eh_handle_resets);
5) 若存在ATA设备时,进入libata eh进行错误处理,后面有机会对这部分做介绍;
6) 检查完成的IO,尝试进行重试(若允许重试切重试次数没达到上限时);