存储课程学习笔记7_fuse库的简单使用(fuse,opencv的demo)

fuse一般称为用户态度文件系统,可以实现在用户层实现对文件系统的控制。

通过fuse内核模块的支持,基于libfuse提供的接口,就可以实现一个文件系统。

fuse内核模块实现了与VFS的对接,运行后可以看到/dev/fuse,拦截相关请求,反馈给fuse进程,进行操作。

使用fuse进行开发,不用关注内核模块,直接使用libfuse库在用户态实现文件系统即可。

0:总结

1:fuse的开发,实际上和前面借助内核插入文件系统大框架感觉差不多,都是实现文件系统基础指令对应的函数。

2:借助opencv实现给图片加水印,opencv的编译以及简单测试。

3:简单了解fuse库下的example下demo,以及简单验证demo。

4:fuse的扩展功能可以根据业务自行扩展,这里只是入门了解。

1:fuse库的使用场景。

fuse库使我们可以在用户态灵活控制文件系统,按需要进行开发(在虚拟化,数据库,云存储,等方向都可以按需使用)。

这里可以用fuse实现对该文件系统下的文件进行特定处理,比如给该文件系统下的图片加水印,给该系统下的文件加特定字段(日志加前缀等)

2:libfuse库的安装

#磁盘满了  借用du -h 查看目录下占用大小 依次进行处理
ubuntu@ubuntu:/home$ du -h -d 1
1.2G	./ubuntu
1.2G	.
ubuntu@ubuntu:~/fuse$ ls
libfuse-fuse-3.16.2.tar.gz
ubuntu@ubuntu:~/fuse$ tar -zxvf libfuse-fuse-3.16.2.tar.gz 

#进行构建 
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2$ mkdir build
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2$ cd build/
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$  meson setup ..
#编译和安装   注意日志,看安装位置。
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ ninja
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ sudo ninja install
...
Installing /home/ubuntu/fuse/libfuse-fuse-3.16.2/include/fuse.h to /usr/local/include/fuse3
...
ubuntu@ubuntu:~/fuse/libfuse-fuse-3.16.2/build$ ls /dev/fuse
/dev/fuse

3:首先了解一下libfuse下的example(了解笔记未深入)

hello.c是最基础的  会在文件系统下创建一个hello文件 有基本的写入。  ./hello -f ./test2
hello_ll.c 和hello同样的功能  用的更详细的接口。
hello_ll_uds.c 在上面基础上借助fuse_session_custom_io 定制了相关输入和输出通道
invalidate_path.c 借助多线程的方式,实现文件内容的持续变化。 fuse_invalidate_path 标记触发,告诉文件系统特定目录发生变化,需要重新处理。

ioctl.c 实现使支持ioctl, 配合ioctl_client.c测试  调用ioctl可以设置或者获取对应文件大小
一个终端上: 
^Croot@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl -f ./test2/
另一个终端上:
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 
6
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 12
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 
12
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ll ./test2/fioc 
-rw-r--r-- 1 root root 12 Sep  6 05:13 ./test2/fioc
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./ioctl_client ./test2/fioc 18
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ll ./test2/fioc 
-rw-r--r-- 1 root root 18 Sep  6 05:13 ./test2/fioc

notify_inval_entry.c 文件系统下文件名动态反应当前时间 
也是新线程  然后通过fuse_lowlevel_notify_expire_entry 移除老的文件
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_entry --update-interval=1 --timeout=30 --no-notify ./test2/
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/
Time_is_05h_45m_04s
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/
Time_is_05h_45m_06s
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ls test2/

notify_inval_inode.c 内核识别inode节点变化 分别不同处理测试
#查看说明  --no-notify 参数  不允许内核修改   文件内容不必变化
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_inode --update-interval=1 --no-notify ./test
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cat current_time 
The current time is 05:58:07
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cat current_time 
The current time is 05:58:07

