已经测试了直接操作nvme磁盘的方式,那么基于可以读写nvme磁盘的功能,如何扩展呢。
通过struct nvme_user_io结构体+ioctl实现对nvme磁盘的读写访问,可以定义结构,对整个磁盘进行管理,以配合业务进行衍生功能。
0:总结
已经能对nvme设备进行基本的读和写了,如何对nvme磁盘进行管理。
根据不同的业务设计,自己定义数据结构对磁盘进行管理,这里定义结构进行管理测试。
可以看看ioctl中参数NVME_IOCTL_SUBMIT_IO 跟踪代码对细节进行了解。
思考:基于当前磁盘的读写,如何实现一个文件系统对该磁盘进行控制。
1:梳理管理方案。(与需求有关,不同需求设计不同,预留磁盘大小不一样,方案不同)
对磁盘的管理,实际上就是对磁盘上的文件进行管理,目录也是一种特殊的文件吧。
对文件进行管理,涉及两个点:
===》存储文件的相关属性,一般用inode节点。
===》文件实际上有自身的内容的,存储在特定的block中。
设计数据结构,实现对整个磁盘进行管理,也就是对文件的inode节点和实际内容存储 管理。
**数据结构的设计如图:**每个扇区,块的大小是512,bitmap每8bit为1byte,占用的块个数为total/(512*8)
在磁盘中细分需要结构的预留空间:
根据业务,总的super结构可以存储在第0个逻辑块处,
然后管理inode的bitmap可以存储在第一个逻辑处,然后预留inode节点内容空间。
最后是实际存储文件内容的逻辑块位置,(存储block的bitmap以及真正block的管理)
inode节点只是存储文件的属性,占用空间相对于文件真正内容就很少了(这里设计的都是存储的超大文件方案),则上面的设计取了super结构块+inode管理块共占用总内存的0.1%,剩下的都分配给存储文件内容的block块。
2:设计对应的数据结构。
#define SECTOR_SIZE 512 //块大小是512 至少分配一个块,以块为单位,不够得空着 计算时用到
//第0个块存储superblock 第一个块存储inode的bitmap 紧跟着存储inode信息 ===》共占用1/1000的磁盘大小
// 接下来1/1000的位置存储block的bitmap以及 对应的起始位置 block中真正存储文件信息
struct zvvfs_superblock {
uint32_t magic; //总的内存大小 21,474,836,480 byte = 20G
uint32_t sb_nr_inodes_total; //总的inode文件节点的个数
uint32_t sb_nr_blocks_total; //总的blocks的个数
uint32_t sb_nr_inode_free; //剩余的inode节点的个数
uint32_t sb_nr_block_free; //剩余block的个数
uint32_t sb_inode_table; //inode节点开始位置 怎么管理inode待定
uint32_t sb_inode_bitmap; //inode对应bitmap块对应的位置
uint32_t sb_block_table; //真正block位置的管理
uint32_t sb_block_bitmap; //block块对应的bitmap位置
};
//文件 或者目录的必要属性信息 因为要预留inode节点空间,要根据结构设计。
struct zvvfs_inode {
uint32_t i_mode;
uint32_t i_uid;
uint32_t i_gid;
uint32_t i_size;
uint32_t i_ctime;
uint32_t i_atime;
uint32_t i_mtime;
uint32_t i_blocks;
};
//基于上面的基础结构,还需要管理文件/目录的额外结构 以及上层方案。
//文件名和对应的inode对应关系 inode中有实际数据存储位置 管理这个结构即所有文件了
struct zvvfs_file {
uint32_t inode;
char filename[128];
};
//目录管理文件 4k 可以自己设计大小 管理目录架构,也可以其他方案。
#define ZVVFS_FILES_PER_BLOCK (4096 / sizeof(struct zvvfs_file))
struct zvvfs_dir_block {
struct zvvfs_file files[ZVVFS_FILES_PER_BLOCK];
};
3:构造super块,提前分配管理磁盘
注意逻辑块的基本大小为512,(和磁盘扇区大小的关系?)
这里已经设计了磁盘中不同位置存储不同的信息,已经设计好了。 比如inode节点的个数限制(1/10000的磁盘空间存储inode节点),比如block块的位置(按业务涉及,这里设计block块单位为2M,设计基于存储大文件)。
//设计super块的内容
struct superblock* zvvfs_write_superblock(int fd, long size)
{
printf(" ====> %ld \n", size);
struct superblock* sb = malloc(sizeof(struct superblock));
if (!sb) {
return NULL;
}
memset(sb, 0, sizeof(struct superblock));
long total_size = size;
uint32_t sector_cnt = total_size/512; //扇区的个数 每个扇区的大小一般512
sb->info.magic = 0xFEEDBABE; //需要类型转换 total_size; // 20*1024*1024*1024; //实际真个磁盘的大小
//inode节点的总个数
sb->info.sb_nr_inodes_total = sector_cnt /10000; //这里总共sector_cnt个扇区 用1/10000用来存储inode节点,可以根据需要自己设计
sb->info.sb_nr_inodes_total *= (512/sizeof(struct zvvfs_inode)); //真正inode节点的个数
//blocks的总个数 因为设计基于大文件的存储,每个块以2M为准
sb->info.sb_nr_blocks_total = sector_cnt - sb->info.sb_nr_inodes_total; //剩下的节点存储blocks块,这里设计block每个块大小为2M 2*1024*1024
sb->info.sb_nr_blocks_total = sb->info.sb_nr_blocks_total/(2*1024*1024/512); //2M一个页
//inode bitmap的开始位置 table的位置从bitmap开始到预留节点个数计算
sb->info.sb_inode_bitmap = 1; //节点的第一个块存储inode的bitmap
//bitmap的块的位置 是inode节点的个数除以512(块的大小) 再除以8 (1个byte有8bit) 先除以8算出bitmap占用的字节个数 再除以512块求得块的个数
sb->info.sb_inode_table = sb->info.sb_inode_bitmap + (sb->info.sb_nr_inodes_total/(512*8))+1;
sb->info.sb_block_bitmap = sector_cnt / 10000 + 1; //前面1/10000的位置+1
sb->info.sb_block_table = sb->info.sb_block_bitmap + (sb->info.sb_nr_blocks_total/(512*8) + 1);
//空闲的个数
sb->info.sb_nr_inode_free = sb->info.sb_nr_inodes_total;
sb->info.sb_nr_block_free = sb->info.sb_nr_blocks_total;
printf("magic = %u \n"
"sb_nr_inodes_total=%d\n"
"sb_nr_blocks_total=%d\n"
"sb_inode_bitmap=%d\n"
"sb_inode_table=%d\n"
"sb_block_bitmap=%d\n"
"sb_block_table=%d\n"
"sb_nr_inode_free=%d\n"
"sb_nr_block_free=%d\n\n\n",
sb->info.magic ,
sb->info.sb_nr_inodes_total, sb->info.sb_nr_blocks_total,
sb->info.sb_inode_bitmap, sb->info.sb_inode_table,
sb->info.sb_block_bitmap, sb->info.sb_block_table,
sb->info.sb_nr_inode_free, sb->info.sb_nr_block_free);
if (0 != nvme_write(fd, 0, sb, sizeof(struct superblock))) {
printf("zvvfs_write_superblock failed\n");
free(sb);
return NULL;
}
return sb;
}
4:测试代码。
//实际上就是构造整个数据结构 按方式对磁盘进行管理。
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <linux/fs.h>
#include <linux/nvme_ioctl.h>
#define SECTOR_SIZE 512 //块大小是512 这里可以思考块和页的关系
//第0个块存储superblock 第一个块存储inode的bitmap 紧跟着存储inode信息 ===》共占用1/1000的磁盘大小
// 接下来1/1000的位置存储block的bitmap以及 对应的起始位置 block中真正存储文件信息
struct zvvfs_superblock {
uint32_t magic; //总的内存大小 21,474,836,480 byte = 20G
uint32_t sb_nr_inodes_total; //总的inode文件节点的个数
uint32_t sb_nr_blocks_total; //总的blocks的个数
uint32_t sb_nr_inode_free; //剩余的inode节点的个数
uint32_t sb_nr_block_free; //剩余block的个数
uint32_t sb_inode_table; //inode节点开始位置 怎么管理inode待定
uint32_t sb_inode_bitmap; //inode对应bitmap块对应的位置
uint32_t sb_block_table; //真正block位置的管理
uint32_t sb_block_bitmap; //block块对应的bitmap位置
};
//文件 或者目录的必要属性信息
struct zvvfs_inode {
uint32_t i_mode;
uint32_t i_uid;
uint32_t i_gid;
uint32_t i_size;
uint32_t i_ctime;
uint32_t i_atime;
uint32_t i_mtime;
uint32_t i_blocks;
};
//文件名和对应的inode对应关系 inode中有实际数据存储位置 管理这个结构即所有文件了
struct zvvfs_file {
uint32_t inode;
char filename[128];
};
//目录管理文件 4k 可以自己设计大小 管理目录架构,也可以其他方案。
#define ZVVFS_FILES_PER_BLOCK (4096 / sizeof(struct zvvfs_file))
struct zvvfs_dir_block {
struct zvvfs_file files[ZVVFS_FILES_PER_BLOCK];
};
//第一个块存储整个磁盘的管理信息 总的块个数 支持总的块个数 总的inode个数
struct superblock {
struct zvvfs_superblock info;
char padding[SECTOR_SIZE - sizeof(struct zvvfs_superblock)]; //补齐一个块用
};
int nvme_write(int fd, __u64 lba, void *blocks, int length);
struct superblock* zvvfs_write_superblock(int fd, long size);
int main(int argc, char *argv[]) {
if (argc < 2) {
return -1;
}
const char *filename = argv[1];
int fd = open(filename, O_RDWR);
if (fd < 0) {
perror("open\n");
return -1;
}
struct stat stat_buf;
int ret = fstat(fd, &stat_buf);
if (ret) {
perror("fstat\n");
return -1;
}
/******************************
S_IFSOCK:套接字(socket)
S_IFLNK:符号链接(symbolic link)
S_IFREG:普通文件(regular file)
S_IFBLK:块设备文件(block device)
S_IFDIR:目录(directory)
S_IFCHR:字符设备文件(character device)
S_IFIFO:命名管道(FIFO)
******************************/
if ((stat_buf.st_mode & S_IFMT) != S_IFBLK) { //是块设备文件
return -1;
}
//获取关联块设备的大小 字节为单位
long blk_size = 0;
if (0 != ioctl(fd, BLKGETSIZE64, &blk_size)) {
perror("ioctl\n");
return -1;
}
stat_buf.st_size = blk_size;
printf("size: %ld\n", stat_buf.st_size);
printf("sector: %ld\n", stat_buf.st_size / SECTOR_SIZE);
//根据相关参数设置super块
struct superblock* sb = zvvfs_write_superblock(fd, blk_size);
//接下来就是inode节点的bitmap和对应内容的写 以及文件实际内容写入block中
//写一个文件时 先构造inode节点 写入信息和bitmap更新 实际内容时block写入,也是更新bitmap对应位置。
//另外就是管理目录下文件 目录架构和inode之间的关系 目录层级关系
free(sb);
close(fd);
return 0;
}
//设计super块的内容
struct superblock* zvvfs_write_superblock(int fd, long size)
{
printf(" ====> %ld \n", size);
struct superblock* sb = malloc(sizeof(struct superblock));
if (!sb) {
return NULL;
}
memset(sb, 0, sizeof(struct superblock));
long total_size = size;
uint32_t sector_cnt = total_size/512; //扇区的个数 每个扇区的大小一般512
sb->info.magic = 0xFEEDBABE; //需要类型转换 total_size; // 20*1024*1024*1024; //实际真个磁盘的大小
//inode节点的总个数
sb->info.sb_nr_inodes_total = sector_cnt /10000; //这里总共sector_cnt个扇区 用1/10000用来存储inode节点,可以根据需要自己设计
sb->info.sb_nr_inodes_total *= (512/sizeof(struct zvvfs_inode)); //真正inode节点的个数
//blocks的总个数 因为设计基于大文件的存储,每个块以2M为准
sb->info.sb_nr_blocks_total = sector_cnt - sb->info.sb_nr_inodes_total; //剩下的节点存储blocks块,这里设计block每个块大小为2M 2*1024*1024
sb->info.sb_nr_blocks_total = sb->info.sb_nr_blocks_total/(2*1024*1024/512); //2M一个页
//inode bitmap的开始位置 table的位置从bitmap开始到预留节点个数计算
sb->info.sb_inode_bitmap = 1; //节点的第一个块存储inode的bitmap
//bitmap的块的位置 是inode节点的个数除以512(块的大小) 再除以8 (1个byte有8bit) 先除以8算出bitmap占用的字节个数 再除以512块求得块的个数
sb->info.sb_inode_table = sb->info.sb_inode_bitmap + (sb->info.sb_nr_inodes_total/(512*8))+1;
sb->info.sb_block_bitmap = sector_cnt / 10000 + 1; //前面1/10000的位置+1
sb->info.sb_block_table = sb->info.sb_block_bitmap + (sb->info.sb_nr_blocks_total/(512*8) + 1);
//空闲的个数
sb->info.sb_nr_inode_free = sb->info.sb_nr_inodes_total;
sb->info.sb_nr_block_free = sb->info.sb_nr_blocks_total;
printf("magic = %u \n"
"sb_nr_inodes_total=%d\n"
"sb_nr_blocks_total=%d\n"
"sb_inode_bitmap=%d\n"
"sb_inode_table=%d\n"
"sb_block_bitmap=%d\n"
"sb_block_table=%d\n"
"sb_nr_inode_free=%d\n"
"sb_nr_block_free=%d\n\n\n",
sb->info.magic ,
sb->info.sb_nr_inodes_total, sb->info.sb_nr_blocks_total,
sb->info.sb_inode_bitmap, sb->info.sb_inode_table,
sb->info.sb_block_bitmap, sb->info.sb_block_table,
sb->info.sb_nr_inode_free, sb->info.sb_nr_block_free);
if (0 != nvme_write(fd, 0, sb, sizeof(struct superblock))) {
printf("zvvfs_write_superblock failed\n");
free(sb);
return NULL;
}
return sb;
}
//在对应块的位置 写入信息
int nvme_write(int fd, __u64 lba, void *blocks, int length) {
struct nvme_user_io io;
memset(&io, 0, sizeof(io));
io.addr = (__u64)blocks;
io.slba = lba;
io.nblocks = (length-1) / SECTOR_SIZE + 1; //(15 + 1) * 256;
io.opcode = 1;
if (-1 == ioctl(fd, NVME_IOCTL_SUBMIT_IO, &io)) { //write
perror("ioctl write");
return -1;
}
//printf("write lba: %lld, nblocks: %d\n", lba, length / SECTOR_SIZE + 1);
return 0;
}
5:测试结果
root@ubuntu:/home/ubuntu/storage/test_nvme# ./nvme_user_superblock /dev/nvme0n1
size: 21474836480
sector: 41943040
====> 21474836480
====> 21474836480
magic = 4276992702
sb_nr_inodes_total=67104
sb_nr_blocks_total=10223
sb_inode_bitmap=1
sb_inode_table=18
sb_block_bitmap=4195
sb_block_table=4198
sb_nr_inode_free=67104
sb_nr_block_free=10223
基于结构设计,创建一个文件,实际上就是给文件名和inode节点以及实际存储blobk位置信息进行关联,以及通过上述结构管理整个磁盘。