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