Nanomsg库中提供的那些基础工具

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值