Erlang中文论坛

标题: erlang NIF部分接口实现(二)类型系统和内存分配接口 [打印本页]

作者: dreamxyp    时间: 2013-10-21 19:06
标题: erlang NIF部分接口实现(二)类型系统和内存分配接口
NIF的内存管理接口为enif_alloc/enif_free。
erl_nif.c

  1. void* enif_alloc(size_t size)
  2. {
  3.     return erts_alloc_fnf(ERTS_ALC_T_NIF, (Uint) size);
  4. }
  5. erl_alloc.h


  6. ERTS_ALC_INLINE
  7. void *erts_alloc_fnf(ErtsAlcType_t type, Uint size)
  8. {
  9.     return (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(
  10.         ERTS_ALC_T2N(type),
  11.         erts_allctrs[ERTS_ALC_T2A(type)].extra,
  12.         size);
  13. }
复制代码


可以看出NIF的内存分配将直接通过ERTS_ALC_T_NIF对应的虚拟机内存分配器ERTS_ALC_A_DRIVER分配内存,ERTS_ALC_A_DRIVER也是利用alloc_util框架实现的内存分配器,详细文档请阅读http://www.erlang.org/doc/man/erts_alloc.html

  1. void enif_free(void* ptr)
  2. {
  3.     erts_free(ERTS_ALC_T_NIF, ptr);
  4. }

  5. void erts_free(ErtsAlcType_t type, void *ptr)
  6. {
  7.     (*erts_allctrs[ERTS_ALC_T2A(type)].free)(
  8.         ERTS_ALC_T2N(type),
  9.         erts_allctrs[ERTS_ALC_T2A(type)].extra,
  10.         ptr);
  11. }
复制代码


对于NIF的内存释放过程也是如此,erlang虚拟机内存管理是一个非常庞杂的系统,此处将不进行分析,读者可以简单地将其看作malloc/free接口(虽然其实现要复杂的多)。



NIF的类型系统接口大同小异,基本上对于每种类型,都有一对make和get接口,稍微特殊的是binary类型。
首先来看NIF的利用进程堆分配内存的接口,它们是make类函数均要使用到的:

  1. static ERTS_INLINE Eterm* alloc_heap(ErlNifEnv* env, unsigned need)
  2. {
  3.     Eterm* hp = env->hp;
  4.     env->hp += need;
  5.     if (env->hp <= env->hp_end) {
  6.         return hp;
  7.     }
  8.     /* env的堆来自于其附着的进程的堆, 若env的堆有足够大的空间,则直接在堆内分配,否则将扩大堆 */
  9.     return alloc_heap_heavy(env, need, hp);
  10. }
  11. static Eterm* alloc_heap_heavy(ErlNifEnv* env, unsigned need, Eterm* hp)
  12. {   
  13.     env->hp = hp;
  14.     if (env->heap_frag == NULL) {      
  15.         ASSERT(HEAP_LIMIT(env->proc) == env->hp_end);
  16.         HEAP_TOP(env->proc) = env->hp;       
  17.     }
  18.     else {
  19.         env->heap_frag->used_size = hp - env->heap_frag->mem;
  20.         ASSERT(env->heap_frag->used_size <= env->heap_frag->alloc_size);
  21.     }
  22.     hp = erts_heap_alloc(env->proc, need, MIN_HEAP_FRAG_SZ);
  23.     /* 此处扩大进程的堆 */
  24.     env->heap_frag = MBUF(env->proc);
  25.     env->hp = hp + need;
  26.     env->hp_end = env->heap_frag->mem + env->heap_frag->alloc_size;

  27.     return hp;
  28. }

  29. Eterm*erts_heap_alloc(Process* p, Uint need, Uint xtra)
  30. {

  31.     ErlHeapFragment* bp;
  32.     Eterm* htop;
  33.     Uint n;


  34.     n = need + xtra;
  35.     bp = MBUF(p);
  36.     if (bp != NULL && need <= (bp->alloc_size - bp->used_size)) {
  37.         Eterm* ret = bp->mem + bp->used_size;
  38.         bp->used_size += need;
  39.         return ret;
  40.     }
  41.     /* 进程的堆在开始时是和进程栈连在一起的,当堆不断扩大,直到不足时,分配器将为堆产生一个新的堆内存片段,之后的内存分配都将在新的堆内存片段上进行,这也是一种懒惰方法 */


  42.     bp = (ErlHeapFragment*)
  43.         ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP_FRAG, ERTS_HEAP_FRAG_SIZE(n));
  44.     /* 分配新的堆内存片段,使用ERTS_ALC_T_HEAP_FRAG对应的ERTS_ALC_A_EHEAP分配器分配内存,它也是一个通过alloc_util框架实现的内存分配器 */


  45.     htop = HEAP_TOP(p);
  46.     if (htop < HEAP_LIMIT(p)) {
  47.         *htop = make_pos_bignum_header(HEAP_LIMIT(p)-htop-1);
  48.         HEAP_TOP(p) = HEAP_LIMIT(p);
  49.     }

  50.     bp->next = MBUF(p);
  51.     MBUF(p) = bp;
  52.     /* 更新进程的堆内存片段信息,堆内存片段是一个单向列表,这也保证了进程堆的自由扩大 */
  53.     bp->alloc_size = n;
  54.     bp->used_size = need;
  55.     MBUF_SIZE(p) += n;
  56.     bp->off_heap.first = NULL;
  57.     bp->off_heap.overhead = 0;
  58.     return bp->mem;
  59. }

  60. #define ERTS_HEAP_ALLOC(Type, Size)                                        \
  61.      erts_alloc((Type), (Size))

  62. ERTS_ALC_INLINE void *erts_alloc(ErtsAlcType_t type, Uint size)
  63. {
  64.     void *res;
  65.     res = (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(
  66.         ERTS_ALC_T2N(type),
  67.         erts_allctrs[ERTS_ALC_T2A(type)].extra,
  68.         size);
  69.     if (!res)
  70.         erts_alloc_n_enomem(ERTS_ALC_T2N(type), size);
  71.     return res;
  72. }
复制代码


对于一些常见的类型,其类型构建过程如下:
  1. ERL_NIF_TERM enif_make_int(ErlNifEnv* env, int i)
  2. {
  3. #if SIZEOF_INT == ERTS_SIZEOF_ETERM
  4.     return IS_SSMALL(i) ? make_small(i) : small_to_big(i,alloc_heap(env,2));
  5. #elif (SIZEOF_LONG == ERTS_SIZEOF_ETERM) || \
  6.   (SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM)
  7.     return make_small(i);
  8. #endif
  9. }
复制代码



对于64位系统,无需为int分配内存,直接将数据内容放置在ERL_NIF_TERM中即可,对于32位大数字才需要分配内存,可见erlang虚拟机对内存分配已经到了抠门的地步了。
  1. ERL_NIF_TERM enif_make_string(ErlNifEnv* env, const char* string, ErlNifCharEncoding encoding)
  2. {
  3.     return enif_make_string_len(env, string, sys_strlen(string), encoding);
  4. }
  5. ERL_NIF_TERM enif_make_string_len(ErlNifEnv* env, const char* string, size_t len, ErlNifCharEncoding encoding)
  6. {
  7.     Eterm* hp = alloc_heap(env,len*2);
  8.     ASSERT(encoding == ERL_NIF_LATIN1);
  9.     return erts_bld_string_n(&hp,NULL,string,len);
  10. }
  11. Eterm erts_bld_string_n(Uint **hpp, Uint *szp, const char *str, Sint len)
  12. {
  13.     Eterm res = THE_NON_VALUE;
  14.     Sint i = len;
  15.     if (szp)
  16.         *szp += len*2;
  17.     if (hpp) {
  18.         res = NIL;
  19.         while (--i >= 0) {
  20.             res = CONS(*hpp, make_small((byte) str[i]), res);
  21.             *hpp += 2;
  22.         }
  23.     }
  24.     return res;
  25. }
复制代码




string也是列表,因此需要分配两倍内存,一个用于保存指针,另一个用于保存数据,构建string时,需要逆序遍历原先的字符串数组。
  1. ERL_NIF_TERM enif_make_tuple(ErlNifEnv* env, unsigned cnt, ...)
  2. {
  3.     Eterm* hp = alloc_heap(env,cnt+1);
  4.     Eterm ret = make_tuple(hp);
  5.     va_list ap;

  6.     *hp++ = make_arityval(cnt);
  7.     va_start(ap,cnt);
  8.     while (cnt--) {
  9.         *hp++ = va_arg(ap,Eterm);          
  10.     }
  11.     va_end(ap);
  12.     return ret;
  13. }
复制代码



tuple是复合类型,仅仅需要在堆上分配tuple的元组个数+1个Eterm即可,一个用于保存tuple本身,其它的用于记录tuple每个成员。
  1. ERL_NIF_TERM enif_make_list(ErlNifEnv* env, unsigned cnt, ...)
  2. {
  3.     if (cnt == 0) {
  4.         return NIL;
  5.     }
  6.     else {
  7.         Eterm* hp = alloc_heap(env,cnt*2);
  8.         Eterm ret = make_list(hp);
  9.         Eterm* last = &ret;
  10.         va_list ap;

  11.         va_start(ap,cnt);
  12.         while (cnt--) {
  13.             *last = make_list(hp);
  14.             *hp = va_arg(ap,Eterm);
  15.             last = ++hp;
  16.             ++hp;
  17.         }
  18.         va_end(ap);
  19.         *last = NIL;
  20.         return ret;
  21.     }
  22. }
复制代码





list分配时也需要分配两倍内存,过程与string类似。
binary的构建有些特殊,分为两个阶段:分配与构造。
binary分配:
  1. unsigned char* enif_make_new_binary(ErlNifEnv* env, size_t size,
  2.                                     ERL_NIF_TERM* termp)
  3. {
  4.     flush_env(env);
  5.     *termp = new_binary(env->proc, NULL, size);
  6.     /* 分配新的binary */
  7.     cache_env(env);
  8.     return binary_bytes(*termp);
  9. }
  10. Eterm new_binary(Process *p, byte *buf, Uint len)
  11. {
  12.     ProcBin* pb;
  13.     Binary* bptr;

  14.     if (len <= ERL_ONHEAP_BIN_LIMIT) {
  15.         ErlHeapBin* hb = (ErlHeapBin *) HAlloc(p, heap_bin_size(len));
  16.         hb->thing_word = header_heap_bin(len);
  17.         hb->size = len;
  18.         if (buf != NULL) {
  19.             sys_memcpy(hb->data, buf, len);
  20.         }
  21.         return make_binary(hb);
  22.     }
  23.     /* 对于小于ERL_ONHEAP_BIN_LIMIT(64)字节的binary,可以直接分配在进程堆上 */

  24.     bptr = erts_bin_nrml_alloc(len);
  25.     /* 对于大于ERL_ONHEAP_BIN_LIMIT(64)字节的binary,将通过ERTS_ALC_T_BINARY对应的ERTS_ALC_A_BINARY分配器进行分配, ERTS_ALC_A_BINARY也是利用alloc_util框架实现的内存分配器 */
  26.     bptr->flags = 0;
  27.     bptr->orig_size = len;
  28.     erts_refc_init(&bptr->refc, 1);
  29.     if (buf != NULL) {
  30.         sys_memcpy(bptr->orig_bytes, buf, len);
  31.     }

  32.     /* 然后构建一个进程binary的结构保存刚刚分配的大额binary */
  33.     pb = (ProcBin *) HAlloc(p, PROC_BIN_SIZE);
  34.     pb->thing_word = HEADER_PROC_BIN;
  35.     pb->size = len;
  36.     pb->next = MSO(p).first;
  37.     MSO(p).first = (struct erl_off_heap_header*)pb;
  38.     pb->val = bptr;
  39.     pb->bytes = (byte*) bptr->orig_bytes;
  40.     pb->flags = 0;

  41.     OH_OVERHEAD(&(MSO(p)), pb->size / sizeof(Eterm));
  42.     return make_binary(pb);
  43. }
  44. ERTS_GLB_INLINE Binary *erts_bin_nrml_alloc(Uint size)
  45. {
  46.     Uint bsize = ERTS_SIZEOF_Binary(size) + CHICKEN_PAD;
  47.     void *res;
  48.     res = erts_alloc(ERTS_ALC_T_BINARY, bsize);
  49.     ERTS_CHK_BIN_ALIGNMENT(res);
  50.     return (Binary *) res;
  51. }
复制代码



binary构造:

  1. Eterm enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin)
  2. {
  3.     if (bin->bin_term != THE_NON_VALUE) {
  4.         return bin->bin_term;
  5.     }
  6.     else if (bin->ref_bin != NULL) {
  7.         Binary* bptr = bin->ref_bin;
  8.         ProcBin* pb;
  9.         Eterm bin_term;
  10.        
  11.         /* !! Copy-paste from new_binary() !! */
  12.         pb = (ProcBin *) alloc_heap(env, PROC_BIN_SIZE);
  13.         pb->thing_word = HEADER_PROC_BIN;
  14.         pb->size = bptr->orig_size;
  15.         pb->next = MSO(env->proc).first;
  16.         MSO(env->proc).first = (struct erl_off_heap_header*) pb;
  17.         pb->val = bptr;
  18.         pb->bytes = (byte*) bptr->orig_bytes;
  19.         pb->flags = 0;
  20.        
  21.         OH_OVERHEAD(&(MSO(env->proc)), pb->size / sizeof(Eterm));
  22.         bin_term = make_binary(pb);       
  23.         if (erts_refc_read(&bptr->refc, 1) == 1) {
  24.             /* Total ownership transfer */
  25.             bin->ref_bin = NULL;
  26.             bin->bin_term = bin_term;
  27.         }
  28.         return bin_term;
  29.     }
  30.     else {
  31.         flush_env(env);
  32.         bin->bin_term = new_binary(env->proc, bin->data, bin->size);
  33.         cache_env(env);
  34.         return bin->bin_term;
  35.     }
  36. }
