1、背景
在 nanomsg 的设计中,utils(实用工具)模块扮演着基础设施的角色,为整个库提供可靠、高效的基础功能支持。本文将全面剖析 utils 模块的架构设计、关键实现和优化技巧。
2、内存管理模块
nanomsg 的内存管理系统由三个紧密协作的核心模块组成:alloc、chunk 和 chunkref,共同构成了高效灵活的内存管理架构。其中:
- alloc:提供基础的 nn_alloc/nn_free 接口;支持内存池(nanomsg自定义的内存管理策略)和系统分配器切换(由宏控制)
- chunk:实现引用计数 (nn_atomic_refcount);内存有效性验证 (魔数tag机制);支持动态调整 (realloc/trim)
- chunkref:小对象优化 (内联存储 ≤32B);透明的大对象管理;移动/拷贝语义分离
2.1、alloc
其实alloc相对容易理解,它提供了两种内存分配的方式,由宏NN_ALLOC_MONITOR控制,一种是库中独有的内存管理方式。另外一种是系统提供的内存管理方式。库中独有的内存管理方式的内存模型如下:
+----------------+----------------+
| nn_alloc_hdr | 用户数据区域 |
+----------------+----------------+
返回给客户的指针是用户数据区域起始位置的地址,地址的移动封装到了函数中,对用户来说是一个黑盒子。这部分不难理解,这部分代码不在详细赘述。
2.2、chunk
chunk 模块是 nanomsg 中负责内存管理的核心组件,它实现了高效、安全的内存分配和引用计数机制,专门为消息传递场景优化。该模块的主要特点包括:支持多引用共享同一块内存、通过 magic number 检测内存损坏、支持内存块的动态调整。nn_chunk 结构体如下:
struct nn_chunk {
/* 引用计数器 */
struct nn_atomic refcount;
/* 数据部分的大小*/
size_t size;
/* 内存释放函数指针 */
nn_chunk_free_fn ffn;
/* 后面跟着:可选空白空间(这部分的设计非常巧妙,减少了数据的移动)、32位空白空间大小、32位tag和实际数据 */
};
chunk内存的内存模型如下:
+-------------------+---------+---------+---------+-----------------------------------+
| nn_chunk 头部 | 可选空间(减少的数据移动) | 空白大小 | 魔数tag | 用户数据 | 可能的填充区域 |
+-------------------+---------+---------+---------+-----------------------------------+
该模块提供了以下函数:
// 内存分配
int nn_chunk_alloc(size_t size, int type, void **result)
// 内存重分配
int nn_chunk_realloc(size_t size, void **chunk)
// 内存释放
void nn_chunk_free(void *p)
// 内存裁剪,这里只移动少量数据,设计的非常巧妙
void *nn_chunk_trim(void *p, size_t n)
// 获取chunk头指针的位置
static struct nn_chunk *nn_chunk_getptr (void *p)
{
uint32_t off;
/*
指针运算:(uint8_t*)p - sizeof(uint32_t) 定位到 tag 位置, 验证魔数是否为 0xdeadcafe,确保内存块有效性
*/
nn_assert (nn_getl ((uint8_t*) p - sizeof (uint32_t)) == NN_CHUNK_TAG);
// 这里是获取chunk结构体和空白区域之间备选空间的大小
off = nn_getl ((uint8_t*) p - 2 * sizeof (uint32_t));
// 返回的是chunk结构体头指针
return (struct nn_chunk*) ((uint8_t*) p - 2 *sizeof (uint32_t) - off -
sizeof (struct nn_chunk));
}
总的来说,理解了这里的内存模型后,在看代码实现就能理解该设计的巧妙之处。
2.3、chunkref
chunkref 是 nanomsg 中一个智能的内存引用管理系统,它通过小对象优化和引用计数技术,实现了高效灵活的内存管理。该模块的主要特点包括:小数据直接存储在结构体内,避免内存分配;大数据使用 nn_chunk 管理,支持共享;减少不必要的数据拷贝。chunkref的数据结构如下:
struct nn_chunkref {
size_t size; // 数据大小或特殊标记,当 ≤ NN_CHUNKREF_MAX时表示内联数据实际大小;当 = NN_CHUNKREF_EXT时表示使用外部 nn_chunk
union {
void *chunk; // 指向大数据的指针
uint8_t ref[NN_CHUNKREF_MAX]; // 内联存储小数据
} u;
};
该模块实现了以下接口:
// 初始化函数。小数据,直接记录大小,不立即分配内存
// 大数据,调用 nn_chunk_alloc 分配内存
void nn_chunkref_init (struct nn_chunkref *self, size_t size);
// 直接接管已有的 nn_chunk 内存块
void nn_chunkref_init_chunk (struct nn_chunkref *self, void *chunk);
// 回收资源
void nn_chunkref_term (struct nn_chunkref *self);
// 外部存储: 转移所有权(原引用置空)
// 内联存储:转换为 nn_chunk 并拷贝数据
void *nn_chunkref_getchunk (struct nn_chunkref *self);
// 移动资源,源对象变为未初始化状态,不增加引用计数
void nn_chunkref_mv (struct nn_chunkref *dst, struct nn_chunkref *src);
// 增加引用计数,内联存储:直接内存拷贝,源对象保持不变
void nn_chunkref_cp (struct nn_chunkref *dst, struct nn_chunkref *src);
// 外部存储:调用 nn_chunk_trim, 从数据头部移除指定字节数
// 内联存储:内存移动调整
void nn_chunkref_trim (struct nn_chunkref *self, size_t n);
2.4 内存申请时序图
2.5 内存销毁时序图
3、线程模块
nanomsg库中是兼容win系统和linux系统的,这里只介绍linux系统的相关实现。线程模块的关键数据结构如下:
struct nn_thread
{
// 线程要执行的函数
nn_thread_routine *routine;
// 函数参数
void *arg;
// 线程句柄
pthread_t handle;
};
该库对linux系统支持调用的是POSIX标准的pthread_create函数,由于其只接受 void* (func*)(void*)类型的函数指针,因此该库又对数据进行了一层封装,定义了一个静态函数static void *nn_thread_main_routine (void *arg)。线程模块中的核心函数是nn_thread_init。该函数的设计体现了对线程安全和信号处理的精细控制。源代码如下:
void nn_thread_init (struct nn_thread *self,
nn_thread_routine *routine, void *arg)
{
int rc;
sigset_t new_sigmask;
sigset_t old_sigmask;
// 这里用来屏蔽所有的信号,因为该库是一个通信中间件,它不会处理用户级别的信号
// 信号表是每个线程所独有的,因此要理解这块代码,必须知道线程独享哪些资源
rc = sigfillset (&new_sigmask);
errno_assert (rc == 0);
rc = pthread_sigmask (SIG_BLOCK, &new_sigmask, &old_sigmask);
errnum_assert (rc == 0, rc);
self->routine = routine;
self->arg = arg;
rc = pthread_create (&self->handle, NULL, nn_thread_main_routine,
(void*) self);
errnum_assert (rc == 0, rc);
// 恢复主线程原有的信号处理设置,不影响创建线程的信号环境
rc = pthread_sigmask (SIG_SETMASK, &old_sigmask, NULL);
errnum_assert (rc == 0, rc);
}
4、多种通信句柄结构体
nanomsg支持多种通信方式,win系统的通信和linux系统的通信。在工具模块,实现了多种通信句柄,比如eventfd、pipe、以及socketpair(底层是unix套接字)。该库针对每种通信句柄都提供了名称相同的基础函数,这样以来在上层通信时,它们是不可感知下层使用的是那种通信方式的,实现了通信和通信方法的解耦合。库对每种通信句柄都提供了如下一些方法:
int nn_efd_init (struct nn_efd *self);
void nn_efd_term (struct nn_efd *self);
nn_fd nn_efd_getfd (struct nn_efd *self);
void nn_efd_stop (struct nn_efd *self);
void nn_efd_signal (struct nn_efd *self);
void nn_efd_unsignal (struct nn_efd *self);
该库提供了使用poll监听通信句柄是否有事件到达的接口int nn_efd_wait (struct nn_efd *self, int timeout)。
5、特有的消息格式
nanomsg作为一个通信中间件,在utils模块中定义了特有的消息格式nn_msg,其定义如下:
struct nn_msg {
// sp协议头
struct nn_chunkref sphdr;
// 扩展消息协议头
struct nn_chunkref hdrs;
// 真正要传递的消息
struct nn_chunkref body;
}
只要理解了nn_chunkref结构体,还nn_msg每个字段的含义,再理解该模块针对nn_msg提供的那些方法就非常的容易了。
6、其它工具模块
nanomsg库还定义了如list、queue、hash、锁、信号量等这些常用的数据结构,以及一些字符串处理函数(比如在母串中寻找子串、判断两个子串是不是相等)、时间相关的工具(计时器等),这些东西比较好理解,这里不再进行赘述,下面主要介绍几个utils中设计到的比较常用的几个小工具。
6.1、nn_fast和nn_slow
这两个小工具的实现如下:
#define nn_fast(x) __builtin_expect ((x), 1)
#define nn_slow(x) __builtin_expect ((x), 0)
__builtin_expect是编译器gcc的内置函数,它的作用是向编译器提供一些预测信息,比如if else中哪些部分执行到的概率大,编译器在编译的时候就是根据程序员给它的预测值进行编译优化。
6.2、NN_NORETURN
库中的具体实现如下:
#if defined _MSC_VER
#define NN_NORETURN __declspec(noreturn)
#elif defined __GNUC__
#define NN_NORETURN __attribute__ ((noreturn))
#else
#define NN_NORETURN
#endif
告诉编译器,该函数不会返回函数调用的地方,目的是让编译器在编译时候进行优化。
6.3、nanomsg提供了很多断言宏
编程时,多使用断言有助于发现一些不可预测的错误,是防御性编程的重要手段。下面挑两个进行简单介绍,个人理解见注释。
#define nn_assert(x) \
do {\
// 代码执行到if体内的概率较小,提交告知编译器
if (nn_slow (!(x))) {\
// 打印backtrace到stderr,其实这里可以打印到任何需要的地方
nn_backtrace_print (); \
// 将代码行数和其它信息打印到stderr
fprintf (stderr, "%s [%d] (%s:%d)\n", nn_err_strerror (err),\
(int) (err), __FILE__, __LINE__);\
// 刷新stderr
fflush (stderr);\
// 执行abort
nn_err_abort ();\
}\
} while (0)
// 验证某个 errno 值是否属于系统定义的错误,如果条件中的err不满足cond时,即它不是系统级的错误时,将err的信息打印
#define errnum_assert(cond, err) \
do {\
if (nn_slow (!(cond))) {\
nn_backtrace_print (); \
fprintf (stderr, "%s [%d] (%s:%d)\n", nn_err_strerror (err),\
(int) (err), __FILE__, __LINE__);\
fflush (stderr);\
nn_err_abort ();\
}\
} while (0)