#没有定义 --no-notify 参数 文件内容会随时间而变化
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./notify_inval_inode --update-interval=1  ./test3
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test# cd ../test3/
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time 
The current time is 06:12:50
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time 
The current time is 06:12:51
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test3# cat current_time 

notify_store_retrieve.c 和notify_inval_inode.c 功能相同  方法不同,主动将更新数据推送到内核  fuse_lowlevel_notify_store

passthrough.c 系列  实际上是fuse直接透传到底层真正的文件系统中  只是一个挂载的功能。 在该目录下操作,实际上是底层真正的文件系统中。

//测试代码貌似是把根目录进行映射了
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_fh -f ./test1 
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test1# ls
bin  boot  dev  etc  home  lib  lib32  lib64  libx32  lost+found  media  mnt  opt  proc  root  run  sbin  snap  srv  sys  tmp  usr  var
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test1# touch 1
//在映射目录中操作  底层实际文件系统已经实时进行操作了 创建了文件1
ubuntu@ubuntu:/$ ls
1     dev   lib    libx32      mnt   root  snap  tmp
bin   etc   lib32  lost+found  opt   run   srv   usr
boot  home  lib64  media       proc  sbin  sys   var

root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_hp  -h
./passthrough_hp: Option ‘h’ does not exist
Usage: ./passthrough_hp --help
       ./passthrough_hp [options] <source> <mountpoint>
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example# ./passthrough_hp  ./test1 ./test2
//在映射目录test2目录下创建操作文件  在实际文件系统目录test1下能识别到
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# touch 1
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# touch 2
root@ubuntu:/home/ubuntu/storage/fuse/libfuse-fuse-3.16.2/build/example/test2# ls ../test1/
1  2


poll.c  借助fuse_notify_poll 触发事件  实现了监控文件操作进行打印。   ./poll -f /mnt
null.c 就是一个基本的空的文件系统   

struct fuse_operations 是一个结构体,用于定义 FUSE 文件系统操作的回调函数。以下是一些常见的成员和其功能介绍:

getattr: 获取文件属性(如大小、权限等)的回调函数。
readdir: 读取目录内容的回调函数。
open: 打开文件的回调函数。
read: 读取文件内容的回调函数。
write: 写入文件内容的回调函数。
mkdir: 创建目录的回调函数。
rmdir: 删除目录的回调函数。
unlink: 删除文件或符号链接的回调函数。
rename: 重命名文件或移动文件到另一个目录的回调函数。
truncate: 调整文件大小(截断或扩展)的回调函数。

4: 了解demo运行时的参数

-f 或 --foreground: 让文件系统在前台运行,输出日志到控制台。
-d 或 --debug: 启用调试模式,输出更详细的日志信息。
-s 或 --singlethread: 强制单线程模式,即所有回调函数都在主线程中执行。
-o <options>: 指定其他额外的选项。例如,可以使用 -o allow_other 来允许其他用户访问该文件系统。
<mountpoint>: 指定挂载点路径,即将文件系统挂载到哪个目录。

struct fuse_cmdline_opts {
    char *mountpoint;           // 挂载点路径
    int fd;                     // 文件描述符
    int show_help;              // 是否显示帮助信息
    int foreground;             // 是否前台运行
    int debug;                  // 是否开启调试模式
};

5:实现一个demo,用fuse给图片加水印

fuse就是一个进程,用户可以按业务进行控制,可以控制在操作文件系统时的行为,比如读图片时给图片加水印。

5.1 加水印要借助opencv,实现一个demo

5.1.1 安装opencv
tar -zxvf opencv-4.10.0.tar.gz 
cd opencv-4.10.0/
mkdir build
cd build/
cmake .. -D CMAKE_BUILD_TYPE=Release -D OPENCV_GENERATE_PKGCONFIG=ON
make -j8
sudo make install
sudo ldconfig

#后面编译测试时用pkg-config 链接库
ubuntu@ubuntu:~/fuse/opencv-4.10.0/build$ opencv_version
4.10.0
ubuntu@ubuntu:~/fuse/opencv-4.10.0/build$ pkg-config --list-all|grep opencv
opencv4           OpenCV - Open Source Computer Vision Library

