介绍

Talloc是一个层次结构的,包含引用计数和析构机制的内存池系统。它构建于标准C库上,定义了一组接口用以简化数据的申请和回收,尤其是对于那些包含了许多动态申请的元素(比如数组和字符串)的复杂数据结构尤为有效。

Talloc库的主要目标是:不必再为每一个复杂的数据结构都单独编写内存释放函数;为申请的内存块提供一个逻辑组织架构;减少长时间运行的应用程序中出现内存泄露的几率。所有这些都依靠在层次结构的talloc context中申请内存而实现:当释放一个context时,它所有的子context都会被释放。

1、什么是talloc context
Talloc context是talloc库中最重要的部分,负责着这个内存分配器的每一个特性。它是talloc管理的内存区域的逻辑单位。

从开发者的视角来看,talloc context完全可以看作使用标准C库申请内存时返回的指针,每个使用talloc库返回的context都可以直接被不使用talloc的函数调用,比如像下面这样:

char *str1 = strdup("I am NOT a talloc context");
char *str2 = talloc_strdup(NULL, "I AM a talloc context");printf("%d\n", strcmp(str1, str2) == 0);free(str1);
talloc_free(str2); /* we can not use free() on str2 */

原因:talloc context内部维护了一个特殊的固定大小的数据结构,叫做talloc chunk。每个chunk存储了内存的元数据,当talloc函数返回一个context(指针)时,它实际上指向的是talloc trunk的用户数据区域,而在使用talloc库函数处理context时,talloc库会将指针向前偏移到talloc chunk的起始地址,如下图所示:

talloc.h中定义了TALLOC_CTX类型,其用来在函数中声明context。它是void类型的一个别名,存在的意义是为了语义上的原因——这样我们可以分辨出void * (任意数据)与TALLOC_CTX *(talloc context)。
1.1、Context元数据
每个context元数据包含了与申请这块内存相关的几部分信息:

a) 名称——用来报告context的层次结构,以及模拟动态类型系统。
b) 申请的内存字节数——这个可以用来判断数组中的元素个数。
c) 附加的析构函数——当此块内存被释放前,它会被执行
d) context的引用
e) 子context以及父context——建立内存的分层机制。

1.2、Talloc context的层级

每个talloc context都保存着它的子节点和父节点的信息。Talloc依靠这些信息建立了一个层次化的内存模型。更明确的说,它建立了一个N叉树,每个节点代表着一个talloc context。这个树的根节点被称为顶级context——一个没有任何父节点的context。

这种方法有几个优点:

1、在释放talloc context结构时,它包含的所有子节点都会被自动释放。
2、可以改变talloc context的父节点,即:将整棵子树移动到另一个节点下方。
3、建立了一种更加自然的数据结构管理方式。

1.3、示例
有一个存储用户基本信息的数据结构——他/她的名字,身份证号以及他/她所属于的所有的组:

struct user {uid_t uid;char *username;size_t num_groups;char **groups;
};

用talloc申请这个数据结构,其结果是如下所示的context树:

user指针的申请过程如下:

/* create new top level context */
struct user *user = talloc(NULL, struct user);user->uid = 1000;
user->num_groups = N;/* make user the parent of following contexts */
user->username = talloc_strdup(user, "Test user");
user->groups = talloc_array(user, char*, user->num_groups);for (i = 0; i < user->num_groups; i++) {/* make user->groups the parent of following context */user->groups[i] = talloc_asprintf(user->groups,"Test group %d", i);
}

user指针的释放如下:

talloc_free(user);

由此可见,talloc和malloc的区别如下:

在释放指针式,如果使用标准C库,则需要先遍历group数组释放每一个元素,然后再释放存储元素的数组和用户名字符串,最后释放整个结构体。而使用talloc,仅仅需要释放结构体context,它的子节点都会被自动释放。

2、建立一个talloc context
最重要的函数,用来创建talloc context。有两种方式:一是类型安全的创建context;二是创建0长度的context。

2.1、类型安全的创建context(推荐)
它将申请指定类型的大小的内存,并返回一个新的,已经被转换过类型的指针,Context的名称将会被自动设置为数据类型的名称,用来模拟动态类型系统。

示例:

struct user *user = talloc(ctx, struct user);/* initialize to default values */
user->uid = 0;
user->name = NULL;
user->num_groups = 0;
user->groups = NULL;/* or we can achieve the same result with */
struct user *user_zero = talloc_zero(ctx, struct user);

