Linux内核SCSI错误处理实战:当硬盘IO卡住时内核的"侦探工作流"

当服务器日志突然蹦出一条"SCSI error: host 0 channel 0 id 0 lun 0 sense key Not Ready"的警告时,作为运维工程师的你,是否好奇过内核在后台默默执行了哪些"抢救措施"?这就像一场精心设计的侦探剧——从错误触发到恢复,内核的SCSI错误处理机制构建了一套缜密的"破案流程"。

1. 案发现场:SCSI错误的两种典型形态

想象一下,你正在指挥一队快递员(SCSI命令)从仓库(存储设备)取货。突然出现了两种异常情况:

  • 快递员失联:命令发出后如同石沉大海,直到超时都未收到回复(IO timeout)
  • 快递员带回坏消息:设备返回了包含错误状态码的响应(CHECK CONDITION)

内核通过scsi_decide_disposition()函数对这两种情况进行初步"验伤":

/* 典型的状态判断逻辑 */
switch (result) {
    case DID_OK:          // 完全成功
        if (good_bytes < scsi_bufflen(scmd))
            return NEEDS_RETRY;  // 部分传输需要重试
        break;
    case DID_TIME_OUT:    // 超时案件
        return FAILED;
    case DID_ERROR:       // 明确错误
        return parse_sense_key(scmd);  // 解析sense key
}

关键决策点在于错误处理并非立即启动。只有当满足以下条件时才会激活"侦探小组"(SCSI Error Handler):

  1. 该主机上所有未完成IO要么超时要么异常返回
  2. 通过scsi_eh_scmd_add()将问题命令加入调查队列
  3. 累计错误达到阈值(避免频繁处理零星错误)

提示:可通过/sys/class/scsi_host/hostX/eh_deadline调整错误处理超时时间(单位秒)

2. 侦探小组的装备箱:错误处理的分级策略

现代SCSI子系统采用类似急诊室"分诊机制"的渐进式恢复策略,其核心哲学是:用最小代价解决问题。就像医生不会对每个感冒患者都开CT检查一样,内核也遵循这样的升级路径:

恢复等级操作类型影响范围典型耗时适用场景
1级ABORT/QUERY单个命令毫秒级命令卡在设备队列
2级LUN复位单个LUN10-100msLUN逻辑单元异常
3级链路复位(I_T_Nexus)设备连接100ms级HBA与设备通信故障
4级端口复位整个端口秒级物理链路问题
5级主机控制器复位整个HBA数秒控制器固件挂死

在LIBSAS驱动的实现中,这个流程体现在sas_scsi_recover_host()的决策树中:

// 简化的处理流程
if (task_abort_success(scmd)) {
    finish_cmd(scmd);  // 成功中止命令
} else if (task_status == TASK_IS_AT_LUN) {
    sas_recover_lun(dev);  // 升级到LUN复位
} else {
    sas_phy_reset(phy);    // 尝试链路复位
    if (failed) {
        sas_port_reset(port);  // 再升级到端口复位
    }
}

实战技巧:通过内核日志可以观察这个升级过程:

$ dmesg | grep -i sas
[  +0.000103] sd 0:0:0:0: [sda] tag#0 ABORT operation started
[  +0.342897] sd 0:0:0:0: [sda] tag#0 ABORT operation failed
[  +0.000011] sas: lldd_I_T_nexus_reset for phy1 initiated
[  +0.120543] sas: I_T nexus reset for phy1 succeeded

3. 物证分析:Sense Key的解码艺术

当设备返回CHECK CONDITION时,Sense Key就是关键的"指纹证据"。熟练解读这些代码能快速定位问题根源:

  • 0x02:NOT READY

    • 常见原因:设备未完成初始化、介质未就绪
    • 典型动作:等待+重试
  • 0x03:MEDIUM ERROR

    • 常见原因:物理坏块、校验失败
    • 典型动作:标记坏块或触发介质扫描
  • 0x04:HARDWARE ERROR

    • 常见原因:控制器或固件故障
    • 典型动作:尝试复位操作
  • 0x05:ILLEGAL REQUEST

    • 常见原因:无效命令参数
    • 典型动作:终止请求并记录

通过sg_logs工具可以直接获取这些关键信息:

$ sg_logs --sense /dev/sda
>>  Sense key: Hardware Error
>>  Additional sense: Internal target failure
>>  Field replaceable unit code: 0x1A

高级技巧:某些HBA卡支持自动重试策略配置(以LSI MegaRAID为例):

$ storcli /c0 show all | grep -A5 "Retry Count"
Retry Count = 8 
PD Retry Count = 5 
Phy Error Retry = 5 
Link Down Retry = 6 

4. 现场重建:模拟真实故障的实验方法

理解理论最好的方式就是亲手触发错误。以下是几种安全的实验方法:

4.1 模拟命令超时

使用sg_turs工具人为制造设备无响应:

# 设置超时时间为5秒
$ sg_turs --timeout=5 /dev/sdb
>>  sense key: Timeout
>>  Transport: retries exhausted

观察内核行为:

$ watch -n 0.1 "cat /proc/scsi/scsi | grep -A3 'Host: scsi0'"

4.2 注入介质错误

通过SCSI DEBUG设备模拟坏块:

# 加载调试驱动
$ modprobe scsi_debug dev_size_mb=1000 opts=2 no_lun=0
# 查看生成的设备
$ lsscsi
[6:0:0:0]   disk    Linux    scsi_debug       0004  /dev/sdc
# 写入触发错误
$ dd if=/dev/urandom of=/dev/sdc bs=1M count=10
>>  sd 6:0:0:0: [sdc] tag#0 FAILED Result: hostbyte=DID_OK driverbyte=DRIVER_SENSE

4.3 跟踪错误处理流程

使用systemtap动态追踪错误处理线程:

probe kernel.function("scsi_error_handler") {
    printf("Error handler activated for host%d\n", $shost->host_no)
}
probe kernel.function("sas_scsi_recover_host") {
    printf("Recovery level: %d\n", $recovery_level)
}

5. 性能与可靠性的平衡艺术

在真实的存储系统中,错误处理策略需要微妙的平衡:

超时参数的黄金法则:

  • 普通SAS硬盘:建议默认超时30秒
  • SSD/NVMe设备:可缩短至5-10秒
  • 磁带设备:可能需要延长至数分钟

重试策略的优化点:

# 伪代码:智能退避算法示例
def calculate_retry_delay(attempt):
    base_delay = 1.0  # 基础延迟1秒
    max_delay = 30.0   # 最大延迟30秒
    return min(base_delay * (2 ** attempt), max_delay)

监控指标关注点:

  • /sys/class/scsi_host/hostX/stats中的复位计数
  • iostat -x中的await和svctm异常波动
  • SMART日志中的硬件错误计数

在云环境中,我们曾遇到过一个典型案例:某批次的NVMe盘在特定负载下会出现微秒级的链路抖动。通过将eh_deadline从默认的60秒调整为5秒,并将链路复位前的重试次数从3次降为1次,成功将故障恢复时间从分钟级缩短到秒级。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