设为首页收藏本站

Erlang中文论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

查看: 28090|回复: 0

erlang NIF部分接口实现(三)持久资源

[复制链接]
发表于 2013-10-21 19:17:56 | 显示全部楼层 |阅读模式
持久资源是NIF中一类非常有用接口,可以把资源看成各种数据结构描述符,然后在各个模块间传递数据结构,从而使得写erlang程序像写c程序一样,弥补一些erlang程序在性能上的不足。
使用持久资源,需要首先创建持久资源类,这些工作可以在NIF被load时进行。

  1. ErlNifResourceType*
  2. enif_open_resource_type(ErlNifEnv* env,
  3.                         const char* module_str,
  4.                         const char* name_str,
  5.                         ErlNifResourceDtor* dtor,
  6.                         ErlNifResourceFlags flags,
  7.                         ErlNifResourceFlags* tried)
  8. {
  9.     ErlNifResourceType* type = NULL;
  10.     ErlNifResourceFlags op = flags;
  11.     Eterm module_am, name_am;

  12.     ASSERT(erts_smp_thr_progress_is_blocking());
  13.     ASSERT(module_str == NULL); /* for now... */
  14.     module_am = make_atom(env->mod_nif->mod->module);
  15.     name_am = enif_make_atom(env, name_str);

  16.     type = find_resource_type(module_am, name_am);
  17.     if (type == NULL) {
  18.         if (flags & ERL_NIF_RT_CREATE) {
  19.             type = erts_alloc(ERTS_ALC_T_NIF, sizeof(struct enif_resource_type_t));
  20.             type->dtor = dtor;
  21.             type->module = module_am;
  22.             type->name = name_am;
  23.             erts_refc_init(&type->refc, 1);
  24.             type->owner = env->mod_nif;
  25.             type->prev = &resource_type_list;
  26.             type->next = resource_type_list.next;
  27.             type->next->prev = type;
  28.             type->prev->next = type;
  29.             op = ERL_NIF_RT_CREATE;
  30.         }
  31.     }
  32.     else {
  33.         if (flags & ERL_NIF_RT_TAKEOVER) {       
  34.             steal_resource_type(type);
  35.             op = ERL_NIF_RT_TAKEOVER;
  36.         }
  37.         else {
  38.             type = NULL;
  39.         }
  40.     }
  41.     if (type != NULL) {
  42.         type->owner = env->mod_nif;
  43.         type->dtor = dtor;
  44.         if (type->dtor != NULL) {
  45.             erts_refc_inc(&type->owner->rt_dtor_cnt, 1);
  46.         }
  47.         erts_refc_inc(&type->owner->rt_cnt, 1);   
  48.     }
  49.     if (tried != NULL) {
  50.         *tried = op;
  51.     }
  52.     return type;
  53. }
复制代码


该接口用于创建一类持久资源,资源名为参数name_str,为资源分配资源描述符ErlNifResourceType,并为其记录资源析构器dtor,为了能使得持久资源也使用erlang的gc机制,需要为资源提供一个析构器回调,使得在资源没有被使用时,由gc正确释放。持久资源的析构器需要用户自行实现。
  1. void* enif_alloc_resource(ErlNifResourceType* type, size_t size)
  2. {
  3.     Binary* bin = erts_create_magic_binary(SIZEOF_ErlNifResource(size), &nif_resource_dtor);
  4.     ErlNifResource* resource = ERTS_MAGIC_BIN_DATA(bin);
  5.     resource->type = type;
  6.     erts_refc_inc(&bin->refc, 1);
  7. #ifdef DEBUG
  8.     erts_refc_init(&resource->nif_refc, 1);
  9. #endif
  10.     erts_refc_inc(&resource->type->refc, 2);
  11.     return resource->data;
  12. }
  13. #define SIZEOF_ErlNifResource(SIZE) (offsetof(ErlNifResource,data) + (SIZE))
复制代码



由于持久资源相当于一段内存,因此其最重要的功能即为内存分配,enif_alloc_resource用于分配资源所占的内存。
  1. ERTS_GLB_INLINE Binary *erts_create_magic_binary(Uint size, void (*destructor)(Binary *))
  2. {
  3.     Uint bsize = ERTS_MAGIC_BIN_SIZE(size);
  4.     Binary* bptr = erts_alloc_fnf(ERTS_ALC_T_BINARY, bsize);
  5.     if (!bptr)
  6.         erts_alloc_n_enomem(ERTS_ALC_T2N(ERTS_ALC_T_BINARY), bsize);
  7.     ERTS_CHK_BIN_ALIGNMENT(bptr);
  8.     bptr->flags = BIN_FLAG_MAGIC;
  9.     bptr->orig_size = ERTS_MAGIC_BIN_ORIG_SIZE(size);
  10.     erts_refc_init(&bptr->refc, 0);
  11.     ERTS_MAGIC_BIN_DESTRUCTOR(bptr) = destructor;
  12.     return bptr;
  13. }
  14. #define ERTS_MAGIC_BIN_SIZE(Sz) (offsetof(ErtsMagicBinary,magic_bin_data) + (Sz))