5.1.2 实现加水印的demo
//直接调用opencv的接口实现图片加水印的demo
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int add_water_mark(void) {
	Mat image = imread("/home/ubuntu/storage/fuse/test/China.jpg");

    if (image.empty())
    {
        cout << "empty file"<< endl;
        return -1;
    }

    Mat watermarkedImage = image.clone();
    string watermarkText = "opencv add to picture"; 
    int fontFace = FONT_HERSHEY_SIMPLEX;
    double fontScale = 2;
    int thickness = 2;
    int baseline = 0;
    Size textSize = getTextSize(watermarkText, fontFace, fontScale, thickness, &baseline);
     Point textOrg((image.cols - textSize.width) / 2, (image.rows + textSize.height) / 2);
     putText(watermarkedImage, watermarkText, textOrg, fontFace, fontScale,
             Scalar(0, 0, 255), thickness);

     imwrite("/home/ubuntu/storage/fuse/test/output.jpg", watermarkedImage);
	return 0;
}
int main()
{
	return add_water_mark();
}
5.1.3 demo编译及运行结果
#编译两种方式 
ubuntu@ubuntu:~/start_test$ g++ opencv_test.c -o opencv_test `pkg-config --cflags --libs opencv4`
ubuntu@ubuntu:~/start_test$ g++ opencv_test.c -o opencv_test -I/usr/local/include/opencv4 -L/usr/local/lib -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_imgcodecs

root@ubuntu:/home/ubuntu/start_test# ./opencv_test
root@ubuntu:/home/ubuntu/start_test# ls
opencv_test.c  image.jpg image_out.jpg  io_uring.c  opencv_test  
#打开image_out.jpg图片,可以看到已经增加相关水印到图片中。。。 其他功能可扩展。

5.2 使用fuse实现测试代码。

参考example,实际上和前面几次借助内核插入文件系统大框架类似,实现struct fuse_operations结构体中对应的函数实现对应文件指令的调用。

5.2.1 实现一个example 编译运行测试
#define FUSE_USE_VERSION	30

#include <fuse3/fuse.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

int sample_getattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi);
int sample_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset,
			struct fuse_file_info *fi, enum fuse_readdir_flags flags);
int sample_write(const char *path, const char *buf, size_t size, off_t offset,
		      struct fuse_file_info *fi);
int sample_read(const char *path, char *buf, size_t size, off_t offset,
		      struct fuse_file_info *fi);

//新的方案demo 这个结构  还有一个结构 struct fuse_lowlevel_ops
struct fuse_operations sample_opera = {
	.getattr = sample_getattr,   //获取文件属性 
	.read = sample_read,		//文件读
	.write = sample_write,     //文件写
	.readdir = sample_readdir, //目录内容
};

int main(int argc, char *argv[]) {
	return fuse_main(argc, argv, &sample_opera, NULL);
}

#define FILE_MAX_LENGTH		1024
static void *sample_buffer = NULL;
static size_t sample_size = 0;

enum {
	SAMPLE_NONE = 0,
	SAMPLE_FOLDER,
	SAMPLE_FILE,
};

static int file_type(const char *path) {

	if (strcmp(path, "/") == 0) {
		return SAMPLE_FOLDER;
	}

	if (strcmp(path, "/safe") == 0) {
		return SAMPLE_FILE;
	}
	
	return SAMPLE_NONE;
}

//获取文件属性 进行处理
//只是简单对目录和文件进行区分 复杂的需要参考example
int sample_getattr (const char *path, struct stat *stbuf, struct fuse_file_info *fi)
{
	stbuf->st_uid = getuid();
	stbuf->st_gid = getgid();
	stbuf->st_atime = stbuf->st_mtime = time(NULL);

	int type = file_type(path);
	printf("sample_getattr --> path: %s, type: %d\n", path, type);
	
	if (type == SAMPLE_FOLDER) {
		stbuf->st_mode = S_IFDIR | 0755;
		stbuf->st_nlink = 2;
	} else if (type == SAMPLE_FILE) {
		stbuf->st_mode = S_IFREG | 0777;
		stbuf->st_nlink = 2;
		stbuf->st_size = sample_size;
	} else {
		return -ENOENT;
	}
	return 0;
}

