Fio中的插件式设计分析,抽取出来一份模板。
一、设计来源
并不是我设计的哈哈哈哈~
最近看fio的源代码,学习了一波fio中关于IO引擎的插件式设计,对重要的一些部分做了摘要。以下是我的总结,其中有一些是我的猜测,有待验证
名词解释:
名词 |
解释 |
主程序 |
调用插件的程序 |
插件 |
可以被方便地替换地部分 |
二、插件与主程序的结构关联
主程序如果想使用插件中的函数,则需要知道插件中对应函数的地址,所以我们可以定义一个结构体集合,用来保存插件提供函数的地址,和其它相关内容。在使用插件时我们只要找到了该结构体的位置,就相当于找到了插件中提供的函数的位置。例如
text1 2 3 4 5 6 7 8 9 10
| struct PluginStruct { char *plugin_name; int plugin_version;
int (*init)(struct thread_data *); int (*uninit)(struct thread_data *);
int (*io_write)(struct thread_data *, struct io_unit*); int (*io_read)(struct thread_data *, struct io_unit*); };
|
在本例中,我们准备做一个读写文件的插件,插件一使用write/read
方式读写;插件二使用pwrite/pread
方式读写。如上述结构体中:init
和uninit
是插件的初始化和反初始化,io_write
和io_read
是对读写接口的封装。
插件可能是由多线程来调用的,为了表明这一点,插件接口的参数中使用结构体struct thread_data
来表示每个线程的私有数据(fio中也是用的thread_data)。
插件中也可能想保存私有数据,但是由于不知道有多少线程会使用该插件,所以只能将数据保存到结构体struct thread_data
中plugin_data
,然后在插件中做类型转换,结构体struct thread_data
只是作为私有数据的plugin_data
承载作用,私有数据plugin_data
在init
和uninit
中分别申请空间和释放空间,对于外部来说可以做到‘神不知,鬼不觉’。
为了能够做到基本的数据读写,结构体struct thread_data
和struct io_unit
定义如下
text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| /* 文件属性,用于保存每个线程操作的文件信息 */ struct FileAttr { char *file_name; int fd; };
/* 线程数据,保存一个线程用到的所有数据 */ struct thread_data { /* some thread private data */ int thread_id; pthread_t thread_handle;
struct PluginStruct *plugin_struct; // 通过该变量访问插件中的函数地址 void *plugin_private_data;
struct FileAttr *file_attr; // 保存该线程用到的文件信息
void *plugin_dll_handle; // 共享库句柄 };
/* io操作单元,一次IO操作必要的信息 */ struct io_unit { void *buffer; // 缓冲区地址 uint64_t size; // 读写大小 uint64_t offset;// 读写偏移 };
|
以上的结构体均定义在主程序头文件中,插件只是使用这些结构体类型,或访问内存中的值,或定义变量。
完整的插件需要包含的主程序头文件plugin.h
定义如下
text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| // plugin.h
// 插件在加载和关闭时,自动调用的构造函数和析构函数 标识? #define plugin_init __attribute__((constructor)) #define plugin_exit __attribute__((destructor))
/* 文件属性,用于保存每个线程操作的文件信息 */ struct FileAttr { char *file_name; int fd; };
/* 线程数据,保存一个线程用到的所有数据 */ struct thread_data { /* some thread private data */ int thread_id; pthread_t thread_handle;
struct PluginStruct *plugin_struct; // 通过该变量访问插件中的函数地址 void *plugin_private_data;
struct FileAttr *file_attr; // 保存该线程用到的文件信息
void *plugin_dll_handle; // 共享库句柄 };
/* io操作单元,一次IO操作必要的信息 */ struct io_unit { void *buffer; // 缓冲区地址 uint64_t size; // 读写大小 uint64_t offset;// 读写偏移 };
struct PluginStruct { char *plugin_name; int plugin_version;
int (*init)(struct thread_data *); int (*uninit)(struct thread_data *);
int (*io_write)(struct thread_data *, struct io_unit*); int (*io_read)(struct thread_data *, struct io_unit*); };
// 插件向主程序注册和反注册接口 extern void plugin_register(struct PluginStruct *); extern void plugin_unregister(struct PluginStruct *);
|
三、插件向主程序注册/主程序主动加载插件
fio中IO引擎的注册包含了两种,(猜测是为了防止其中一种失败,然后采用另一种方式)。
主程序主动加载插件方式是主程序通过dlopen()
、dlsym()
和dlclose()
系列函数完成插件中struct PluginStruct
结构体变量的加载,从而获取到插件中相应函数的地址;插件向主程序注册方式是主程序在加载插件动态库时,自动调用某些”构造函数”,而在构造函数中调用主程序的注册函数可以完成插件的注册。
插件程序定义如下:
text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| // plugin_1.c
/* 包含定义插件必须的头文件 */ #include "plugin.h"
// 插件私有变量定义 struct plugin_private_data { // some private data int write_call_times; int read_call_times; }
static int plugin_1_init(struct thread_data *td) { }
static int plugin_1_uninit(struct thread_data *td) { }
static int plugin_1_io_read(struct thread_data *td, struct io_unit *io_u) { }
static int plugin_1_io_write(struct thread_data *td, struct io_unit *io_u) { }
// 注册所有本插件的相关函数到插件结构体中 struct PluginStruct plugin = { .plugin_name = "plugin_1", .plugin_version = 1, .init = plugin_1_uninit, .uninit = plugin_1_uninit, .io_write = plugin_1_io_write, .io_read = plugin_1_io_read, };
// 插件动态库在加载时会自动调用该函数,因为plugin_init的原因 static void plugin_init plugin_1_auto_register(){ plugin_register(&plugin); } // 插件动态库在关闭时会自动调用该函数 static void plugin_exit plugin_1_auto_unregister(){ plugin_unregister(&plugin); }
|
3.1 主程序主动加载插件
主程序通过dlopen()
、dlsym()
和dlclose()
系列函数完成插件中plugin
变量的加载,从而完成插件的注册,这种方式的前提是插件中一定定义了plugin
变量,否则无法完成插件的加载。
主动加载示例如下:
text1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // plugin.c
static PluginStruct* load_plugin(struct thread_data *td, char *plugin_dll_path){ struct PluginStruct *plugin; void *dll_handle = dlopen(plugin_dll_path, RTLD_LAZY); if (!dll_handle) { return NULL; }
plugin = dlsym(dll_handle, plugin_dll_path); // 这是啥? if (!plugin){ plugin = dlsym(dll_handle, "plugin"); } return plugin; }
|
3.2 插件向主程序注册
插件中需要定义的代码如下:
text1 2 3 4 5 6 7 8 9 10
| // plugin_1.c
// 插件动态库在加载时会自动调用该函数,因为plugin_init的原因 static void plugin_init plugin_1_auto_register(){ plugin_register(&plugin); } // 插件动态库在关闭时会自动调用该函数 static void plugin_exit plugin_1_auto_unregister(){ plugin_unregister(&plugin); }
|
原文链接: https://www.delta1037.cn/2021/C_C++/C-C++插件式设计/
版权声明: 转载请注明出处.