裸盘是如何能达到我们日常操作目录那样,按目录依次访问文件等,实际上就是基于裸盘上,用文件系统进行控制。

0:总结。

0:mount是入口,一个裸盘先赋予文件系统,然后mount后才可以用。
1:内核提供了插入文件系统的方法,register_filesystem函数和对应的struct file_system_type 结构体。

2:插入内核模块的demo,基于插入内核模块,实现插入一个新的文件系统。

3:基于文件系统,实现对应的指令,mount是入口(mount_nodev和mount_bdev两种mount的区别),才有其他后续接口:关注struct super_block , struct inode_operations,struct file_operations。

1:首先要了解文件系统,以及挂载。

新增一个裸盘后,首先需要给裸盘赋予文件系统,然后就可以挂载后使用了。

#可以看到 sdb设备是新增的空闲设备 10G  虚拟机上的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# mkfs.ext4 /dev/sdb 
mke2fs 1.46.5 (30-Dec-2021)
Creating filesystem with 2621440 4k blocks and 655360 inodes
Filesystem UUID: 96099ecd-4c7c-4006-b843-7faf4ceb07c4
Superblock backups stored on blocks: 
	32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632

Allocating group tables: done                            
Writing inode tables: done                            
Creating journal (16384 blocks): done
Writing superblocks and filesystem accounting information: done 

#对该磁盘进行挂载 挂载后可通过挂载目录对磁盘进行访问
root@ubuntu:/home/ubuntu/start_test# mkdir mnt
root@ubuntu:/home/ubuntu/start_test# mount -t ext4 /dev/sdb /home/ubuntu/start_test/mnt/

#可以看到 已经挂载成功 
root@ubuntu:/home/ubuntu/start_test/mnt# ls
lost+found
root@ubuntu:/home/ubuntu/start_test# df -h
...
/dev/sdb                           9.8G   28K  9.3G   1% /home/ubuntu/start_test/mnt

#可以借助目录对磁盘进行访问了。
root@ubuntu:/home/ubuntu/start_test/mnt# mkdir 1
root@ubuntu:/home/ubuntu/start_test/mnt# umount /dev/sdb 
umount: /home/ubuntu/start_test/mnt: target is busy.
root@ubuntu:/home/ubuntu/start_test/mnt# cd ../
root@ubuntu:/home/ubuntu/start_test# umount /dev/sdb 

2:插入内核模块demo

本节目的是借助内核提供的文件系统接口,插入一个文件系统内核模块。

2.1 插入一个内核模块,观察日志。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h> //printk

static int __init hello_init(void) {
    printk(KERN_INFO "Hello, World!\n");
    return 0;
}