//简单演示目录下这几个的读 
int sample_readdir (const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags)
{
	//只是最简单的这几个目录的演示
	filler(buf, ".", NULL, 0, (enum fuse_fill_dir_flags)0);
	filler(buf, "..", NULL, 0, (enum fuse_fill_dir_flags)0);
	filler(buf, "safe", NULL, 0, (enum fuse_fill_dir_flags)0);

	return 0;
}
//文件写 先写入一个文件再读 
int sample_write(const char *path, const char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
	int type = file_type(path); //写入safe文件
	printf("sample_write %s --> type: %d\n", path , type);

	if (type != SAMPLE_FILE) {
		return -EINVAL;
	}

	if (sample_buffer == NULL) {
		sample_buffer = malloc(FILE_MAX_LENGTH);
	}

	//写入的buffer进行大小和偏移的处理  返回实际写入的长度
	if (size + offset > FILE_MAX_LENGTH)
	{
		memcpy((void*)((char *)sample_buffer+offset), buf, FILE_MAX_LENGTH - offset);
		sample_size = FILE_MAX_LENGTH;
		
		return FILE_MAX_LENGTH - offset;
	} 

	sample_size = size;
	memcpy((void*)((char *)sample_buffer+offset), buf, size);
	
	return size;
}

//文件的读  要读取文件的路径   存储读取数据的缓冲区  期望读的大小   文件中开始读取偏移量   包含有关文件信息和打开标志的结构体指针。
int sample_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi)
{
	int type = file_type(path);
	printf("sample_read --> type: %d\n", type);
	if (type != SAMPLE_FILE) {
		return -EINVAL;
	}

	if (sample_buffer == NULL) {
		return -EINVAL;
	}

	//考虑越界相关问题  直接从上面写入的指针处写入吗? 期望读size 从offset 读 实际读sample_size
	printf("sample_read --> %ld, %ld , %ld\n", size, offset, sample_size);
	memcpy(buf, (char *)sample_buffer+offset, sample_size);
	printf("buf: %s\n", buf);
	return sample_size;
}

//那文件的读和写,不就实际上在内存中管理了。
//可以参考example 更细节的操作。
//核心还是每个指令对应的函数  了解其函数参数细节,按要求依次进行处理即可。 
5.2.2 编译和运行测试:
ubuntu@ubuntu:~/start_testgcc fuse_test.c -o fuse_test -lfuse3
ubuntu@ubuntu:~/start_test$ mkdir test1
#启动 在前台打印日志观察   fuse已经接管了test1 目录 
ubuntu@ubuntu:~/start_test$ ./fuse_test -f test1/
sample_getattr --> path: /, type: 1
sample_getattr --> path: /, type: 1
sample_getattr --> path: /, type: 1
sample_getattr --> path: /safe, type: 2
sample_getattr --> path: /safe, type: 2
sample_write /safe --> type: 2
sample_getattr --> path: /safe, type: 2
sample_read --> type: 2
sample_read --> 4096, 0 , 30
buf: 11111111111111111111111111111

#在test1目录下执行如下操作,会观察到上面的日志
ubuntu@ubuntu:~/start_test/test1$ cd ../test1/
ubuntu@ubuntu:~/start_test/test1$ ls
safe
ubuntu@ubuntu:~/start_test/test1$ echo "11111111111111111111111111111" >safe
ubuntu@ubuntu:~/start_test/test1$ cat safe
11111111111111111111111111111


#如果把上面加水印的接口加入到对应的写或者读接口中,就可以在fuse中实现对文件系统中的特定图片进行处理了。
#参考example中的demo,也可以对fuse对文件系统的扩展有一定了解。
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值