如何获取块设备底层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数据:
- 打开设备(可以是
/dev/sgX这类SG设备文件,也可以直接打开/dev/sda这类块设备文件,只要内核支持)。 - 构造
sg_io_hdr_t结构体,指定要执行的SCSI命令、数据缓冲区、sense缓冲区。 - 调用
ioctl(fd, SG_IO, &hdr)执行命令。 - 命令失败后,直接从
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
相关产品推荐
相关产品推荐