static void __exit hello_exit(void) {
    printk(KERN_INFO "Goodbye, World!\n");
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hello World test");
MODULE_DESCRIPTION("A simple example kernel module");

2.2 借助make对其进行编译,生成ko文件。

obj-m += hello_module.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
all:
        make -C $(KERNELDIR) M=$(PWD) modules
clean:
        make -C $(KERNELDIR) M=$(PWD) clean

2.3 编译该模块,加入内核和移出内核,观察日志。

#插入内核模块 移除内核模块
root@ubuntu:/home/ubuntu/storage/module_test# insmod hello_module.ko 
root@ubuntu:/home/ubuntu/storage/module_test# rmmod hello_module.ko 
#使用dmesg查看系统日志  (-x这里打印前面的级别)
root@ubuntu:/etc# dmesg -x |tail
kern  :info  : [526406.354334] Hello, World!
kern  :err   : [526413.567482] Goodbye, World!

#带系统时间进行显示   可以了解dmesg相关指令参数
root@ubuntu:/etc# dmesg -x -T|tail
kern  :info  : [Fri May 17 04:53:13 2024] Hello, World!
kern  :err   : [Fri May 17 04:53:20 2024] Goodbye, World!

3:注册文件系统(通过内核模块,给特定目录加入文件系统)

借助mount -t 文件系统 (插入文件系统内核模块后,借助mount指令实现挂载,可以测试文件系统)

内核提供了两种挂载的方式。

mount_nodev 更适合用于创建虚拟文件系统, 如创建的一个文件夹。

mount_bdev需要的是绑定对应的块设备(如硬盘,硬盘上的 ext4、xfs 等常见文件系统。)

3.1 注册文件系统,确定mount的入口正确( 通过struct file_system_type结构体,register_filesystem 注册文件系统)

主要了解register_filesystem 函数接口以及struct file_system_type结构体 中相关函数,以及操作对应函数入口。

这里仅仅观察执行mount时,能否正常日志记录。

3.1.1 代码demo,借助makefiel编译
//这里如果mount时没有实现该功能 测试后,会导致模块无法卸载
#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h>  //内核下:usr/src/linux-headers-5.15.0-107/include/linux/

//挂载虚拟文件时用  对应file_system_type中name
// mount -t filesys_test nodev ./mnt/
// mount_nodev();

//挂载设备文件时用
// mount -t filesys_test /dev/nvme ./mnt/
// mount_bdev();
//借助mount -t 指令实现挂载时触发
struct dentry *filesys_mount(struct file_system_type *fstype, int flags,
		       const char *dev_name, void *data) {
{
	printk("filesys mount ...\n");
	return NULL;  //这里如果只是NULL 导致该模块无法卸载
}          
	
void filesys_kill_superblock (struct super_block *)  
{
	printk("filesys des ... \n");
}

//定义对应的文件系统结构体  对应内核模块的linux/fs.h 
//文件系统看看 linux/fs.h  文件下的相关内容
struct file_system_type file_sys_st  = {
	.owner = THIS_MODULE,
	.name = "filesys_test",
	// .fs_flags =  //看一下默认多少
	// .init_fs_context = 必要的初始化相关函数
	// .parameters  未定义相关文件系统参数
	.mount =  filesys_mount, 			//挂载时对应执行的函数指针
	.kill_sb = filesys_kill_superblock	//销毁超级块时触发
};

//一定要注意类型对应  参数有个void  function declaration isn’t a prototype [-Werror=strict-prototypes
static int __init filesys_init(void)
{
	//注册文件系统
	int ret = register_filesystem(&file_sys_st);
	if(ret)
	{
		printk("init module: register filesys error. [%d]. \n", ret);
		return ret;
	}
	printk("init module: register filesys success. [%d]. \n", ret);
	return ret;
}

static void __exit filesys_exit(void)
{
	//对应上文模块初始化
	unregister_filesystem(&file_sys_st);
	printk("destory module: unregister filesys.\n");
}

module_init(filesys_init);
module_exit(filesys_exit);
MODULE_LICENSE("GPL");


–需要了解一下fs.h中结构体和相关函数

struct file_system_type {
	const char *name;
	int fs_flags; 					//文件系统标志位
		#define FS_REQUIRES_DEV		1     //该文件系统需要一个实际设备作为其挂载点。
		#define FS_BINARY_MOUNTDATA	2
		#define FS_HAS_SUBTYPE		4
		#define FS_USERNS_MOUNT		8			// Can be mounted by userns root 
		#define FS_DISALLOW_NOTIFY_PERM	16		// Disable fanotify permission events 
		#define FS_ALLOW_IDMAP         32      	// FS has been updated to handle vfs idmappings. 
		#define FS_THP_SUPPORT		8192		// Remove once all fs converted 
		#define FS_RENAME_DOES_D_MOVE	32768	// FS will handle d_move() during rename() internally. 
	int (*init_fs_context)(struct fs_context *);    //挂载前的动作
	const struct fs_parameter_spec *parameters;    //文件系统参数的结构体
	struct dentry *(*mount) (struct file_system_type *, int,  const char *, void *);          // 挂载函数指针
	void (*kill_sb) (struct super_block *);   // 销毁超级块函数指针
	struct module *owner;         		// 所属模块指针(可选)
	struct file_system_type * next;		// 链表下一个元素指针(可选)
	struct hlist_head fs_supers;

	struct lock_class_key s_lock_key;
	struct lock_class_key s_umount_key;
	struct lock_class_key s_vfs_rename_key;
	struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];

	struct lock_class_key i_lock_key;
	struct lock_class_key i_mutex_key;
	struct lock_class_key invalidate_lock_key;
	struct lock_class_key i_mutex_dir_key;
};

extern int register_filesystem(struct file_system_type *);
extern int unregister_filesystem(struct file_system_type *);
3.1.2 对应执行和测试(导致该内核模块无法卸载)
root@ubuntu:/home/ubuntu/storage/filesys# insmod filesys.ko 
root@ubuntu:/home/ubuntu/storage/filesys# mount -t filesys_test nodev ./mnt/
Killed
# 发现mount后 该内核模块无法移除,对应的dmesg中有相关错误日志
root@ubuntu:/home/ubuntu/storage/filesys# rmmod filesys.ko 
rmmod: ERROR: Module filesys is in use
root@ubuntu:/home/ubuntu/storage/filesys# lsmod|grep file
filesys                16384  1

root@ubuntu:/# dmesg
[537582.603611] init module: register filesys success. [0]. 
[537589.942759] filesys mount ...
[537589.943232] BUG: kernel NULL pointer dereference, address: 0000000000000068
[537589.943612] #PF: supervisor read access in kernel mode
[537589.943997] #PF: error_code(0x0000) - not-present page
[537589.944405] PGD 0 P4D 0 
[537589.944774] Oops: 0000 [#1] SMP NOPTI

3.2 实现文件系统对应mount模块,使该模块正常(mount_nodev和mount_bdev差别)

3.2.1 必要代码模块
//个人理解:mount是文件系统挂载的第一步,比如把这个目录当做根目录,基于该目录下去创建文件系统,使支持相关文件/文件夹操作,(禁止外部原文件系统访问)。
//mount时 需要初始化相关的超级块 需要对根目录节点进行处理以及相关初始化
const struct inode_operations filesys_inode_ops = 
{};

const struct file_operations filesys_file_ops = 
{};

//回调函数 对参数做初始化
//struct super_block 超级块 存储了文件系统磁盘大小,系统类型,inode表等
static int filesys_super_block(struct super_block * sb, void *data, int flag)
{
	struct inode *root_inode;

	printk("filesys mount super_block ...\n");
	//创建根目录节点 
	root_inode = new_inode(sb); //创建一个inode节点,从sb中分配

	//初始化第一个inode的节点信息
	// void inode_init_owner(struct user_namespace *mnt_userns, struct inode *inode,
	// 	      const struct inode *dir, umode_t mode);
	// init_user_ns 命名空间  &nop_mnt_idmap
	inode_init_owner(&init_user_ns, root_inode, NULL, S_IFDIR); //S_IFDIR表示目录类型 sys/stat.h中

	root_inode->i_sb = sb;
	root_inode->i_op = &filesys_inode_ops;  //inode操作相关函数
	root_inode->i_fop = &filesys_file_ops;  //文件操作相关函数
	//fs.h中封装的函数
	root_inode->i_atime = root_inode->i_mtime = root_inode->i_ctime = current_time(root_inode);

	//根目录对应的结构体指针 
	//d_make_root函数用于在mount一个文件系统的时候,为文件系统的root inode创建对应的dentry。
	//dentry 是表示目录项的数据结构  该目录需要的必要信息
	sb->s_root = d_make_root(root_inode);
	return 0;
}

//挂载虚拟文件时用  对应file_system_type中name
// mount -t filesys_test nodev ./mnt/
// mount_nodev();

//挂载设备文件时用
// mount -t filesys_test /dev/nvme ./mnt/
// mount_bdev();
//借助mount -t 指令实现挂载时触发
struct dentry *filesys_mount(struct file_system_type *fstype, int flags,
		       const char *dev_name, void *data) 
{
	struct dentry* re; //为了解决告警放最前面
	printk("filesys mount ...\n");

	//mount时实际上需要关注目标文件夹的一些特性  不同的文件类型不同的接口
	re =  mount_nodev(fstype, flags, data, filesys_super_block);
	return re;  //这里如果只是NULL 导致该模块无法卸载
}          

void filesys_kill_superblock(struct super_block * sb)  
{
	printk("filesys des ... \n");
	//这里卸载需要需求清理
	kill_litter_super(sb); // 调用默认的卸载函数
	return;
}
3.2.2 执行结果,能正常umount以及移除内核模块(暂时该目录下创建删除等各种指令依然无法使用):
root@ubuntu:/home/ubuntu/storage/filesys# insmod filesys.ko 
root@ubuntu:/home/ubuntu/storage/filesys# mount -t filesys_test nodev ./mnt/
root@ubuntu:/home/ubuntu/storage/filesys# umount ./mnt/
root@ubuntu:/home/ubuntu/storage/filesys# rmmod filesys.ko 

root@ubuntu:/home/ubuntu/storage/filesys# dmesg --clear
root@ubuntu:/home/ubuntu/storage/filesys# dmesg
root@ubuntu:/home/ubuntu/storage/filesys# dmesg
[ 2065.265739] init module: register filesys success. [0]. 
[ 2071.640164] filesys mount ...
[ 2071.640215] filesys mount super_block ...
[ 2079.303963] filesys des ... 
[ 2082.553245] destory module: unregister filesys.

3.3 基于mount已经实现,实现其他基本指令(ls,cd,mkdir等)

构造对应的结构体 struct inode_operations 和struct file_operations 实现内部接口。

3.3.1 代码demo, 相关inode_operations 对应的接口
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/fs.h>  //内核下:usr/src/linux-headers-5.15.0-107/include/linux/

//考虑已有的结构体和必要内容结构体 文件夹信息和文件内容如何存储和识别
//超级块==》inode首节点根目录==》inode节点目录/文件识别必要信息
#define MAX_FILE_NUM 1024
#define MAX_FILENAME_LEN 64

typedef struct file_context_st{
	char *buffer;  //文件内容的存储 需要研究文件数据块的存储逻辑 小文件 大文件,,,
	int buf_len;
	char filename[MAX_FILENAME_LEN];
}FILE_CONTEXT_ST;

//inode节点中i_ino唯一标识 可以与文件或者目录结构做对应关系 
//inode中可以指向该文件的写入内容地址
FILE_CONTEXT_ST g_file[MAX_FILE_NUM] = {0};

int get_one_file_idx(void) {
	int i = 0;
	for (i = 0;i < MAX_FILE_NUM;i ++) {
		if (g_file[i].buffer == NULL && g_file[i].buf_len == 0) {
			return i;
		}
	}

	return MAX_FILE_NUM;
}


int filesys_inode_create(struct user_namespace *uns, struct inode *dir,struct dentry *dentry,
		       umode_t mode, bool excl);
struct dentry *filesys_inode_lookup (struct inode *inode,struct dentry *dentry, unsigned int flags);
int filesys_inode_mkdir(struct user_namespace *uns, struct inode *dir, struct dentry *dentry, umode_t mode);
int filesys_inode_rmdir(struct inode *inode, struct dentry *dentry);
int filesys_inode_unlink(struct inode *inode, struct dentry *dentry);

int filesys_file_open (struct inode *inode, struct file *filp);
int filesys_file_release(struct inode *inode, struct file *filp);
ssize_t filesys_file_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset);
ssize_t filesys_file_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset);
int filesys_file_iterate (struct file *filp, struct dir_context *ctx);