复制代码


同样,对于get系列接口,也是大同小异的:
  1. int enif_get_int(ErlNifEnv* env, Eterm term, int* ip)
  2. {
  3. #if SIZEOF_INT ==  ERTS_SIZEOF_ETERM
  4.     return term_to_Sint(term, (Sint*)ip);
  5. #elif (SIZEOF_LONG ==  ERTS_SIZEOF_ETERM) || \
  6.   (SIZEOF_LONG_LONG ==  ERTS_SIZEOF_ETERM)
  7.     Sint i;
  8.     if (!term_to_Sint(term, &i) || i < INT_MIN || i > INT_MAX) {
  9.         return 0;
  10.     }
  11.     *ip = (int) i;
  12.     return 1;
  13. #else
  14. #  error Unknown word size
  15. #endif     
  16. }
复制代码



对于64位系统和32位系统小数,直接可以从Eterm中提取数据内容,对于32位大数,需要一个较复杂的转换过程。

  1. int enif_get_string(ErlNifEnv *env, ERL_NIF_TERM list, char* buf, unsigned len,
  2.                     ErlNifCharEncoding encoding)
  3. {
  4.     Eterm* listptr;
  5.     int n = 0;

  6.     ASSERT(encoding == ERL_NIF_LATIN1);
  7.     if (len < 1) {
  8.         return 0;
  9.     }
  10.     while (is_not_nil(list)) {             
  11.         if (is_not_list(list)) {
  12.             buf[n] = '\0';
  13.             return 0;
  14.         }
  15.         listptr = list_val(list);

  16.         if (!is_byte(*listptr)) {
  17.             buf[n] = '\0';
  18.             return 0;
  19.         }
  20.         buf[n++] = unsigned_val(*listptr);
  21.         if (n >= len) {
  22.             buf[n-1] = '\0'; /* truncate */
  23.             return -len;
  24.         }
  25.         list = CDR(listptr);
  26.     }
  27.     buf[n] = '\0';
  28.     return n + 1;
  29. }
