存储课程学习笔记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设备的交互。

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;

	// 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位固定长度对应的协议

在这里插入图片描述

对应的代码模块:

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 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映射直接进行写和读测试

测试代码如下:

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 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交互写和读,查看结果。

//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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/875450.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode第414周赛第二题:3281. 范围内整数的最大得分

一&#xff1a;题目&#xff1a; 给你一个整数数组 start 和一个整数 d&#xff0c;代表 n 个区间 [start[i], start[i] d]。 你需要选择 n 个整数&#xff0c;其中第 i 个整数必须属于第 i 个区间。所选整数的 得分 定义为所选整数两两之间的 最小 绝对差。 返回所选整数的…

利他决策的神经机制:脑电功能连接网络分析

摘要 利他主义是一种复杂的亲社会行为&#xff0c;具有多样性的动机和显著的个体差异。研究利他主义的神经机制对于识别行为中的亲社会和反社会倾向的客观标志至关重要。本研究旨在通过网络方法分析基于EEG的功能连接模式来深入探讨利他主义的机制。为了实验性地引发利他决策情…

eclipse配置maven

eclipse配置maven 启动 Eclipse&#xff0c;转到 Window > Preferences 在左侧导航栏中&#xff0c;展开 Maven 节点。 在 User Settings 下&#xff0c;单击 Add。 浏览到 Maven 安装目录中 conf/settings.xml 文件。 在 Global Settings 下&#xff0c;单击 Add。 浏览到…

动态规划算法---04.斐波那契数列模型_解码方法_C++

题目链接&#xff1a;91. 解码方法 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/decode-ways/description/ 一、题目解析 题目&#xff1a; 题目大意&#xff1a;从题目中我们可以知道&#xff0c;解码就是在字符串s中由‘1’到‘26’的字符可以转化…

架构模式:MVC

引言 MVC&#xff0c;即 Model&#xff08;模型&#xff09;-View&#xff08;视图&#xff09;-Controller&#xff08;控制器&#xff09;&#xff0c;是广泛应用于交互式系统中的典型架构模式&#xff0c;尤其在 GUI 和 Web 应用中。 MVC 的概念源自 GOF&#xff08;Gang …

Web安全之GroovyShell讲解:错误与正确示范,安全问题与解决方案

第一章、引言 Groovy 是一门基于 Java 虚拟机&#xff08;JVM&#xff09;的动态语言&#xff0c;而 GroovyShell 是 Groovy 提供的一个灵活强大的脚本执行工具。通过 GroovyShell&#xff0c;开发者可以在运行时动态执行 Groovy 脚本&#xff0c;它的灵活性非常适合那些需要动…

多层建筑能源参数化模型和城市冠层模型的区别

多层建筑能源参数化&#xff08;Multi-layer Building Energy Parameterization, BEP&#xff09;模型和城市冠层模型&#xff08;Urban Canopy Model, UCM&#xff09;都是用于模拟城市环境中能量交换和微气候的数值模型&#xff0c;但它们的侧重点和应用场景有所不同。以下是…

MongoDB事务机制

事务机制 1.事务概念 在对数据的操作的过程中&#xff0c;涉及到一连串的操作&#xff0c;这些操作如果失败&#xff0c;会导致我们的数据部分变化了&#xff0c;部分没变化。这个过程就好比如你去吃早餐&#xff0c;你点完餐了&#xff0c;并且吃完早餐了&#xff0c;没付钱你…

【文件包含】——日志文件注入

改变的确很难&#xff0c;但结果值得冒险 本文主要根据做题内容的总结&#xff0c;如有错误之处&#xff0c;还请各位师傅指正 一.伪协议的失效 当我们做到关于文件包含的题目时&#xff0c;常用思路其实就是使用伪协议&#xff08;php:filter,data,inpput等等&#xff09;执行…

职业技能大赛背景下的移动互联网应用软件开发(Android)实训室建设方案