const struct inode_operations filesys_inode_ops = 
{
	//创建文件 查看文件  移除文件  返回文件信息  查看属性  链接指令等
	.create = filesys_inode_create,
	.lookup = filesys_inode_lookup,
	.mkdir = filesys_inode_mkdir,
	.rmdir = filesys_inode_rmdir,
	.unlink = filesys_inode_unlink,
};

const struct file_operations filesys_file_ops = 
{
	.open = filesys_file_open,
	.release = filesys_file_release,
	.read = filesys_file_read,
	.write = filesys_file_write,
	.iterate = filesys_file_iterate,
};

//这里创建文件 需要考虑文件名和文件内容 文件夹的创建对应mkdir 
//需要考虑逐层创建文件和文件夹的结构 struct dentry 
int filesys_inode_create(struct user_namespace *uns, struct inode *dir,struct dentry *dentry,
		       umode_t mode, bool excl) 
{
	//编译时有校验  初始化放在前面
	struct inode *inode;
	struct super_block *sb = dir->i_sb;
	int idx = 0;

	//从参数中获取到文件名 
	printk("filename: %s\n", dentry->d_name.name);

	//实际上就是创建一个节点,保存必要的信息 
	inode = new_inode(sb);  //这里要考虑父节点  同级节点 
	inode->i_sb = sb;
	inode->i_op = &filesys_inode_ops; //这里文件节点 应该对相关操作做限制吧?
	inode->i_fop = &filesys_file_ops;

	//这里涉及文件名  以及文件预留内存等必要信息  
	idx = get_one_file_idx();
	if(idx >= MAX_FILE_NUM)
	{
		return -EINVAL;
	}
	g_file[idx].buffer = kmalloc(1024, GFP_KERNEL);
	g_file[idx].buf_len = 0;
	strncpy(g_file[idx].filename, dentry->d_name.name, MAX_FILENAME_LEN);
	
	inode->i_ino = idx; //唯一标识 可以找到关联内容
	inode->i_private = &g_file[idx]; //私有空间 存储必要信息  指针

	//使用传递参数对inode做必要的初始化
	inode_init_owner(uns, inode, dir, mode);
	//创建dentry结构 做相关指向关联 加入目录的功能
	d_add(dentry, inode); //
	return 0;
}

