创建虚拟机时,可以选择SCSI,STAT,NVME不同类型的磁盘。
0:总结
===》了解内核提供的访问scsi的结构和方法 (主要是sg_io_hdr_t 结构体和ioctl函数)。
===》需要读scsi协议文档,了解相关指令,只演示了16字节固定长度读和写指令。
===》了解mmap,直接映射磁盘可以实现读写功能。
1:简单了解概念。
sata 是串行接口,访问sata设备, 除了使用控制指令(原语交互),就是使用fis数据包进行数据交互。(直接使用串口连接进行通信)
scsi 采用并行传输方式,支持多个指令同时发送,需要参考对应的协议文档,构造协议进行磁盘的交互。
nvme是一种基于pcie总线封装的存储接口协议,支持多个队列、并行操作和低延迟I/O操作等。
2:构造scsi相关交互指令,和scsi设备进行读写交互。
这里采用虚拟机测试的方法,新增磁盘,选择磁盘类型为scsi,操作改磁盘。
2.1 内核中提供了专门的结构,使用ioctl进行写入。
主要关注struct sg_io_hdr 结构体,构造对应的对象,用ioctl进行与scsi设备的交互。
sg_io_hdr_t 是用于与 SCSI 设备进行通信的数据结构,它包含了执行 SCSI I/O 操作时所需的各种参数和状态信息。这个结构体在进行 SCSI 命令的传输和数据交互时起到关键的作用。主要了解 #include <scsi/sg.h> 头文件内容
typedef struct sg_io_hdr
{int interface_id; //表示接口标识,通常设置为 ‘S’,表示 SCSI generic。int dxfer_direction; // 数据传输方向 SG_DXFER_NONE: 没有数据传输。 SG_DXFER_TO_DEV: 将数据从主机传输到设备。 SG_DXFER_FROM_DEV: 将数据从设备传输到主机。 SG_DXFER_TO_FROM_DEV: 双向数据传输。unsigned char cmd_len; // SCSI 命令的长度(字节数)。unsigned char mx_sb_len; //可写入 sense_buffer(感知缓冲区)的最大长度unsigned short iovec_count; //scatter-gather 元素的数量。0 表示没有 scatter-gather 操作。unsigned int dxfer_len; //数据传输的总字节数void __user *dxferp; //指向数据传输内存或 scatter-gather 列表的指针 可以传多个地址unsigned char __user *cmdp; //指向要执行的 SCSI 命令的指针void __user *sbp; //指向 sense_buffer 内存的指针,用于存储设备返回的感知数据unsigned int timeout; // 操作的超时时间,单位为毫秒。MAX_UINT 表示没有超时限制unsigned int flags; //标志位,控制操作的一些行为。可以使用 SG_FLAG... 常量int pack_id; // 用于内部用途的包标识,通常不使用。void __user * usr_ptr; // 内部用途的用户指针,通常不使用unsigned char status; //SCSI 命令的状态unsigned char masked_status;//经过位移和掩码处理后的 SCSI 状态unsigned char msg_status; //消息级别的数据(可选)。unsigned char sb_len_wr; //实际写入到 sense_buffer 的字节数unsigned short host_status; //主机适配器返回的错误状态unsigned short driver_status; //软件驱动程序返回的错误状态。int resid; //实际传输的字节数与预期传输的字节数之间的差值unsigned int duration; // 命令执行的时间,单位为毫秒。unsigned int info; /* [o] auxiliary information */
} sg_io_hdr_t; /* 64 bytes long (on i386) */#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...); //request 是依赖于设备的请求码,使用SG_IO标志与scsi设备进行交互
2.2 参考scsi协议文档,构造对应指令与scsi设备进行交互。
这里参考文档中直接访问指令相关 写和读磁盘相关指令,可以看到提供不同长度的固定长度协议指令(6,10,12,16,32),这里只用固定长度16字节构造指令实现写和读的功能进行测试。
2.2.1 16字节固定长度读指令构造
文档中对应的16位固定长度读指令如下:
对应协议构造与触发指令如下:
//参考对应的协议 设置相关指令进行读取 blkname为对应的scsi设备 lba为读位置, cnt_of_blocks为读的块数
int scsi_cmd16_read(char *blkname, int lba, int cnt_of_blocks)
{int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");return -1;}//按上面固定长度构造对应的指令char cmd[16] = {0x88, 0, 0, 0, 0, 0, (lba >> 24), (lba >> 16), (lba >> 8), lba,(cnt_of_blocks >> 24), (cnt_of_blocks >> 16), (cnt_of_blocks >> 8), cnt_of_blocks,0, 0};char *buffer = (char *)malloc(cnt_of_blocks * BLOCK_SIZE);char sense_buffer[32] = {0};sg_io_hdr_t io_hdr;io_hdr.interface_id = 'S';io_hdr.cmdp = cmd;io_hdr.cmd_len = 16;io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;io_hdr.dxfer_len = BLOCK_SIZE * cnt_of_blocks;io_hdr.dxferp = buffer;io_hdr.sbp = sense_buffer; //附加数据地址io_hdr.mx_sb_len = 32; //附加数据数据长度io_hdr.timeout = 20000;// syncint i = 0;if (ioctl(fd, SG_IO, &io_hdr) < 0) {for (i = 0;i < 32;i ++) { //附加数据中信息 printf("%hx ", sense_buffer[i]);}printf("\n");return -1;}for (i = 0;i < BLOCK_SIZE * cnt_of_blocks;i ++) {if (i % BLOCK_SIZE == 0) {printf("\n\n new block \n");}printf("%hx ", buffer[i]);}printf("\n");close(fd);
}
2.2.2 16字节固定长度写指令构造
协议文档文档中写16位固定长度对应的协议
对应的代码模块:
int scsi_cmd16_write(char *blkname, int lba, int cnt_of_blocks)
{int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");}//参考协议构造对应的指令 char cmd[16] = {0x8A, 0, 0, 0, 0, 0, (lba >> 24), (lba >> 16), (lba >> 8), lba,(cnt_of_blocks >> 24), (cnt_of_blocks >> 16), (cnt_of_blocks >> 8), cnt_of_blocks,0, 0};char *buffer = (char *)malloc(cnt_of_blocks * BLOCK_SIZE);int i = 0;for (i = 0;i < cnt_of_blocks * BLOCK_SIZE;i ++) {buffer[i] = i % 0x80;}char sense_buffer[32] = {0}; //附加信息 用于存储对应的执行结果sg_io_hdr_t io_hdr;io_hdr.interface_id = 'S';io_hdr.cmdp = cmd;io_hdr.cmd_len = 16;io_hdr.dxfer_direction = SG_DXFER_TO_DEV;io_hdr.dxfer_len = BLOCK_SIZE * cnt_of_blocks;io_hdr.dxferp = buffer;io_hdr.sbp = sense_buffer;io_hdr.mx_sb_len = 32;io_hdr.timeout = 20000;if (ioctl(fd, SG_IO, &io_hdr) < 0) {for (i = 0;i < 32;i ++) {printf("%hx ", sense_buffer[i]);}printf("\n");return -1;}printf("write successfull\n");return 0;
}
2.2.3 16字节固定长度读写指令进行测试 main函数。
//了解scsi文档 参考内部其中块控制指令read和write实现对其进行demo测试
int main(int argc, char *argv[]) {char *blkname;if (argc != 4) return -1;blkname = argv[1]; //scsi设备 //逻辑地址和物理地址有映射关系 可以研究,逻辑地址一般是512字节int lba = atoi(argv[2]); //逻辑块地址 和物理地址有映射关系 int cnt_of_blocks = atoi(argv[3]); //块个数int ret = scsi_cmd16_write(blkname, lba, cnt_of_blocks);if (ret) return -1; ret = scsi_cmd16_read(blkname, lba, cnt_of_blocks);if (ret) return -1;return 0;
}
2.2.4 测试运行
#查找对应设备 找到新增的scsi设备
ubuntu@ubuntu:~/start_test$ lsblk
...
sdb 8:16 0 10G 0 disk
sr0 11:0 1 1.8G 0 rom
root@ubuntu:/home/ubuntu/start_test# gcc scsi_cmd_test.c -o scsi_cmd_test
root@ubuntu:/home/ubuntu/start_test# ./scsi_cmd_test /dev/sdb 32 2
write successfullnew block
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 ...new block
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 ...
2.3 使用mmap映射直接进行写和读测试
测试代码如下:
int main(int argc, char *argv[]) {char *blkname;if (argc != 4) return -1;blkname = argv[1]; //scsi设备 //逻辑地址和物理地址有映射关系 可以研究,逻辑地址一般是512字节int lba = atoi(argv[2]); //逻辑块地址 和物理地址有映射关系 int cnt_of_blocks = atoi(argv[3]); //块个数//这里注意mmap映射时相关参数的设置 普通文件 设备文件的映射参数用MAP_SHARED才写入成功int ret;ret = scsi_mmap_write(blkname, lba, cnt_of_blocks);if (ret) return -1;ret = scsi_mmap_read(blkname, lba, cnt_of_blocks);if (ret) return -1;return 0;
}
运行结果如下:
root@ubuntu:/home/ubuntu/start_test# ./scsi_cmd_test /dev/sdb 128 2new block
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19...new block
0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 19...
2.4:完整测试代码
可以尝试使用指令和mmap交互写和读,查看结果。
//sata scsi nvme几种设备类型 sata串口通信的方式 scsi使用并行交互 nvme借助pcie
//sata 使用控制指令和fsi命令进行控制
//scsi 参考对应的文档中指令 按指令协议构造 sg_io_hdr_t 用ioctl 和scsi设备进行交互
//nvme 设备内核中也提供了对应的结构和函数 控制对应设备 可以设计对应的block数据结构依次控制整个磁盘 扇区 inode节点 block之间的关系/*************************************
struct sg_io_hdr {int interface_id; // 接口标识符(通常设置为 'S')int dxfer_direction; // 数据传输方向,可选值:SG_DXFER_NONE, SG_DXFER_TO_DEV, SG_DXFER_FROM_DEV, SG_DXFER_TO_FROM_DEVunsigned char cmd_len; // 命令长度(单位字节)unsigned char mx_sb_len; // 最大附加数据长度(单位字节)unsigned short iovec_count; // 散列/聚合缓冲区数量unsigned int dxfer_len; // 数据传输长度(单位字节)void *dxferp; // 数据缓冲区指针void *cmdp; // 命令缓冲区指针void *sbp; // 附加数据缓冲区指针unsigned int timeout; // 超时时间(毫秒)unsigned int flags; // 标志位控制参数int pack_id; // 请求包 ID (多个请求可以使用相同的 ID 进行关联)void *usr_ptr; // 用户定义的指针,可用于自定义操作或回调函数等
}void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);addr:指定映射的起始地址,通常设置为 NULL,由操作系统自动选择合适的地址。length:指定映射的长度,以字节为单位。prot:指定映射区域的保护方式(权限)。可以是以下值之一:PROT_NONE:无权限,不能访问。PROT_READ:可读权限。PROT_WRITE:可写权限。PROT_EXEC:可执行权限。 这些值也可以通过按位或运算组合使用。flags:指定了一些选项标志:MAP_SHARED:对映射区域的修改会反映到底层文件中,并且多个进程可以共享该区域(需要有正确设置的文件描述符)。MAP_PRIVATE:对映射区域进行修改不会影响底层文件,并且对该区域的写入操作会产生私有副本(每个进程独立拥有一份副本)。MAP_FIXED:指定映射到的地址必须是准确的,如果不可用则会报错。fd:要映射的文件描述符(仅在映射文件时使用),如果是共享内存或匿名映射,则为 -1。offset:文件中的偏移量,指定从哪个位置开始映射文件(仅在映射文件时使用)。
*************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <scsi/scsi_ioctl.h>
#include <scsi/sg.h>
#include <sys/mman.h>#define BLOCK_SIZE 512
int scsi_cmd16_write(char *blkname, int lba, int cnt_of_blocks);
int scsi_cmd16_read(char *blkname, int lba, int cnt_of_blocks);int scsi_mmap_write(char *blkname, int lba, int cnt_of_blocks);
int scsi_mmap_read(char *blkname, int lba, int cnt_of_blocks);
//了解scsi文档 参考内部其中块控制指令read和write实现对其进行demo测试
int main(int argc, char *argv[]) {char *blkname;if (argc != 4) return -1;blkname = argv[1]; //scsi设备 //逻辑地址和物理地址有映射关系 可以研究,逻辑地址一般是512字节int lba = atoi(argv[2]); //逻辑块地址 和物理地址有映射关系 int cnt_of_blocks = atoi(argv[3]); //块个数// int ret = scsi_cmd16_write(blkname, lba, cnt_of_blocks);// if (ret) return -1; // ret = scsi_cmd16_read(blkname, lba, cnt_of_blocks);// if (ret) return -1;//这里注意mmap映射时相关参数的设置 普通文件 设备文件的映射参数用MAP_SHARED才写入成功int ret;ret = scsi_mmap_write(blkname, lba, cnt_of_blocks);if (ret) return -1;ret = scsi_mmap_read(blkname, lba, cnt_of_blocks);if (ret) return -1;return 0;
}int scsi_cmd16_write(char *blkname, int lba, int cnt_of_blocks)
{int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");}char cmd[16] = {0x8A, 0, 0, 0, 0, 0, (lba >> 24), (lba >> 16), (lba >> 8), lba,(cnt_of_blocks >> 24), (cnt_of_blocks >> 16), (cnt_of_blocks >> 8), cnt_of_blocks,0, 0};char *buffer = (char *)malloc(cnt_of_blocks * BLOCK_SIZE);int i = 0;for (i = 0;i < cnt_of_blocks * BLOCK_SIZE;i ++) {buffer[i] = i % 0x80;}char sense_buffer[32] = {0}; //附加信息 用于存储对应的执行结果sg_io_hdr_t io_hdr;io_hdr.interface_id = 'S';io_hdr.cmdp = cmd;io_hdr.cmd_len = 16;io_hdr.dxfer_direction = SG_DXFER_TO_DEV;io_hdr.dxfer_len = BLOCK_SIZE * cnt_of_blocks;io_hdr.dxferp = buffer;io_hdr.sbp = sense_buffer;io_hdr.mx_sb_len = 32;io_hdr.timeout = 20000;if (ioctl(fd, SG_IO, &io_hdr) < 0) {for (i = 0;i < 32;i ++) {printf("%hx ", sense_buffer[i]);}printf("\n");return -1;}printf("write successfull\n");return 0;
}
//参考对应的协议 设置相关指令进行读取
int scsi_cmd16_read(char *blkname, int lba, int cnt_of_blocks)
{int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");return -1;}// 1024, 0x400char cmd[16] = {0x88, 0, 0, 0, 0, 0, (lba >> 24), (lba >> 16), (lba >> 8), lba,(cnt_of_blocks >> 24), (cnt_of_blocks >> 16), (cnt_of_blocks >> 8), cnt_of_blocks,0, 0};char *buffer = (char *)malloc(cnt_of_blocks * BLOCK_SIZE);char sense_buffer[32] = {0};sg_io_hdr_t io_hdr;io_hdr.interface_id = 'S';io_hdr.cmdp = cmd;io_hdr.cmd_len = 16;io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;io_hdr.dxfer_len = BLOCK_SIZE * cnt_of_blocks;io_hdr.dxferp = buffer;io_hdr.sbp = sense_buffer; //附加数据地址io_hdr.mx_sb_len = 32; //附加数据数据长度io_hdr.timeout = 20000;// syncint i = 0;if (ioctl(fd, SG_IO, &io_hdr) < 0) {for (i = 0;i < 32;i ++) { //附加数据中信息 printf("%hx ", sense_buffer[i]);}printf("\n");return -1;}for (i = 0;i < BLOCK_SIZE * cnt_of_blocks;i ++) {if (i % BLOCK_SIZE == 0) {printf("\n\n new block \n");}printf("%hx ", buffer[i]);}printf("\n");close(fd);
}int scsi_mmap_read(char *blkname, int lba, int cnt_of_blocks) {int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");return -1;}off_t size = lseek(fd, 0, SEEK_END);if (size == -1) {perror("Failed to get disk size");close(fd);exit(EXIT_FAILURE);}void *mmaped = mmap(NULL, BLOCK_SIZE * cnt_of_blocks, PROT_WRITE | PROT_READ, MAP_PRIVATE, fd, lba * BLOCK_SIZE); if (mmaped == MAP_FAILED) {perror("Failed to mmap disk\n");close(fd);return -1;}char *buffer = (char *)mmaped;int i = 0;for (i = 0;i < BLOCK_SIZE * cnt_of_blocks;i ++) {if (i % BLOCK_SIZE == 0) {printf("\n\n new block \n");}printf("%hx ", buffer[i]);}printf("\n");munmap(mmaped, BLOCK_SIZE * cnt_of_blocks);close(fd);return 0;
}//这里的写入是没有用的 需要通过协议写入
int scsi_mmap_write(char *blkname, int lba, int cnt_of_blocks) {int fd;if ((fd = open(blkname, O_RDWR)) < 0) {printf("device file opening failed\n");return -1;}void *mmaped = mmap(NULL, BLOCK_SIZE * cnt_of_blocks, PROT_READ | PROT_WRITE, MAP_SHARED, fd, lba * BLOCK_SIZE); if (mmaped == MAP_FAILED) {perror("Failed to mmap disk\n");close(fd);return -1;}char *buffer = (char *)mmaped;int i = 0;for (i = 0;i < cnt_of_blocks * BLOCK_SIZE;i ++) {buffer[i] = i % 0x80;}if (msync(mmaped, BLOCK_SIZE * cnt_of_blocks, MS_SYNC) == -1) {perror("msync");close(fd);return 1;}munmap(mmaped, BLOCK_SIZE * cnt_of_blocks);close(fd);return 0;
}