`
wqtn22
  • 浏览: 101059 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

erlang NIF部分接口实现(二)类型系统和内存分配接口

    博客分类:
  • erts
 
阅读更多

NIF的内存管理接口为enif_alloc/enif_free。

erl_nif.c

 

void* enif_alloc(size_t size)

{

    return erts_alloc_fnf(ERTS_ALC_T_NIF, (Uint) size);

}

erl_alloc.h

 

 

ERTS_ALC_INLINE

void *erts_alloc_fnf(ErtsAlcType_t type, Uint size)

{

    return (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(

ERTS_ALC_T2N(type),

erts_allctrs[ERTS_ALC_T2A(type)].extra,

size);

}

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

 

void enif_free(void* ptr)

{

    erts_free(ERTS_ALC_T_NIF, ptr);

}

 

void erts_free(ErtsAlcType_t type, void *ptr)

{

    (*erts_allctrs[ERTS_ALC_T2A(type)].free)(

ERTS_ALC_T2N(type),

erts_allctrs[ERTS_ALC_T2A(type)].extra,

ptr);

}

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

 

 

 

NIF的类型系统接口大同小异,基本上对于每种类型,都有一对make和get接口,稍微特殊的是binary类型。

首先来看NIF的利用进程堆分配内存的接口,它们是make类函数均要使用到的:

 

static ERTS_INLINE Eterm* alloc_heap(ErlNifEnv* env, unsigned need)

{

    Eterm* hp = env->hp;

    env->hp += need;

    if (env->hp <= env->hp_end) {

return hp;

    }

    /* env的堆来自于其附着的进程的堆, 若env的堆有足够大的空间,则直接在堆内分配,否则将扩大堆 */

    return alloc_heap_heavy(env, need, hp);

}

static Eterm* alloc_heap_heavy(ErlNifEnv* env, unsigned need, Eterm* hp)

{    

    env->hp = hp;

    if (env->heap_frag == NULL) {       

ASSERT(HEAP_LIMIT(env->proc) == env->hp_end);

HEAP_TOP(env->proc) = env->hp;

    }

    else {

env->heap_frag->used_size = hp - env->heap_frag->mem;

ASSERT(env->heap_frag->used_size <= env->heap_frag->alloc_size);

    }

    hp = erts_heap_alloc(env->proc, need, MIN_HEAP_FRAG_SZ);

    /* 此处扩大进程的堆 */

    env->heap_frag = MBUF(env->proc);

    env->hp = hp + need;

    env->hp_end = env->heap_frag->mem + env->heap_frag->alloc_size;

 

    return hp;

}

 

Eterm*erts_heap_alloc(Process* p, Uint need, Uint xtra)

{

 

    ErlHeapFragment* bp;

    Eterm* htop;

    Uint n;

 

 

    n = need + xtra;

    bp = MBUF(p);

    if (bp != NULL && need <= (bp->alloc_size - bp->used_size)) {

Eterm* ret = bp->mem + bp->used_size;

bp->used_size += need;

return ret;

    }

    /* 进程的堆在开始时是和进程栈连在一起的,当堆不断扩大,直到不足时,分配器将为堆产生一个新的堆内存片段,之后的内存分配都将在新的堆内存片段上进行,这也是一种懒惰方法 */

 

 

    bp = (ErlHeapFragment*)

ERTS_HEAP_ALLOC(ERTS_ALC_T_HEAP_FRAG, ERTS_HEAP_FRAG_SIZE(n));

    /* 分配新的堆内存片段,使用ERTS_ALC_T_HEAP_FRAG对应的ERTS_ALC_A_EHEAP分配器分配内存,它也是一个通过alloc_util框架实现的内存分配器 */

 

 

    htop = HEAP_TOP(p);

    if (htop < HEAP_LIMIT(p)) {

*htop = make_pos_bignum_header(HEAP_LIMIT(p)-htop-1);

HEAP_TOP(p) = HEAP_LIMIT(p);

    }

 

    bp->next = MBUF(p);

    MBUF(p) = bp;

    /* 更新进程的堆内存片段信息,堆内存片段是一个单向列表,这也保证了进程堆的自由扩大 */

    bp->alloc_size = n;

    bp->used_size = need;

    MBUF_SIZE(p) += n;

    bp->off_heap.first = NULL;

    bp->off_heap.overhead = 0;

    return bp->mem;

}

 

#define ERTS_HEAP_ALLOC(Type, Size) \

     erts_alloc((Type), (Size))

 

ERTS_ALC_INLINE void *erts_alloc(ErtsAlcType_t type, Uint size)
{
    void *res;
    res = (*erts_allctrs[ERTS_ALC_T2A(type)].alloc)(
ERTS_ALC_T2N(type),
erts_allctrs[ERTS_ALC_T2A(type)].extra,
size);
    if (!res)
erts_alloc_n_enomem(ERTS_ALC_T2N(type), size);
    return res;
}
对于一些常见的类型,其类型构建过程如下:
ERL_NIF_TERM enif_make_int(ErlNifEnv* env, int i)
{
#if SIZEOF_INT == ERTS_SIZEOF_ETERM
    return IS_SSMALL(i) ? make_small(i) : small_to_big(i,alloc_heap(env,2));
#elif (SIZEOF_LONG == ERTS_SIZEOF_ETERM) || \
  (SIZEOF_LONG_LONG == ERTS_SIZEOF_ETERM)
    return make_small(i);
#endif
}
对于64位系统,无需为int分配内存,直接将数据内容放置在ERL_NIF_TERM中即可,对于32位大数字才需要分配内存,可见erlang虚拟机对内存分配已经到了抠门的地步了。
ERL_NIF_TERM enif_make_string(ErlNifEnv* env, const char* string, ErlNifCharEncoding encoding)
{
    return enif_make_string_len(env, string, sys_strlen(string), encoding);
}
ERL_NIF_TERM enif_make_string_len(ErlNifEnv* env, const char* string, size_t len, ErlNifCharEncoding encoding)
{
    Eterm* hp = alloc_heap(env,len*2);
    ASSERT(encoding == ERL_NIF_LATIN1);
    return erts_bld_string_n(&hp,NULL,string,len);
}
Eterm erts_bld_string_n(Uint **hpp, Uint *szp, const char *str, Sint len)
{
    Eterm res = THE_NON_VALUE;
    Sint i = len;
    if (szp)
*szp += len*2;
    if (hpp) {
res = NIL;
while (--i >= 0) {
   res = CONS(*hpp, make_small((byte) str[i]), res);
   *hpp += 2;
}
    }
    return res;
}
string也是列表,因此需要分配两倍内存,一个用于保存指针,另一个用于保存数据,构建string时,需要逆序遍历原先的字符串数组。
ERL_NIF_TERM enif_make_tuple(ErlNifEnv* env, unsigned cnt, ...)
{
    Eterm* hp = alloc_heap(env,cnt+1);
    Eterm ret = make_tuple(hp);
    va_list ap;

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

va_start(ap,cnt);
while (cnt--) {
   *last = make_list(hp);
   *hp = va_arg(ap,Eterm);
   last = ++hp;
   ++hp;
}
va_end(ap);
*last = NIL;
return ret;
    }
}
list分配时也需要分配两倍内存,过程与string类似。
binary的构建有些特殊,分为两个阶段:分配与构造。
binary分配:
unsigned char* enif_make_new_binary(ErlNifEnv* env, size_t size,
   ERL_NIF_TERM* termp)
{
    flush_env(env);
    *termp = new_binary(env->proc, NULL, size);
    /* 分配新的binary */
    cache_env(env);
    return binary_bytes(*termp);
}
Eterm new_binary(Process *p, byte *buf, Uint len)
{
    ProcBin* pb;
    Binary* bptr;

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

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

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

    OH_OVERHEAD(&(MSO(p)), pb->size / sizeof(Eterm));
    return make_binary(pb);
}

ERTS_GLB_INLINE Binary *erts_bin_nrml_alloc(Uint size)

{

    Uint bsize = ERTS_SIZEOF_Binary(size) + CHICKEN_PAD;

    void *res;

    res = erts_alloc(ERTS_ALC_T_BINARY, bsize);

    ERTS_CHK_BIN_ALIGNMENT(res);

    return (Binary *) res;

}

binary构造:

Eterm enif_make_binary(ErlNifEnv* env, ErlNifBinary* bin)

{

    if (bin->bin_term != THE_NON_VALUE) {

return bin->bin_term;

    }

    else if (bin->ref_bin != NULL) {

Binary* bptr = bin->ref_bin;

ProcBin* pb;

Eterm bin_term;

/* !! Copy-paste from new_binary() !! */

pb = (ProcBin *) alloc_heap(env, PROC_BIN_SIZE);

pb->thing_word = HEADER_PROC_BIN;

pb->size = bptr->orig_size;

pb->next = MSO(env->proc).first;

MSO(env->proc).first = (struct erl_off_heap_header*) pb;

pb->val = bptr;

pb->bytes = (byte*) bptr->orig_bytes;

pb->flags = 0;

OH_OVERHEAD(&(MSO(env->proc)), pb->size / sizeof(Eterm));

bin_term = make_binary(pb);

if (erts_refc_read(&bptr->refc, 1) == 1) {

   /* Total ownership transfer */

   bin->ref_bin = NULL;

   bin->bin_term = bin_term;

}

return bin_term;

    }

    else {

flush_env(env);

bin->bin_term = new_binary(env->proc, bin->data, bin->size);

cache_env(env);

return bin->bin_term;

    }

}

同样,对于get系列接口,也是大同小异的:
int enif_get_int(ErlNifEnv* env, Eterm term, int* ip)
{
#if SIZEOF_INT ==  ERTS_SIZEOF_ETERM
    return term_to_Sint(term, (Sint*)ip);
#elif (SIZEOF_LONG ==  ERTS_SIZEOF_ETERM) || \
  (SIZEOF_LONG_LONG ==  ERTS_SIZEOF_ETERM)
    Sint i;
    if (!term_to_Sint(term, &i) || i < INT_MIN || i > INT_MAX) {
return 0;
    }
    *ip = (int) i;
    return 1;
#else
#  error Unknown word size 
#endif     
}

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

int enif_get_string(ErlNifEnv *env, ERL_NIF_TERM list, char* buf, unsigned len,

   ErlNifCharEncoding encoding)

{

    Eterm* listptr;

    int n = 0;

 

    ASSERT(encoding == ERL_NIF_LATIN1);

    if (len < 1) {

return 0;

    }

    while (is_not_nil(list)) {    

if (is_not_list(list)) {

   buf[n] = '\0';

   return 0;

}

listptr = list_val(list);

 

if (!is_byte(*listptr)) {

   buf[n] = '\0';

   return 0;

}

buf[n++] = unsigned_val(*listptr);

if (n >= len) {

   buf[n-1] = '\0'; /* truncate */

   return -len;

}

list = CDR(listptr);

    }

    buf[n] = '\0';

    return n + 1;

}

取得string时,将重新拷贝一份。
int enif_get_tuple(ErlNifEnv* env, Eterm tpl, int* arity, const Eterm** array)
{
    Eterm* ptr;
    if (is_not_tuple(tpl)) {
return 0;
    }
    ptr = tuple_val(tpl);
    *arity = arityval(*ptr);
    *array = ptr+1;
    return 1;
}
元组的取得较为简单,仅仅向调用者返回元组成员个数和元组成员数组。
int enif_get_list_cell(ErlNifEnv* env, Eterm term, Eterm* head, Eterm* tail)
{
    Eterm* val;
    if (is_not_list(term)) return 0;
    val = list_val(term);
    *head = CAR(val);
    *tail = CDR(val);
    return 1;
}
列表的取得过程比较麻烦,需要调用者遍历列表,不断对列表调用enif_get_list_cell直到最后一个元素。
int enif_inspect_binary(ErlNifEnv* env, Eterm bin_term, ErlNifBinary* bin)
{
    ErtsAlcType_t allocator = is_proc_bound(env) ? ERTS_ALC_T_TMP : ERTS_ALC_T_NIF;
    union {
struct enif_tmp_obj_t* tmp;
byte* raw_ptr;
    }u;
    u.tmp = NULL;
    bin->data = erts_get_aligned_binary_bytes_extra(bin_term, &u.raw_ptr, allocator,
   sizeof(struct enif_tmp_obj_t));
    if (bin->data == NULL) {
return 0;
    }
    if (u.tmp != NULL) {
u.tmp->allocator = allocator;
u.tmp->next = env->tmp_obj_list;
u.tmp->dtor = &aligned_binary_dtor;
env->tmp_obj_list = u.tmp;
    }
    bin->bin_term = bin_term;
    bin->size = binary_size(bin_term);
    bin->ref_bin = NULL;
    ADD_READONLY_CHECK(env, bin->data, bin->size); 
    return 1;
}
byte*erts_get_aligned_binary_bytes_extra(Eterm bin, byte** base_ptr, ErtsAlcType_t allocator, unsigned extra)
{
    byte* bytes;
    Eterm* real_bin;
    Uint byte_size;
    Uint offs = 0;
    Uint bit_offs = 0;
    
    if (is_not_binary(bin)) {
return NULL;
    }
    byte_size = binary_size(bin);
    real_bin = binary_val(bin);
    if (*real_bin == HEADER_SUB_BIN) {
ErlSubBin* sb = (ErlSubBin *) real_bin;
if (sb->bitsize) {
   return NULL;
}
offs = sb->offs;
bit_offs = sb->bitoffs;
real_bin = binary_val(sb->orig);
    }
    if (*real_bin == HEADER_PROC_BIN) {
bytes = ((ProcBin *) real_bin)->bytes + offs;
    } else {
bytes = (byte *)(&(((ErlHeapBin *) real_bin)->data)) + offs;
    }
    if (bit_offs) {
byte* buf = (byte *) erts_alloc(allocator, byte_size + extra);
*base_ptr = buf;
buf += extra;
erts_copy_bits(bytes, bit_offs, 1, buf, 0, 1, byte_size*8);
bytes = buf;
    }
    return bytes;
}
通常取得binary的时不会有数据拷贝,除非遇到通过匹配切分出的binary,这也是一种懒惰复制方法。

主要的类型系统接口已经分析完了,对于每个类型,都有一个get和make函数,binary类例外,get函数会取得类型的数据内容,make函数会为类型分配内存,并构造类型。

 

 

分享到:
评论

相关推荐

    rustler编写erlang nif

    在Erlang生态系统中,Native Implemented Functions (NIFs) 是一种机制,允许开发者用其他语言(如C、C++或Rust)编写性能关键部分的代码,然后在Erlang虚拟机(VM)中调用。Rustler是一个库,专门用于简化使用Rust...

    erlang nif test

    - **内存管理**:Erlang VM负责内存的分配和回收,但NIF代码需要遵循特定的规则来操作Erlang术语,如使用`enif_alloc`分配内存,然后用`enif_free`释放。 - **错误传播**:Erlang的错误处理机制与C不同,所以要确保...

    erl_nif_rustler_过程宏写法

    在Erlang中,`NIF (Native Implemented Functions)` 是一种机制,允许开发者使用其他语言(如C或Rust)...通过理解`rustler_macro`的过程宏和Erlang NIF的生命周期,开发者可以轻松地将Rust功能集成到Erlang系统中。

    erl_nif 扩展erlang的另外一种方法

    在Erlang中,为了实现与C或其他低级语言的高效交互,Erlang提供了一个名为`erl_nif`的接口。本文将深入探讨`erl_nif`,了解它是如何扩展Erlang的功能,并讨论如何使用它来提升性能。 `erl_nif`(Erlang NIF,Native...

    Windows下使用NIF扩展Erlang完整例子

    Windows下使用NIF扩展Erlang完整例子,包含nif工程项目,erlang引用例子。 配套文章:http://blog.csdn.net/mycwq/article/details/17527485

    erlang -c语言程序接口.pdf

    Erlang与C语言接口主要通过两种方式实现:一种是将C语言编写的代码直接嵌入到Erlang程序中;另一种是通过进程间通信(IPC)的方式让Erlang与C语言程序进行交互。Erlang倾向于采用第二种方式,即进程间通信,来与C语言...

    erlang整理的一些心得和lunix查看cpu和内存信息的方法

    在IT领域,Erlang是一种强大的并发编程语言,主要用于构建高可用性、容错性和分布式系统。它以其轻量级进程、消息传递和热代码升级等特性而闻名。Unix(包括其衍生版本如Linux)是广泛使用的操作系统,具有丰富的...

    rustler —用于创建Erlang NIF函数的安全Rust桥-Rust开发

    示例Rustler是一个用于以安全的Rust代码编写Erlang NIF的库。 这意味着应该没有任何办法。 入门指南 示例Rustler是一个用于以安全的Rust代码编写Erlang NIF的库。 这意味着应该没有办法使BEAM(Erlang VM)崩溃。 该...

    erlang-rust-nif:在 Rust 中实现的 Erlang NIF 示例

    Rust 中的 Erlang NIF 这是一个如何在 Rust 中实现 NIF 的示例。 它对我有用,也可能对你有用,但如果它吃掉了你的作业,请不要生气。 虽然这将是可行的写现实世界的代码下面这个例子,因为整个erl_nif.h接口可用...

    erlang的timer和实现机制

    此外,提供的"erlang的timer和实现机制.pdf"和"更多erlang资料下载.txt"也是进一步学习和研究的好资源。 总之,Erlang的timer模块是其强大并发能力的一个体现,通过熟练掌握这一部分,开发者可以构建出更加高效和...

    Erlang应用程序接口(视频,1/4)

    这是第一卷。 在2008 CN Erlounge III的“Erlang应用程序接口”讲演的视频。PPT等其它资料在这里: http://blog.csdn.net/aimingoo/archive/2009/01/14/3777765.aspx 有关信息参见: ...

    rustler:用于创建Erlang NIF函数的Safe Rust桥

    Rustler会自动处理内存管理、错误处理和类型转换,确保NIF函数在Erlang VM中正确运行。 在Erlang应用中,这些NIF函数可以通过标准的Erlang/Elixir接口调用,就像调用普通的Erlang模块一样。这样,开发者可以在保持...

    erlang版本的protobuf(erl_protobuffs)

    2. **内存管理**:Erlang的垃圾回收机制与C++或Java不同,`erl_protobuffs`充分利用了这一特性,优化了内存管理和对象生命周期,减少了不必要的内存开销。 3. **集成性**:由于Erlang主要用于构建分布式系统,`erl_...

    enif_protobuf:使用enif(Erlang nif)的Google Protobuf实现

    **enif_protobuf** 是一个基于 **Erlang ...总之,enif_protobuf是Erlang社区对Google Protobuf的一种强大实现,通过Erlang NIF机制实现了高性能的数据序列化和反序列化,简化了Erlang系统与其他系统之间的数据交换。

    erlang-tc:Erlang NIF用于threshold_crypto

    - **Erlang模块**:Erlang代码部分,定义了与NIF交互的接口函数,这些函数在Erlang代码中可以像普通函数一样调用。 - **NIF接口定义**:Erlang模块中会有`erlang:nifEXPORTS`列表,列出所有暴露给Erlang的NIF函数...

    erlpuzzle:Erlang NIF 到 libpuzzle,一个快速查找视觉相似图像的库

    ErlPuzzle - Erlang 的 libpuzzle NIF Puzzle 库旨在快速找到视觉上相似的图像(gif、png、jpg),即使它们已被调整大小、重新压缩、重新着色或稍微修改。 该库是免费的、轻量级的但非常快速、可配置、易于使用,...

    erlang 部分源码

    5. **类型系统** 虽然Erlang是动态类型的语言,但它也有一些静态类型检查的特性。查看源码,可以理解Erlang如何在运行时进行类型检查。 6. **模式匹配** 模式匹配是Erlang的一大特色,它在函数调用、解构等场景中...

    Erlang应用程序接口 - Topic at CN Erlounge III

    我在Erlounge III大会上的讲演PPT。 相关的视频在这里: http://groups.google.com/group/erlang-china/browse_thread/thread/2154c39503795edc

Global site tag (gtag.js) - Google Analytics