struct dentry *filesys_inode_lookup (struct inode *inode,struct dentry *dentry, unsigned int flags) {

	printk("filesys_inode_lookup\n");

	return NULL;

}

//mkdir 文件夹的创建 
int filesys_inode_mkdir(struct user_namespace *uns, struct inode *dir, struct dentry *dentry, umode_t mode) {

	printk("filesys_inode_mkdir\n");

	return 0;
}

// rmdir dir
int filesys_inode_rmdir(struct inode *inode, struct dentry *dentry) {

	printk("filesys_inode_rmdir\n");

	return 0;
}

// rm file
int filesys_inode_unlink(struct inode *inode, struct dentry *dentry) {

	printk("filesys_inode_unlink\n");
	return 0;
}	

//ls 调用 open iterate release
//touch 调用 inode_lookup open release
int filesys_file_open (struct inode *inode, struct file *filp)
{
	printk("filesys_file_open\n");
	return 0;
}

int filesys_file_release(struct inode *inode, struct file *filp)
{
	printk("filesys_file_release\n");
	return 0;
}

ssize_t filesys_file_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset)
{
	size_t len = length > 1024 ? 1024 : length;
	FILE_CONTEXT_ST *blk = filp->f_path.dentry->d_inode->i_private;
	
	char *ptr = blk->buffer;
	int buflen = blk->buf_len;

	if (*offset >= buflen) {
		return 0;
	}

	printk("filesys_file_read len: %ld, offset: %lld\n", len, *offset);

	if (!ptr)
		return -EINVAL;
	ptr += *offset;

	if (copy_to_user(buffer, ptr, len)) {
		return -EFAULT;
	}
	*offset += len;
	return len;
}

