实例分析Erlang二进制(Binary)匹配的内部机制

Erlang的二进制操作很简单很强大,《Erlang二进制创建的内部机制和优化》一文介绍了binary的创建,现在就来探索它的逆过程,匹配操作。

下面是一个简单实例,它的功能是binary_to_list,把二进制流按字节匹配出来生成list。

%% test.erl
-module(test).
-export([t/1,t2/4]).


t(<<H,T/binary>>) ->
    [H|t(T)];
t(<<>>) -> [].

将test.erl生成test.S,看看它执行了哪些指令。
erlc +\'S\' test.erl
%% test.S
{function, t, 1, 2}.
  {label,1}.
    {line,[{location,"test.erl",4}]}.
    {func_info,{atom,test},{atom,t},1}.
  {label,2}.
    %% bs_start_match2,匹配初始化
    {test,bs_start_match2,{f,1},1,[{x,0},0],{x,0}}.
    %% bs_get_integer2,从二制制流中取出整数
    {test,bs_get_integer2,
          {f,3},
          1,
          [{x,0},
           {integer,8},
           1,
           {field_flags,[{anno,[4,{file,"test.erl"}]},unsigned,big]}],
          {x,1}}.
    {'%',{bin_opt,[4,{file,"test.erl"}]}}.
    {test,bs_test_unit,{f,4},[{x,0},8]}.
    {allocate,1,2}.
    {move,{x,1},{y,0}}.
    {line,[{location,"test.erl",5}]}.
    {call,1,{f,2}}.
    {test_heap,2,1}.
    {put_list,{y,0},{x,0},{x,0}}.
    {deallocate,1}.
    return.
  {label,3}.
    {test,bs_test_tail2,{f,4},[{x,0},0]}.
    {move,nil,{x,0}}.
    return.
  {label,4}.
    {bs_context_to_binary,{x,0}}.
    {jump,{f,1}}.
上面的匹配操作是通过 bs_start_match2 和 bs_get_integer2 这两条指令实现的。

打开文件 $ERL_TOP/erts/emulator/beam/ops.tab 文件,找到映射的相关指令:
bs_start_match2 Fail=f ica X Y D => jump Fail
bs_start_match2 Fail Bin X Y D => i_bs_start_match2 Bin Fail X Y D
i_bs_start_match2 r f I I d
i_bs_start_match2 x f I I d
i_bs_start_match2 y f I I d


# Fetching integers from binaries.
bs_get_integer2 Fail=f Ms=rx Live=u Sz=sq Unit=u Flags=u Dst=d => \
			gen_get_integer2(Fail, Ms, Live, Sz, Unit, Flags, Dst)


i_bs_get_integer_small_imm r I f I d
i_bs_get_integer_small_imm x I f I d
i_bs_get_integer_imm r I I f I d
i_bs_get_integer_imm x I I f I d
i_bs_get_integer f I I d
i_bs_get_integer_8 r f d
i_bs_get_integer_8 x f d
i_bs_get_integer_16 r f d
i_bs_get_integer_16 x f d
i_bs_get_integer_32 r f I d
i_bs_get_integer_32 x f I d
bs_start_match2对应i_bs_start_match2
bs_get_integer2对应的有很多,在我们实例中是按字节匹配的,所以这里会选择 i_bs_get_integer_8

现在我们来看 beam_emu.c 中的具体实现。

