存储课程学习笔记1_访问scsi磁盘读写测试(struct sg_io_hdr,ioctl,mmap)

创建虚拟机时,可以选择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;
}
相关推荐
niu_881 年前
【无标题】
usb·msc·scsi