复制代码


取得string时,将重新拷贝一份。
  1. int enif_get_tuple(ErlNifEnv* env, Eterm tpl, int* arity, const Eterm** array)
  2. {
  3.     Eterm* ptr;
  4.     if (is_not_tuple(tpl)) {
  5.         return 0;
  6.     }
  7.     ptr = tuple_val(tpl);
  8.     *arity = arityval(*ptr);
  9.     *array = ptr+1;
  10.     return 1;
  11. }
复制代码



元组的取得较为简单,仅仅向调用者返回元组成员个数和元组成员数组。
  1. int enif_get_list_cell(ErlNifEnv* env, Eterm term, Eterm* head, Eterm* tail)
  2. {
  3.     Eterm* val;
  4.     if (is_not_list(term)) return 0;
  5.     val = list_val(term);
  6.     *head = CAR(val);
  7.     *tail = CDR(val);
  8.     return 1;
  9. }
复制代码



列表的取得过程比较麻烦,需要调用者遍历列表,不断对列表调用enif_get_list_cell直到最后一个元素。
  1. int enif_inspect_binary(ErlNifEnv* env, Eterm bin_term, ErlNifBinary* bin)
  2. {
  3.     ErtsAlcType_t allocator = is_proc_bound(env) ? ERTS_ALC_T_TMP : ERTS_ALC_T_NIF;
  4.     union {
  5.         struct enif_tmp_obj_t* tmp;
  6.         byte* raw_ptr;
  7.     }u;
  8.     u.tmp = NULL;
  9.     bin->data = erts_get_aligned_binary_bytes_extra(bin_term, &u.raw_ptr, allocator,
  10.                                                     sizeof(struct enif_tmp_obj_t));
  11.     if (bin->data == NULL) {
  12.         return 0;
  13.     }
  14.     if (u.tmp != NULL) {
  15.         u.tmp->allocator = allocator;
  16.         u.tmp->next = env->tmp_obj_list;
  17.         u.tmp->dtor = &aligned_binary_dtor;
  18.         env->tmp_obj_list = u.tmp;
  19.     }
  20.     bin->bin_term = bin_term;
  21.     bin->size = binary_size(bin_term);
  22.     bin->ref_bin = NULL;
  23.     ADD_READONLY_CHECK(env, bin->data, bin->size);
  24.     return 1;
  25. }
  26. byte*erts_get_aligned_binary_bytes_extra(Eterm bin, byte** base_ptr, ErtsAlcType_t allocator, unsigned extra)
  27. {
  28.     byte* bytes;
  29.     Eterm* real_bin;
  30.     Uint byte_size;
  31.     Uint offs = 0;
  32.     Uint bit_offs = 0;
  33.    
  34.     if (is_not_binary(bin)) {
  35.         return NULL;
  36.     }
  37.     byte_size = binary_size(bin);
  38.     real_bin = binary_val(bin);
  39.     if (*real_bin == HEADER_SUB_BIN) {
  40.         ErlSubBin* sb = (ErlSubBin *) real_bin;
  41.         if (sb->bitsize) {
  42.             return NULL;
  43.         }
  44.         offs = sb->offs;
  45.         bit_offs = sb->bitoffs;
  46.         real_bin = binary_val(sb->orig);
  47.     }
  48.     if (*real_bin == HEADER_PROC_BIN) {
  49.         bytes = ((ProcBin *) real_bin)->bytes + offs;
  50.     } else {
  51.         bytes = (byte *)(&(((ErlHeapBin *) real_bin)->data)) + offs;
  52.     }
  53.     if (bit_offs) {
  54.         byte* buf = (byte *) erts_alloc(allocator, byte_size + extra);
  55.         *base_ptr = buf;
  56.         buf += extra;
  57.         erts_copy_bits(bytes, bit_offs, 1, buf, 0, 1, byte_size*8);       
  58.         bytes = buf;
  59.     }
  60.     return bytes;
  61. }
复制代码





通常取得binary的时不会有数据拷贝,除非遇到通过匹配切分出的binary,这也是一种懒惰复制方法。
主要的类型系统接口已经分析完了,对于每个类型,都有一个get和make函数,binary类例外,get函数会取得类型的数据内容,make函数会为类型分配内存,并构造类型。






欢迎光临 Erlang中文论坛 (https://bbs.erldoc.com/) Powered by Discuz! X3.3