在文件beam_emu.c中搜索i_bs_start_match2,经过一些参数处理后就goto do_start_match,
由于beam_emu.c中的代码比较多,这里就不贴了,它最后会调用erl_bits.c中的erts_bs_start_match_2函数。
Eterm
erts_bs_start_match_2(Process *p, Eterm Binary, Uint Max)
{
    Eterm Orig;
    Uint offs;
    Uint* hp;
    Uint NeededSize;
    ErlBinMatchState *ms;
    Uint bitoffs;
    Uint bitsize;
    Uint total_bin_size;
    ProcBin* pb;


    ASSERT(is_binary(Binary));
    total_bin_size = binary_size(Binary);
    if ((total_bin_size >> (8*sizeof(Uint)-3)) != 0) {
        return THE_NON_VALUE;
    }
    // 测试输出
    erts_fprintf(stderr, "start match, total_bin_size:%ld\n", total_bin_size);
    NeededSize = ERL_BIN_MATCHSTATE_SIZE(Max);
    // 为match context二进制分配内存空间
    hp = HeapOnlyAlloc(p, NeededSize);
    ms = (ErlBinMatchState *) hp;
    ERTS_GET_REAL_BIN(Binary, Orig, offs, bitoffs, bitsize);
    pb = (ProcBin *) boxed_val(Orig);
    if (pb->thing_word == HEADER_PROC_BIN && pb->flags != 0) {
        erts_emasculate_writable_binary(pb);
    }
    // 初始化match context二进制
    ms->thing_word = HEADER_BIN_MATCHSTATE(Max);
    (ms->mb).orig = Orig;
    (ms->mb).base = binary_bytes(Orig);
    (ms->mb).offset = ms->save_offset[0] = 8 * offs + bitoffs;
    (ms->mb).size = total_bin_size * 8 + (ms->mb).offset + bitsize;
    return make_matchstate(ms);
}
到此为止 i_bs_start_match2 指令执行完毕。

接着来看 i_bs_get_integer_8 经过一系列参数处理后,最终执行的代码是:
do_bs_get_integer_8: {
    ErlBinMatchBuffer *_mb;
    Eterm _result;
    _mb = ms_matchbuffer(bs_get_integer8_context);
    如果剩余不足8位,则匹配失败。
    if (_mb->size - _mb->offset < 8) {
        ClauseFail();
    }
    // 如果offset不是8的倍数,则不能按正常的字节读取,跳转到erl_bits.c文件中的 erts_bs_get_integer_2函数中处理
    // 我们的这个例子中不会有这种情况,暂时不分析这部分。
    if (BIT_OFFSET(_mb->offset) != 0) {
        _result = erts_bs_get_integer_2(c_p, 8, 0, _mb);
    } else {
        // 测试输出
        erts_fprintf(stderr, "matched value:%d\n", _mb->base[BYTE_OFFSET(_mb->offset)]);
        // #MARK_A
        _result = make_small(_mb->base[BYTE_OFFSET(_mb->offset)]);
        _mb->offset += 8;
    }
    StoreBifResult(1, _result);
}
上面#MARK_A处就是匹配操作中取值的内部实现,也是本例匹配操作的关键和精华,虽然它只有两行代码。
要理解这两行代码,先要了解match context,
关于match context二进制的介绍和两个结构体,可参见《Erlang Binary的内部结构和分类介绍》一文。
先重温一下ErlBinMatchBuffer的结构:
// This structure represents a binary to be matched.  
typedef struct erl_bin_match_buffer {  
    Eterm orig;         /* 指向原始二进制数据包(ProcBin或ErlHeapBin) */  
    byte* base;         /* 直接指向二进制数据(ProcBin->bytes或ErlHeapBin->data) */  
    Uint offset;        /* Offset in bits. */  
    size_t size;        /* Size of binary in bits. */  
} ErlBinMatchBuffer;

在#MARK_A处,BYTE_OFFSET(_mb->offset)是把位偏量移转换成字节偏移量,
由于base指针是直接指向二进制数据的,现在就可以很方便的从中取出匹配到的值,即_mb->base[N],N为字节偏移量。
匹配完成后,位偏移量要自增一个字节(_mb->offset += 8),让它定位到下一个匹配点。
_result = make_small(_mb->base[BYTE_OFFSET(_mb->offset)]);

以上贴出的代码中,一些地方我们加上了一些测试输出,现在就来运行一下这个实例,看看输出结果:
Eshell V5.10.2  (abort with ^G)
1> test:t(<<1,2,3>>).  
start match, total_bin_size:3
matched value:1
matched value:2
matched value:3
...(此处省略N行与本文内容无关的输出)
[1,2,3]

END





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值