一、建设背景 随着科技的持续进步&#xff0c;移动设备已成为人们日常生活中不可或缺的一部分。据相关数据&#xff0c;移动互联网的使用率在近年来显著上升。在这样的背景下&#xff0c;移动互联技术不仅推动了科技的发展&#xff0c;也渗透到了智能家居、车联网、工业自动化…

TESSY创建需要手写桩的测试用例

如果需要让桩函数有额外的功能&#xff0c;如&#xff1a;传参检测、局部数据处理、多传参检测、函数实现变更等&#xff0c;可以进行手写桩。 我们以tessy5.1 IDE为例&#xff0c;给大家展示编写一个需要手写桩的测试用例过程。 1、前期的准备工作 可以参考以下文章&#xff1…

Qt常用控件——QTextEdit

文章目录 QTextEdit核心属性和信号同步显示示例信号示例 QTextEdit核心属性和信号 QTextEdit表示多行输入框&#xff0c;是一个富文本和markdown编辑器&#xff0c;并且能在内存超出编辑框范围时自动提供滚动条。 QPlainTexEdit是纯文本&#xff0c;QTextEdit不仅表示纯文本&a…

记得忘记密码情况下如何退出苹果Apple ID

在日常使用苹果手机时&#xff0c;我们可能会遇到需要退出Apple ID的情况&#xff0c;比如更换手机、不再使用某些服务或出于安全考虑等。下面&#xff0c;我们就来详细介绍一下苹果手机如何退出Apple ID。 情况一&#xff1a;记得Apple ID密码 若是记得Apple ID密码&#xff…

Tomcat服务详解

一、部署Tomcat服务器 JDK安装官方网址&#xff1a;https://www.oracle.com/cn/java Tomcat安装官方网址&#xff1a;Apache Tomcat - Welcome! 安装JDK 1.获取安装包 wget https://download.oracle.com/otn/java/jdk/8u411-b09/43d62d619be4e416215729597d70b8ac/jdk-8u41…

透明任务栏怎么设置?Windows电脑任务栏透明效果(详尽指南)

电脑任务栏可以自定义调整透明度&#xff0c;在Windows10和Windows11系统中&#xff0c;任务栏透明是默认不透明的。如果想要设置任务栏透明度&#xff0c;那么推荐以下方法实现&#xff0c;简单三个步骤即可实现&#xff0c;一起来看看吧&#xff01; 第一步、选择合适的任务栏…

基于鸿蒙API10的RTSP播放器(五:拖动底部视频滑轨实现跳转)

拖动前播放位置&#xff1a; 拖动后播放位置&#xff1a; 在Slider组件中&#xff0c;添加onChange方法进行监听&#xff0c;当视频轨道拖放结束时&#xff0c;触发this.seekTo()函数&#xff0c;其中seekTo函数需要传递一个视频已播放时长作为参数 Slider({ value: this.p…

【iOS】push和present的区别

【iOS】push和present的区别 文章目录 【iOS】push和present的区别前言pushpop presentdismiss简单小demo来展示dismiss和presentdismiss多级 push和present的区别区别相同点 前言 在iOS开发中&#xff0c;我们经常性的会用到界面的一个切换的问题&#xff0c;这里我们需要理清…

【专题】2024年8月医药行业报告合集汇总PDF分享(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p37621 在科技飞速发展的当今时代&#xff0c;医药行业作为关乎人类生命健康的重要领域&#xff0c;正处于前所未有的变革浪潮之中。数智医疗服务的崛起&#xff0c;为医疗模式带来了全新的转变&#xff0c;开启了医疗服务的新时代。…

SQL超时的常见原因和解决思路

目录 1. SQL超时的常见原因2. 解决思路2.1. 优化SQL查询2.2. 处理锁竞争2.3. 网络和连接配置2.4. 资源监控与调整 3. 预防措施4. 示例&#xff1a;设置SQL超时5. 总结 SQL超时问题常常会影响应用程序的性能和用户体验&#xff0c;了解和处理SQL超时的思路非常重要。以下是一些常…

【Oracle篇】全面理解优化器和SQL语句的解析步骤(含执行计划的详细分析和四种查看方式)(第二篇,总共七篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…