2.2、零长度的context
零长度的context是一个没有任何语义含义的context,它只由context的元数据构成,类型是TALLOC_CTX*。

它一般用来聚合几个数据结构到一个(零长度的)父context,比如一个临时的context用来保存这个函数里的所有内存,而函数的调用者对这些内存并不关心。申请一个临时的context可以让函数业务执行后的清理工作变得更简单。

举例:

TALLOC_CTX *tmp_ctx = NULL;
struct foo *foo = NULL;
struct bar *bar = NULL;/* new zero-length top level context */
tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {return ENOMEM;
}foo = talloc(tmp_ctx, struct foo);
bar = talloc(tmp_ctx, struct bar);/* free everything at once */
talloc_free(tmp_ctx);

3、过继talloc context
Talloc可以变更一个context的父节点,这种操作被称为“过继”(译者注:原文为stealing,为更加易懂翻译为过继),它是最重要的talloc context操作之一。

如果我们需要让一个context的生命周期比它的父节点更长时,那么可以将其过继给其他父节点,比如:将数据库的搜索结果过继给内存中的缓存,或者将父节点从一个通用结构更改为一个具体的结构,反之亦然。最常见的场景(至少对Samba来说),是将数据从某一个函数内专用的context过继给输出context,作为输出函数的参数。

举例:

struct foo {char *a1;char *a2;char *a3;
};struct bar {char *wurst;struct foo *foo;
};struct foo *foo = talloc_zero(ctx, struct foo);
foo->a1 = talloc_strdup(foo, "a1");
foo->a2 = talloc_strdup(foo, "a2");
foo->a3 = talloc_strdup(foo, "a3");struct bar *bar = talloc_zero(NULL, struct bar);
/* change parent of foo from ctx to bar */
bar->foo = talloc_steal(bar, foo);/* or do the same but assign foo = NULL */
bar->foo = talloc_move(bar, &foo);

talloc_move()函数与talloc_steal函数类似,不同点是它进一步将源指针设为了NULL。

一般来说,源指针自身是不会被改变的(talloc只改变了它的元数据中的父节点)。但一个常见的用法是将函数调用结果赋值给一个另外的变量,这样的话通过原来的指针访问变量应当被避免,除非必须如此。在这种情况下推荐使用talloc_move()来过继context。由于它将源指针设为NULL,这样避免了指针被意外释放,也避免了旧变量在父节点被更改的情况下被错误的使用。

4、动态类型
使用C语言进行泛型编程是非常困难的,这里没有像面向对象语言一样的模板和继承关系,也没有动态类型系统。因此,使用这种语言进行泛型编程的方法一般是将一个变量转换为void*类型,将其通过一个泛型函数传递给具体的回调函数。

void generic_function(callback_fn cb, void *pvt)
{/* do some stuff and call the callback */cb(pvt);
}void specific_callback(void *pvt)
{struct specific_struct *data;data = (struct specific_struct*)pvt;/* ... */
}void specific_function()
{struct specific_struct data;generic_function(callback, &data);
}

无论在编译器编译还是代码运行时,系统都无法检查传递的参数类型是否正确。将一个错误类型的数据传递给回调函数将会造成不可预知的结果(不一定导致程序崩溃)。

每一个talloc context都包含一个名字,并且此名字在任何时候都是可用的,因此,在变量的类型经过转换而无法分辨的时候,它可以用来帮助我们分辨context的类型。

虽然context的名称可以被设置为任意字符串,建议将其设置为变量类型的名字。

推荐使用talloc()或者talloc_array(或者它的变体)中的一个函数用来创建context,它们会自动将context名称设置为变量类型。

可以通过使用两个相似的函数来同时做到类型检查和类型转换:

talloc_get_type()
talloc_get_type_abort()

talloc的动态类型的一种典型的应用就是:用于判断向回调函数传递的参数是否非法,若是,则程序将会中止。如下:

void foo_callback(void *pvt)
{struct foo *data = talloc_get_type_abort(pvt, struct foo);/* ... */
}int do_foo()
{struct foo *data = talloc_zero(NULL, struct foo);/* ... */return generic_function(foo_callback, data);
}

假如,在编写服务端程序时,我们可能希望在开发环境中出现此种情况时中止程序(来确保错误不被忽视),而在生产环境下去尝试从错误中恢复。可以通过向talloc注册一个自定义的abort函数:

void my_abort(const char *reason)
{fprintf(stderr, "talloc abort: %s\n", reason);
#ifdef ABORT_ON_TYPE_MISMATCHabort();
#endif
}

