ngx_rtmp_shared.c文件主要是nginx-rtmp的内存池
nginx本身有一套自己的内存池,nginx-rtmp模块根据业务特性音视频又写了一套内存回收的机制
下面主要从nginx-rtmp内存池的内部实现原理以及内存池的使用做介绍分析
提供外部调用的API
- ngx_rtmp_alloc_shared_buf 申请分配块内存
- ngx_rtmp_free_shared_chain 内存块回收
- ngx_rtmp_append_shared_bufs 内存拷贝追加
- ngx_rtmp_acquire_shared_chain 内存块引用计数加加
内存块申请
ngx_rtmp_alloc_shared_buf主要用来申请块内存,块内存大小是固定大小根据chunk_size配置的,默认是4096
- 每次申请先从cscf->free链表中取,如果有取一个节点
- 如果没有,重新申请内存块,内存块buf大小 cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER
- 给chain的bufpos、start、end位置置位指向实际可以存放数据的位置
关于引用计数可以看这篇 “rtmp模块引用计数”
ngx_chain_t *
ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)
{
u_char *p;
ngx_chain_t *out;
ngx_buf_t *b;
size_t size;
/* 先从回收块内存的free链表指针取,有就取一块,没有就重新申请 */
if (cscf->free) {
out = cscf->free;
cscf->free = out->next;
} else {
/* 先申请块用以存放音视频帧body的数据块 + rtmp header的大小18字节 */
size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;
/* 整个块内存的带下,引用计数占用的四字节 + chain本身结构体 + buf结构体 + size 大小 */
p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES
+ sizeof(ngx_chain_t)
+ sizeof(ngx_buf_t)
+ size);
if (p == NULL) {
return NULL;
}
/* 由于引用计数是提前占了四字节,所以chain的位置从四字节后开始取 */
p += NGX_RTMP_REFCOUNT_BYTES;
out = (ngx_chain_t *)p;
p += sizeof(ngx_chain_t);
out->buf = (ngx_buf_t *)p;
p += sizeof(ngx_buf_t);
out->buf->start = p;
/* 将指针位置buf->start位置指向真正存放数据的位置 */
out->buf->end = p + size;
}
out->next = NULL;
b = out->buf;
b->pos = b->last = b->start + NGX_RTMP_MAX_CHUNK_HEADER;
b->memory = 1;
/* buffer has refcount =1 when created! 设置当前内存块引用计数值为1 */
ngx_rtmp_ref_set(out, 1);
return out;
}
从上面代码分析可知,rtmp模块每次申请都是优先从free链表内存池里面先取,不够再去申请。
内存块回收
ngx_rtmp_free_shared_chain对于chain块内存回收的时候,并不是真正意义上的free内存,而是放到链表当中下次循环使用,这样的好处减少频繁内存申请释放
void
ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in)
{
ngx_chain_t *cl;
/* 释放的时候,优先判断一下引用计数是否为0,如果不为0 当前并不释放,因为还有其他的地方再用 */
if (ngx_rtmp_ref_put(in)) {
return;
}
/* 引用计数为0,将当前内存块放到链表的结尾 */
for (cl = in; ; cl = cl->next) {
if (cl->next == NULL) {
cl->next = cscf->free;
cscf->free = in;
return;
}
}
}
free内存的时候,注意以下几点
- 1)引用计数为0,才开始释放内存
- 2)释放内存并不是真正的调用系统调用free操作,而是将当前内存块通过尾插法的方式放到cscf->free链表当中
内存追加
ngx_rtmp_append_shared_bufs 主要是内存追加,将in里面的数据放到head的后面
ngx_chain_t *
ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
ngx_chain_t *head, ngx_chain_t *in)
{
ngx_chain_t *l, **ll;
u_char *p;
size_t size;
ll = &head;
p = in->buf->pos;
l = head;
/* 先将指针指向head链表最后的位置 */
if (l) {
for(; l->next; l = l->next);
ll = &l->next;
}
for ( ;; ) {
/* 新分配一块内存块l */
if (l == NULL || l->buf->last == l->buf->end) {
l = ngx_rtmp_alloc_shared_buf(cscf);
if (l == NULL || l->buf == NULL) {
break;
}
*ll = l;
ll = &l->next;
}
/* 将内存块in里面的内容拷贝到l当中,in有可能后面next还有数据所以是循环遍历 */
while (l->buf->end - l->buf->last >= in->buf->last - p) {
l->buf->last = ngx_cpymem(l->buf->last, p,
in->buf->last - p);
in = in->next;
if (in == NULL) {
goto done;
}
p = in->buf->pos;
}
/* 如果后面l 后面内存空间不够用,将剩余可用的先拷贝进去然后再申请新的内存块再拷贝 */
size = l->buf->end - l->buf->last;
l->buf->last = ngx_cpymem(l->buf->last, p, size);
p += size;
}
done:
*ll = NULL;
/* in里面的数据拷贝追加完之后,再返回链表头 head */
return head;
}
总结:
内存循环利用的原理其实很简单:
- 1)弄一个cscf->free链表用来回收内存块,下次需要重新分配先从free链表取,减少内存块频繁的申请和释放
- 2)引用计数的应用,引用计数C++里面应用比较广泛,C语言这种用法确实很巧妙。以后自己设计也可以这么用