创建虚拟机时,可以选择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设备的交互。
c
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位固定长度读指令如下:
对应协议构造与触发指令如下:
c
//参考对应的协议 设置相关指令进行读取 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;
// sync
int 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位固定长度对应的协议
对应的代码模块:
c
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函数。
c
//了解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 测试运行
bash
#查找对应设备 找到新增的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 successfull
new 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映射直接进行写和读测试
测试代码如下:
c
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;
}
运行结果如下:
bash
root@ubuntu:/home/ubuntu/start_test# ./scsi_cmd_test /dev/sdb 128 2
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...
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交互写和读,查看结果。
c
//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_DEV
unsigned 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, 0x400
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;
// sync
int 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;
}