此时talloc_get_type_abort()函数的效果将会变成这样:

talloc_set_abort_fn(my_abort);TALLOC_CTX *ctx = talloc_new(NULL);
char *str = talloc_get_type_abort(ctx, char);
if (str == NULL) {/* recovery code */
}
/* talloc abort: ../src/main.c:25: Type mismatch:name[talloc_new: ../src/main.c:24] expected[char] */

5、析构函数
开发者可以为talloc context添加一个析构函数,用于在释放talloc context时,析构函数可以做一些相关联的事情。

示例:
比如,创建一个动态链表,在释放元素前,需要先确认它已经从链表中被移除了。一般来说,这需要两个先后完成的动作:将其从链表中移除,然后释放内存。但是,使用talloc时,可以通过设置析构函数将元素从链表中移除,而talloc_free()完成内存的释放。

析构函数如下:

int list_remove(struct list_el *el)
{/* remove element from the list */
}

设置析构函数:

struct list_el* list_insert(TALLOC_CTX *mem_ctx,struct list_el *where,void *ptr)
{struct list_el *el = talloc(mem_ctx, struct list_el);el->data = ptr;/* insert into list */talloc_set_destructor(el, list_remove);return el;
}

释放内存:

struct list_el* list_insert_free(TALLOC_CTX *mem_ctx,struct list_el *where,void *ptr)
{struct list_el *el = NULL;el = list_insert(mem_ctx, where, ptr);talloc_steal(el, ptr);return el;
}

6、内存池
内存池是一块固定大小的预申请好的内存空间,在需要申请新内存时,将从内存池中分配,而不是从系统中分配新的内存。内存池是通过创建一个指向预申请内存空间区域内的指针来实现的,这使得内存池无法扩容,否则会改变指针的位置——原来指向它内部的指针将变为无效指针。因此,在使用内存池时,需要评估好它所需要的内存空间。

Talloc库包含了自行实现的内存池,在初始化一个内存池context时,使用talloc_pool()函数。

talloc内存池具有以下几个属性:

如果从一个内存池中申请内存,则将从内存池中分配所需的内存;
如果一个context是内存池的子context,则它将使用内存池的空间。
如果内存池的剩余空间不够,则将创建一个新的非内存池context,独立于内存池之外。

/* 为内存池申请1KiB内存 */
TALLOC_CTX *pool_ctx = talloc_pool(NULL, 1024);/* 从内存池中取走512B, 内存池中还剩下512B */
void *ptr = talloc_size(pool_ctx, 512);/* 1024B > 512B, 这将在内存池之外创建一个新的talloc chunk */
void *ptr2 = talloc_size(ptr, 1024);/* 内存池中还有512可用字节,这将从中再取走200B. */
void *ptr3 = talloc_size(ptr, 200);/* 这将销毁context 'ptr3' 但是内存并没有被释放, 内存池中可用的内存大小将会增加到 512B. */
talloc_free(ptr3);/* 这将同时释放 'pool_ctx' 和 'ptr2' 的内存. */
talloc_free(pool_ctx);

如果一个talloc内存池的子节点更改了它的父节点,则整块内存池都不能释放,直到此子节点被释放。

TALLOC_CTX *mem_ctx = talloc_new(NULL);
TALLOC_CTX *pool_ctx = talloc_pool(NULL, 1024);
struct foo *foo = talloc(pool_ctx, struct foo);/* mem_ctx 不在内存池中 */
talloc_steal(mem_ctx, foo);/* pool_ctx 被标记已经被释放, 但这块内存并没有被释放, 再次访问pool_ctx这块内存将产生一个错误 */
talloc_free(pool_ctx);/* 这时才会释放pool_ctx的内存. */
talloc_free(mem_ctx);

原文:https://blog.csdn.net/yuyeanci/article/details/53314089
Talloc下载地址:https://www.samba.org/ftp/talloc/
Linux man page:https://linux.die.net/man/3/talloc
https://talloc.samba.org/talloc/doc/html/index.html