复制代码



可以看到持久资源的内存分配器,也是ERTS_ALC_A_BINARY,但是其分配的内存包括三个部分:
1.ErtsMagicBinary:用于将资源融入ERTS_ALC_A_BINARY分配器;
2.ErlNifResource:用于将建立持久资源的抽象层;
3.持久资源本身:持久资源所占内存部分。
这里有个值得注意的地方,在erts_create_magic_binary调用erts_alloc_fnf时,返回的不是一个ErtsMagicBinary结构,而是一个Binary结构。
  1. typedef struct binary {
  2.     ERTS_INTERNAL_BINARY_FIELDS
  3.     SWord orig_size;
  4.     char orig_bytes[1]; /* to be continued */
  5. } Binary;
  6. typedef struct {
  7.     ERTS_INTERNAL_BINARY_FIELDS
  8.     SWord orig_size;
  9.     void (*destructor)(Binary *);
  10.     char magic_bin_data[1];
  11. } ErtsMagicBinary;
  12. #define ERTS_INTERNAL_BINARY_FIELDS                                \
  13.     UWord flags;                                                        \
  14.     erts_refc_t refc;                                                \
  15.     ERTS_BINARY_STRUCT_ALIGNMENT
复制代码



ErtsMagicBinary与Binary在拥有公共的成员,包括flags,refc等,而flags中记录了数据结构的真正类型,erts_create_magic_binary将此处分配的Binary初始化为BIN_FLAG_MAGIC类型,这是一种多态的手法。
除了分配内存,enif_alloc_resource还将资源的析构器设置为nif_resource_dtor:
  1. static void nif_resource_dtor(Binary* bin)
  2. {
  3.     ErlNifResource* resource = (ErlNifResource*) ERTS_MAGIC_BIN_DATA(bin);
  4.     ErlNifResourceType* type = resource->type;
  5.     ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == &nif_resource_dtor);

  6.     if (type->dtor != NULL) {
  7.         ErlNifEnv env;
  8.         pre_nif_noproc(&env, type->owner);
  9.         type->dtor(&env,resource->data);
  10.         post_nif_noproc(&env);
  11.     }
  12.     if (erts_refc_dectest(&type->refc, 0) == 0) {
  13.         ASSERT(type->next == NULL);
  14.         ASSERT(type->owner != NULL);
  15.         ASSERT(type->owner->mod == NULL);
  16.         steal_resource_type(type);
  17.         erts_free(ERTS_ALC_T_NIF, type);
  18.     }
  19. }
复制代码








可以看到,这个析构器是真正的外层融入gc的析构器,它从资源的 ErtsMagicBinary结构中取得ErlNifResource结构,利用这个结构内记录的资源真正的析构器释放资源。
enif_alloc_resource在为资源分配内存时,还需要为资源设置正确的类型,类型即为该函数的第一个参数。
资源可以通过make接口传递到erlang模块中:
  1. ERL_NIF_TERM enif_make_resource(ErlNifEnv* env, void* obj)
  2. {
  3.     ErlNifResource* resource = DATA_TO_RESOURCE(obj);
  4.     ErtsBinary* bin = ERTS_MAGIC_BIN_FROM_DATA(resource);
  5.     Eterm* hp = alloc_heap(env,PROC_BIN_SIZE);
  6.     return erts_mk_magic_binary_term(&hp, &MSO(env->proc), &bin->binary);
  7. }
  8. ERTS_GLB_INLINE Eterm
  9. erts_mk_magic_binary_term(Eterm **hpp, ErlOffHeap *ohp, Binary *mbp)
  10. {
  11.     ProcBin *pb = (ProcBin *) *hpp;
  12.     *hpp += PROC_BIN_SIZE;

  13.     ASSERT(mbp->flags & BIN_FLAG_MAGIC);

  14.     pb->thing_word = HEADER_PROC_BIN;
  15.     pb->size = 0;
  16.     pb->next = ohp->first;
  17.     ohp->first = (struct erl_off_heap_header*) pb;
  18.     pb->val = mbp;
  19.     pb->bytes = (byte *) mbp->orig_bytes;
  20.     pb->flags = 0;

  21.     erts_refc_inc(&mbp->refc, 1);

  22.     return make_binary(pb);   
  23. }
复制代码



