實例分析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





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章