Page 506 - 《软件学报》2024年第4期
P. 506
2084 软件学报 2024 年第 35 卷第 4 期
榫卯框架中使用高阶函数复合各层级函数, 其 C 宏实现如图 4 所示. 对于一个层级函数而言, 它至少需要实
现 4 个层级函数接口 create_obj, alloc_obj, free_obj 和 getmeta_obj, 分别用于线程创建时初始化内存请求上下文中
的层级对象, 处理分配和释放内存请求, 以及获取内存对象的元数据. 层级函数接口的输入参数包含内存请求上下
文 mctx_t 和已完成连接的层级函数 lfunc_t 的接口. 图 4 给出了一个简单层级函数的实现示例, 该层级函数用于统
计指定大小内存申请和释放次数. 对开发者而言, 编写一个层级函数只需要关注本层提供的功能以及相关数据结
构, 而无需考虑其上层或下层代码. 一个完整的内存分配器构建过程首先需要使用 DEFINE_OSLAYER 完成系统
接口层的定义, 然后通过 LAYER_COMPOSE 高阶函数将多个层级函数连接.
一个简单基础层级函数实现示例
// 可连接的层级函数定义 该层级函数用于统计指定大小内存申请和释放的次数
typedef mctx_t * lfunc_t (mctx_t *ctx);
// 声明层级
#define __DECLARE_OBJ_FUNC (func_name) \ DECLARE_LA YER(count)
static inline mctx_t * func_name##_obj(mctx_t *ctx, lfunc_t *ufunc); // 层级对象结构体定义
typedef struct{
// 每一层至少需要实现的层级函数 unsigned long alloc_count;
#define DECLARE_LA YER(layer_name) \ unsigned long free_count;
MAP(__DECLARE_OBJ_FUNC , layer_name##_create, }count_obj_t ;
layer_name##_alloc, layer_name##_free, layer_name##_getmeta)
count_obj_t count_obj_global;
#define __LFUNC_COMPOSABLE (unlinkedf, linkedf) \ // 将储存层级数据的对象指针记录到线程的内存上下文中
static inline mctx_t *__linked_##unlinkedf (mctx_t *ctx) static inline mctx_t * count_cr eate_obj (mctx_t *ctx, lfunc_t *ufunc){
{ return unlinkedf (ctx, linkedf);} // 此处所有线程共享一个全局对象指针
mctx_setlayer_obj (ctx, &count_obj_global);
// 定义系统接口使用的辅助函数 // 若线程私有对象指针则使用 os_alloc接口分配线程私有的空间
#define __OS_LAYER(layer_type) \ return ufunc (ctx);
__LFUNC_COMPOSABLE (layer_type##_obj, NULL ) }
// 用于复合层级函数的辅助函数 static inline mctx_t * count_alloc_obj (mctx_t *ctx, lfunc_t *ufunc){
#define __LCOMPOSE (linkedl1_type, l2_type) \ // 使用分级管理策略判断是否直接跳过本级
__LFUNC_COMPOSABLE (l2_type##_obj, __linked_##linkedl1_type##_obj ) MATCH_HANDLE_SIZE (count, ctx->rsize, ctx, ufunc);
// 获取当前层级对象
#define __LCOMPOSE_TYPE (l1, l2, type) \ count_obj_t *l = (count_obj_t *)mctx_getlayer_obj (ctx);
__LCOMPOSE (l1##_##type, l2##_##type) debug_printf ("count alloc size %llu\n", ctx->rsize);
// 增加统计计数
// 定义系统接口层函数 FAA(&l->alloc_count, 1);
#define DEFINE_OSLA YER(alayer) \ // 内存请求转交给上级处理
MAP(__OS_LAYER, alayer##_create, return ufunc (ctx);
alayer##_alloc, alayer##_free, alayer##_getmeta) }
// 层级连接,进行三种函数的复合 static inline mctx_t * count_fr ee_obj (mctx_t *ctx, lfunc_t *ufunc){
#define __LAYER_CONNECT (linkedl1, l2) \ // ...
__LCOMPOSE_TYPE (linkedl1, l2, create) \ }
__LCOMPOSE_TYPE (linkedl1, l2, alloc) \
__LCOMPOSE_TYPE (linkedl1, l2, free) \ static inline mctx_t * count_getmeta_obj (mctx_t *ctx, lfunc_t *ufunc){
__LCOMPOSE_TYPE (linkedl1, l2, getmeta) // 该层不负责管理内存对象的元数据 ,直接交给上层处理
return ufunc (ctx);
// 使用FOLD LEFT连接整个层级函数链表 }
#define LAYER_COMPOSE (...) \
FOLDL (__LAYER_CONNECT , __VA_ARGS__)
图 4 榫卯框架的实现细节与一个简单的层级函数示例
由于 C 语言并不支持函数式编程, 因此实现函数的复合还需要用到 C 预处理的元编程特性. 层级函数的复合
主要依靠高阶函数 LAYER_CONNECT 实现. 该高阶函数要求输入一个上级已完成连接的层级函数和未完成连接
的层级函数, 输出一个完成连接的层级函数__linked_layer_type_obj. 在编译预处理阶段, C 预处理器会展开相关
宏, 替换宏参数从而实现中间函数的生成与函数的复合. 此外, 图 4 中所使用的函数式编程中常用的高阶函数
MAP 和 FOLDL 依赖 C99 标准可变参数宏机制以及 C++20 标准可递归宏机制. 其中 MAP 接受一个函数 f 和一串
输入, 将每个输入传入 f 执行; FOLDL 则将前一次输出与下一次输入一同传入函数 f, 递归产生输出. 由于这两个
高阶宏函数的 C 实现较为复杂且有相应开源代码库 macrofun 可供参考, 本文在此不再赘述其具体实现.
在榫卯框架中, 可复合的层级函数分为系统接口层函数, 基础层函数和用户接口层函数 3 种, 其中基础层函数
允许存在多个, 而系统接口层函数和用户接口层函数只允许最多 1 个, 分别位于层级顺序的第 1 个和最后 1 个. 榫