首先需要在进程堆上分配一个ProcBin结构,使得包裹资源的Binary能够传递到进程执行流中去,注意,通常binary在小于64字节时,可以直接分配到进程堆上,但是由于资源并不是特定于进程的,所以应该由ERTS_ALC_A_BINARY分配器分配,而不是由ERTS_ALC_A_EHEAP分配器分配,其生命周期也与进程无关。另外需要注意的是,持久资源对应的ProcBin结构,其数据长度为0,用户在调用资源创建接口建立的资源,在控制台上的输出也是<<>>。


如果将来需要在某个时机取得资源,可以调用如下接口:
  1. int enif_get_resource(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifResourceType* type,
  2.                       void** objp)
  3. {
  4.     ProcBin* pb;
  5.     Binary* mbin;
  6.     ErlNifResource* resource;
  7.     if (!ERTS_TERM_IS_MAGIC_BINARY(term)) {
  8.         return 0;
  9.     }
  10.     pb = (ProcBin*) binary_val(term);
  11.     /*if (pb->size != 0) {       
  12.         return 0; / * Or should we allow "resource binaries" as handles? * /
  13.     }*/
  14.     mbin = pb->val;
  15.     resource = (ErlNifResource*) ERTS_MAGIC_BIN_DATA(mbin);
  16.     if (ERTS_MAGIC_BIN_DESTRUCTOR(mbin) != &nif_resource_dtor
  17.         || resource->type != type) {       
  18.         return 0;
  19.     }
  20.     *objp = resource->data;
  21.     return 1;
  22. }
  23. #define ERTS_MAGIC_BIN_DATA(BP) \
  24.   ((void *) ((ErtsBinary *) (BP))->magic_binary.magic_bin_data)
复制代码



binary本身是有多态机制的,在从binary中提取资源描述符时,也需要判断binary是否ErtsMagicBinary,可以通过
ERTS_TERM_IS_MAGIC_BINARY宏进行判断,资源描述符ErlNifResource是包裹在ErtsMagicBinary结构中的,而真正的资源数据结构又是包裹在ErlNifResource结构中的,就像剥洋葱,一层一层的剥去外层数据结构,最终到的持久资源本身的数据结构。
gc可以在正确的时机释放持久资源所占的内存了,如果用户需要主动释放内存,可以调用:
  1. void enif_release_resource(void* obj)
  2. {
  3.     ErlNifResource* resource = DATA_TO_RESOURCE(obj);
  4.     ErtsBinary* bin = ERTS_MAGIC_BIN_FROM_DATA(resource);

  5.     ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == &nif_resource_dtor);
  6. #ifdef DEBUG
  7.     erts_refc_dec(&resource->nif_refc, 0);
  8. #endif
  9.     if (erts_refc_dectest(&bin->binary.refc, 0) == 0) {
  10.         erts_bin_free(&bin->binary);
  11.     }
  12. }
复制代码



由于资源可以被共享,因此资源本身是有引用计数的,当引用计数减为0,资源就可以被真正释放了。
增加资源的引用计数可以通过如下接口:
  1. void enif_keep_resource(void* obj)
  2. {
  3.     ErlNifResource* resource = DATA_TO_RESOURCE(obj);
  4.     ErtsBinary* bin = ERTS_MAGIC_BIN_FROM_DATA(resource);

  5.     ASSERT(ERTS_MAGIC_BIN_DESTRUCTOR(bin) == &nif_resource_dtor);
  6. #ifdef DEBUG
  7.     erts_refc_inc(&resource->nif_refc, 1);
  8. #endif
  9.     erts_refc_inc(&bin->binary.refc, 2);
  10. }
复制代码




增加和减少资源的引用计数的次数必须匹配,否则可能引发问题。
至此,持久资源的主要接口的实现就介绍完了,用户使用时,可以先通过enif_open_resource_type建立资源类型的描述符,然后利用此描述符,使用enif_alloc_resource分配资源所占用的内存空间,使用enif_make_resource将资源导出到erlang模块层,在进程间传递资源描述符,资源再传回NIF时,可以通过enif_get_resource取回资源描述符中的资源数据结构,同时可以通过enif_keep_resource来共享资源,通过enif_release_resource来放弃使用资源,gc系统也会正确回收引用计数为0的资源,开发者再也不用担心内存没有被正确释放了。
持久资源为NIF的开发带来了极大的便利,用户可以将一些大规模的数据结构一次传入内存,生成一个资源描述符,然后在进程间传递资源描述符而不是资源数据本身,减轻每次资源数据拷贝的开销,同时持久资源也是线程安全的,写erlang程序也可以像写c程序一样高效了。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|Erldoc.com  

GMT+8, 2024-3-28 18:28 , Processed in 0.404184 second(s), 11 queries , File On.

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表