ssize_t filesys_file_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset)
{
	size_t len = length > 1024 ? 1024 : length;
	FILE_CONTEXT_ST *blk = filp->f_path.dentry->d_inode->i_private;
	char *ptr = blk->buffer;
	
	printk("filesys_file_write\n");

	if (!ptr)
		return -EINVAL;
	ptr += *offset;

	if (copy_from_user(ptr, buffer, len)) {
		return -EFAULT;
	}
	blk->buf_len = len;
	filp->f_inode->i_size = blk->buf_len;

	*offset += len;

	return len;
}

int filesys_file_iterate (struct file *filp, struct dir_context *ctx)
{
	int count = get_one_file_idx();
	int i = 0;

	printk("filesys_file_iterate--> count: %d, pos: %lld\n", count, ctx->pos);
	if(ctx->pos > count) return 0;

	//在目录中添加.和..
	if (!dir_emit_dots(filp, ctx)) {
		return 0;
	}
	
	//遍历将一个文件或子目录的信息添加到目录中的函数
	for (i = 0;i < count;i ++) {
		dir_emit(ctx, g_file[i].filename, strlen(g_file[i].filename), i, DT_UNKNOWN);
		ctx->pos ++;
	}
	
	return 0;
}

3.3.2 运行结果测试:

只是测试框架以及方向,其他基本指令待实现。

mount -t filesys_test nodev ./mnt/ 中filesys_test 这里是代码中注册的文件系统名。

root@ubuntu:/home/ubuntu/storage/filesys# insmod filesys.ko 
root@ubuntu:/home/ubuntu/storage/filesys# mount -t filesys_test nodev ./mnt/
root@ubuntu:/home/ubuntu/storage/filesys# touch ./mnt/1.txt
root@ubuntu:/home/ubuntu/storage/filesys# echo "123456" >./mnt/1.txt
root@ubuntu:/home/ubuntu/storage/filesys# cat ./mnt/1.txt
123456
root@ubuntu:/home/ubuntu/storage/filesys# umount ./mnt/
root@ubuntu:/home/ubuntu/storage/filesys# rmmod filesys.ko 
root@ubuntu:/home/ubuntu/storage/filesys# dmesg
[ 3456.748025] init module: register filesys success. [0]. 
[ 3464.294293] filesys mount ...
[ 3464.294353] filesys mount super_block ...
[ 3474.675258] filesys_inode_lookup
[ 3474.675264] filename: 1.txt
[ 3474.675272] filesys_file_open
[ 3474.675280] filesys_file_release
[ 3487.604103] filesys_file_open
[ 3487.604123] filesys_file_write
[ 3487.604125] filesys_file_write
[ 3487.604126] filesys_file_write
[ 3487.604128] filesys_file_release
[ 3492.519216] filesys_file_open
[ 3492.519237] filesys_file_read len: 1024, offset: 0
[ 3492.519306] filesys_file_release
[ 3498.087984] filesys des ...
...
[ 3501.761647] destory module: unregister filesys.

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部