Talloc内存池介绍相关推荐

  1. c++——内存池介绍

    1.默认内存管理函数的不足 利用默认的内存管理操作符new/delete和函数malloc()/free()在堆上分配和释放内存会有一些额外的开销. 系统在接收到分配一定大小内存的请求时,首先查找内部 ...

  2. C++ 内存池介绍与经典内存池的实现

    代码编译运行环境:VS2012+Debug+Win32 文章目录 1.默认内存管理函数的不足 2.内存池简介 2.1 内存池的定义 2.2 内存池的优点 2.3 内存池的分类 3.经典的内存池技术 3 ...

  3. 【项目设计】高并发内存池(一)[项目介绍|内存池介绍|定长内存池的实现]

  4. 内存池:申请内存与释放内存

    1.内存池简介 内存池是池化技术中的一种形式.通常我们在编写程序的时候回使用 new delete 这些关键字来向操作系统申请内存,而这样造成的后果就是每次申请内存和释放内存的时候,都需要和操作系统的 ...

  5. 【项目设计】高并发内存池

    文章目录 项目介绍 内存池介绍 定长内存池的实现 高并发内存池整体框架设计 threadcache threadcache整体设计 threadcache哈希桶映射对齐规则 threadcacheTL ...

  6. 从入门到掉坑:Go 内存池/对象池技术介绍

    作者:deryzhou,腾讯 PCG 后台开发工程师 Go 中怎么实现内存池,直接用 map 可以吗?常用库里 GroupCache.BigCache 的内存池又是怎么实现的?有没有坑?对象池又是什么 ...

  7. java内存区域及静态常量池、运行时常量池介绍

    java内存区域介绍 我们先来介绍下虚拟机运行时数据区的结构: 我们项目中的每一个线程在运行时,都会有拥有自己独立的栈数据和程序计数器.程序计数器可以看作字节码命令的指示器,记录了下个需要执行的字节码 ...

  8. java 内存池_内存池技术介绍(图文并茂,非常清楚)

    看到一篇关于内存池技术的介绍文章,受益匪浅,转贴至此. 6.1 自定义内存池性能优化的原理 如前所述,读者已经了解到"堆"和"栈"的区别.而在编程实践中,不可避 ...

  9. boost pool内存池库使用简要介绍

    2019独角兽企业重金招聘Python工程师标准>>> 我厂内存次神马的一般都自己实现.我曾经也自己写过一个demo(mempool).后来发现boost库里面有一个内存池库boos ...

  10. android内存池,两种常见的内存管理方法:堆和内存池

    描述 本文导读 在程序运行过程中,可能产生一些数据,例如,串口接收的数据,ADC采集的数据.若需将数据存储在内存中,以便进一步运算.处理,则应为其分配合适的内存空间,数据处理完毕后,再释放相应的内存空 ...

最新文章

  1. 解决使用Spring Boot、Multipartfile实现上传提示无法找到文件的问题
  2. SpringCloud Ribbon实战(三)
  3. 中国建筑行业2020年度回顾及未来展望
  4. shiro subject.getprincipal()为null_(变强、变秃)Java从零开始之Shiro安全框架
  5. 查看Oracle实例的EM端口
  6. VUE---不连接后端的登录验证规则方法
  7. python基于udp的网络聊天室再用tkinter显示_Python实现网络聊天室的示例代码(支持多人聊天与私聊)...
  8. beeline安装_Hive 系列 之 简介与安装
  9. 设置SUID用于提权或降权
  10. Go 语言的垃圾回收算法被吹过头?与Java比如何?
  11. maven工程下管理module发布到SVN注意
  12. 学会这一招,轻松将PDF转网页HTML,快来码住
  13. Windows消息大全(转载)
  14. css3多变形,CSS3 clip-path polygon图形构建与动画变换二三事
  15. LE Coded PHY和LE Uncoded PHY
  16. 红外额温枪方案(包含原理图和PCB文件)
  17. 负载均衡器/LB - 学习/实践
  18. mysql 锁机制与原理详解
  19. java中编写一个三角形类,java 三角形类Triangle java 三角形类 Triangle的用法详解
  20. 【仿美团点餐App】—— 首页(一)

热门文章

  1. LOL_2D局域网小游戏(Qt)
  2. 常见计算机故障维修实验报告,实训报告12微机常见故障处理实训报告
  3. 删除非系统盘的msdia80.dll文件以及出现的dllregisterserver调用失败错误代码0x80004005问题
  4. VARCHART XGantt 甘特图中的日期和夏令时
  5. springboot实现第三方qq登入
  6. Ubuntu 10.04 使用libfetion
  7. 海康大华等传统摄像机的RTSP流通过拉转GB/T28181输出级联到国标流媒体服务平台
  8. 海康威视Linux下SDK开发(Ubuntu16.04 QT5.10)
  9. duilib 添加自定义list一例
  10. 小马哥----山寨高仿小米5 图片1:1机型 机型曝光 与真假鉴别方法