1. 程式人生 > >LWIP資料包管理學習

LWIP資料包管理學習

------------------pbuf.h------------------- struct pbuf{     struct pbuf* next;          //構成pbuf連結串列時指向下一個pbuf結構
    void* payload;               //資料指標,指向該pbuf所記錄的資料區域。     u16_t tot_len;     
         //當前pbuf及其後續所有pbuf中包含的資料總長度
    u16_t len;                      //當前pbuf的資料長度
    u8_t   type;               
   //當前pbuf的型別
    u8_t   flags;                  //狀態位,未用到
    u16_t ref;                     //指向該pbuf的指標數,即該pbuf被引用的次數。

}
 一個pbuf結構的內容就是這樣子的,後面一大塊空白的就是資料區。注意,payload的箭頭沒有指向資料區的開頭,這裡有一個offset。這個一段offset是為了在LWIP各層中填充首部用的,offset還有大用處,後面會講。如果資料比較多,一個pbuf裝不下,就得用多個pbuf來裝,並且串成連結串列。像這樣

注意了,只有這個連結串列頭的pbuf有offset,其他pbuf沒有。這個應該不難理解吧。這裡還要說一點,並不是一定資料多了就要做成連結串列,得看pbuf中的type。
pbuf這個結構裡值得重點講的就是type了。
----------pbuf.h--------------------------
type enum{
    PBUF_RAM,
    PBUF_ROM,
    PBUF_REF,
    PBUF_POOL
}pbuf_type;
--------------------------------------------
為什麼要分型別呢?這是為了效率和節省空間。pbuf是個不小的資料結構呢,要知道後面的資料區動輒幾十上百位元組,所以你要用pbuf就得動用記憶體申請。記憶體有記憶體池和記憶體堆兩種,該申請哪一種呢。type就是為了描述這個而來,PBUF_RAM就是從記憶體堆中申請,PBUF_POOL就是從記憶體池中申請。PBUF_ROM和PBUF_REF是從哪申請呢?LWIP的作者為了儘量減少資料的拷貝帶來的開銷,兼顧靈活性。在這整了兩種特別的pbuf型別,剛剛上面講的PBUF_RAM PBUF_POOL從記憶體池記憶體堆中申請的時候是連後面的資料區都一起申請的,所以申請得到的記憶體是一大塊的。而PBUF_REF PBUF_ROM 申請的就只是pbuf結構體,沒有申請後面的資料區。為什麼要這樣做呢,這是考慮到在使用過程中,比如說我有一個全域性的陣列,裡面存了我要傳送的資料,如果我申請的是PBUF_RAM PBUF_POOL的話,我就得把這個全域性陣列的內容拷貝到申請的pbuf的資料區中,前面說了拷貝資料是個很大的開銷,我們想要避免。所以就有了這個PBUF_REF PBUF_ROM,你申請得到這種型別的pbuf後,只要把裡面的payload指標指向你的資料就好了,不用拷貝。這兩者的區別只在於PBUF_REF指向的資料必須在RAM空間內,而PBUF_ROM指向的資料必須在ROM空間內。然後我們要說說PBUF_RAM PBUF_POOL的區別,PBUF_RAM的話從記憶體堆申請,不管你資料多大,如果申請成功就一個pbuf。PBUF_POOL的話,如果資料太大就會成一個連結串列。從這點上來講,為了成功率還是儘量用POOL吧,畢竟如果記憶體堆用得多了,可能比較碎片化,你想再申請個大的pbuf估計會不成功。


最後講講操作pbuf的函式,重點就是申請和釋放兩個函數了。
struct pbuf* pbuf_alloc(pbuf_layer layer,u16_t length,pbuf_type type)
這個是申請函式,length就是資料長度啦,如果你申請的不帶資料區的PBUF_REF PBUF_ROM的話,這個長度也沒太大意義了。type就上面講的型別啦。
layer是啥東東?原來LWIP在申請pbuf的時候還要說明要申請的是那一層的pbuf,不同層的pbuf的offset也不同。
-------------pbuf,h----------------------
typedef enum{
    PBUF_TRANSPORT,    //傳輸層
    PBUF_IP,                    //網路層
    PBUF_LINK,                //鏈路層
    PBUF_RAM                //原始層,不預留空間
}pbuf_layer;
-------------------------------------------------
釋放函式是u8_t pbuf_free(struct pbuf* p)
pbuf的釋放就要小心了,這裡要講到pbuf裡的ref這個變數,這個變數記錄了這個pbuf被引用的次數,在pbuf剛申請的時候整個變數是1。至於pbuf是怎麼被引用的,暫時還不清楚。如果pbuf是串成連結串列的話,連結串列裡表頭之後的每一個pbuf的ref都是1,就是說每一個pbuf都被前面一個pbuf引用。pbuf在釋放的時候,就會把pbuf的ref值減1,然後函式會判斷ref減完之後是不是變成0,如果是0就會根據pbuf的型別呼叫記憶體池或者記憶體堆回收函式進行回收。前面剛講,如果是連結串列的話,每一個pbuf都被前面的引用,所以如果你把表頭回收了,第二個pbuf的ref就得減1,那它就變成0了,又回收,第二個被回收了,第三個的ref就得減1,又變0,又回收,這樣連鎖反應下去就把整條連結串列都收回去了。然後這裡就有個很危險的事了,這個pbuf_free函式,你要傳的引數是連結串列頭指標,假如你傳的不是連結串列頭而是指向連結串列中間的某個pbuf
的指標,那就出大事了,這個pbuf_free可不會幫你檢查是不是連結串列頭,這樣子勢必會導致一部分pbuf沒被回收,意味著一部分記憶體池就這樣死了,以後沒辦法用了。
pbuf_realloc函式在相應pbuf(連結串列)尾部釋放一定的空間,將資料包pbuf中的資料長度減少為某個長度值。對於PBUF_RAM型別的pbuf,函式將呼叫記憶體堆管理中介紹到的mem_realloc函式,釋放這些多餘的空間。對於其他三種類型的pbuf,該函式只是修改pbuf中的長度欄位值,並不釋放對應的記憶體池空間。
pbuf_header函式用於調整pbuf的payload指標(向前或向後移動一定位元組數),在前面也說到過,在pbuf的資料區前可能會預留一些協議首部空間,而pbuf被建立時,payload指標是指向資料區的,為了實現對這些預留空間的操作,可以呼叫函式pbuf_header使payload指標指向資料區前的首部欄位,這就為各層對資料包首部的操作提供了方便。當然,進行這個操作的時候,len和tot_len欄位值也會隨之更新。
pbuf_take函式用於向pbuf的資料區域拷貝資料。pbuf_copy函式用於將一個任何型別的pbuf中的資料拷貝到一個PBUF_RAM型別的pbuf中。pbuf_chain函式用於連線兩個pbuf(連結串列)為一個pbuf連結串列。pbuf_ref函式用於將pbuf中的值加1。