You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取块设备底层IO错误及驱动上报的底层错误详情

好问题!当你用read()/write()/lseek()这类通用系统调用操作块设备(像/dev/hdd、/dev/sgX或者/dev/nvmeXnY这类)碰到EIO错误时,确实没法直接从这些unistd函数拿到底层的硬件错误详情——因为这些系统调用会把复杂的硬件错误封装成通用的errno返回,不会暴露SCSI sense或者NVMe错误日志这类底层数据。下面给你分两种场景梳理解决方案:

一、如果坚持用unistd函数:通过sysfs事后查询错误信息

如果不想改动现有用read()/write()的代码,只能通过内核在sysfs中暴露的节点来事后获取错误信息:

  • 块设备的错误日志通常会被内核存在/sys/block/[dev_name]/device/路径下(比如/sys/block/sda/device/对应/dev/sda)。
  • 对于SCSI设备,你可以读取该目录下的sense文件,里面会保存最近的SCSI sense数据;对于NVMe设备,错误信息可能在/sys/class/nvme/nvmeX/下的error_log或相关属性文件中。
  • 举个简单的代码示例,在read()返回EIO后读取sense数据:
#include <stdio.h>
#include <unistd.h>
#include <scsi/sg.h>

void get_scsi_sense(const char *dev_path) {
    char sysfs_path[256];
    // 从/dev/sda提取设备名sda,拼接sysfs路径
    snprintf(sysfs_path, sizeof(sysfs_path), "/sys/block/%s/device/sense", dev_path + 5);
    FILE *fp = fopen(sysfs_path, "r");
    if (!fp) {
        perror("Failed to open sense file");
        return;
    }
    unsigned char sense_buf[256];
    size_t len = fread(sense_buf, 1, sizeof(sense_buf), fp);
    fclose(fp);
    // 解析sense数据为sg_scsi_sense_hdr结构体
    if (len >= sizeof(sg_scsi_sense_hdr_t)) {
        sg_scsi_sense_hdr_t *hdr = (sg_scsi_sense_hdr_t *)sense_buf;
        printf("Sense Key: 0x%02X, ASC: 0x%02X, ASCQ: 0x%02X\n",
               hdr->sense_key, hdr->asc, hdr->ascq);
    }
}
  • 注意:这种方式是事后查询,无法和具体的read()/write()调用绑定,可能会被后续的设备操作覆盖错误信息,而且sysfs路径可能因内核版本、设备类型略有差异。
二、更可靠的方案:直接使用SG_IO/NVMe IOCTL获取底层错误

如果想精准获取和操作绑定的底层错误(比如sg_scsi_sense_hdr),必须绕过通用unistd函数,直接使用设备专属的ioctl命令和驱动交互:

2.1 SCSI类设备(HDD、多数SSD):使用SG_IO ioctl

几乎所有SCSI兼容设备(包括通过SCSI协议暴露的SATA/SAS设备)都支持SG_IO命令,它能让你直接发送SCSI命令,并实时获取sense数据:

  1. 打开设备(可以是/dev/sgX这类SG设备文件,也可以直接打开/dev/sda这类块设备文件,只要内核支持)。
  2. 构造sg_io_hdr_t结构体,指定要执行的SCSI命令、数据缓冲区、sense缓冲区。
  3. 调用ioctl(fd, SG_IO, &hdr)执行命令。
  4. 命令失败后,直接从sensep指向的缓冲区解析sg_scsi_sense_hdr。

示例代码片段:

#include <fcntl.h>
#include <sys/ioctl.h>
#include <scsi/sg.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>

int main() {
    int fd = open("/dev/sg0", O_RDWR);
    if (fd < 0) {
        perror("Failed to open SG device");
        return 1;
    }

    sg_io_hdr_t hdr = {0};
    // SCSI READ10命令:读1个扇区(512字节)
    unsigned char read_cmd[10] = {0x28, 0, 0, 0, 0x01, 0, 0, 0, 0x01, 0};
    unsigned char data_buf[512] = {0};
    unsigned char sense_buf[256] = {0};

    // 填充SG_IO头部参数
    hdr.interface_id = 'S';
    hdr.cmd_len = sizeof(read_cmd);
    hdr.cmdp = read_cmd;
    hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    hdr.dxfer_len = sizeof(data_buf);
    hdr.dxferp = data_buf;
    hdr.sense_len = sizeof(sense_buf);
    hdr.sensep = sense_buf;
    hdr.timeout = 5000; // 5秒超时

    if (ioctl(fd, SG_IO, &hdr) < 0) {
        if (errno == EIO) {
            printf("IO error occurred, parsing sense data...\n");
            sg_scsi_sense_hdr_t *sense_hdr = (sg_scsi_sense_hdr_t *)sense_buf;
            // 检查是否为有效的sense数据
            if ((sense_hdr->response_code & 0x70) == 0x70) {
                printf("Sense Key: 0x%02X (%s)\n", sense_hdr->sense_key, 
                       sense_hdr->sense_key == 0x03 ? "Medium Error" : "Other");
                printf("ASC: 0x%02X, ASCQ: 0x%02X\n", sense_hdr->asc, sense_hdr->ascq);
                // 根据这些值可查询具体错误,比如0x11/0x00表示未校正的介质错误
            }
        }
    }

    close(fd);
    return 0;
}

2.2 NVMe设备:使用NVMe专属IOCTL

NVMe设备有自己的命令集,需要用NVME_IOCTL_ADMIN_CMD或NVME_IOCTL_IO_CMD来发送命令,错误信息会返回在nvme_completion结构体的status_field中,也可以读取错误日志页面获取详情:

  • 你需要包含<linux/nvme.h>头文件,构造nvme_admin_cmd结构体发送管理命令(比如读取错误日志)。
  • 命令执行后,从nvme_completion的status字段解析具体错误码,比如NVME_SC_INVALID_OPCODE表示无效命令,NVME_SC_MEDIA_ERROR表示介质错误。
三、总结
  • 用标准unistd函数(read()/write())只能通过sysfs事后查询错误,精度和实时性不足;
  • 要获取sg_scsi_sense_hdr这类底层错误信息,必须使用SG_IO(SCSI设备)或NVMe专属IOCTL接口,直接与设备驱动交互,才能实时绑定到具体操作并拿到精准的错误详情。

内容的提问来源于stack exchange,提问作者kreuzerkrieg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:02:16