天天看點

Zephyr OS 中的簡化版網絡 Bufferdefine NET_BUF_SIMPLE(_size) \

本文介紹 Zephyr OS 中的簡化版網絡 Buffer。

轉自:https://github.com/tidyjiang8/zephyr-inside/blob/old/src/net/common/simply-buf.md

引言

緩沖池(Buffer Pool)是 Zephyr OS 中的兩個協定棧 uIP 和 yaip 所共用的資料結構。緩沖池分為兩類,分别是簡化版 Buffer 和完整版 Buffer。完整版 Buffer 的接口實作利用了簡單般 Buffer 提供的結構。我們先學習簡化版 Buffer。

相關代碼位于include/buf.h和net/buf.c。

Buffer 的定義

簡化版 Buffer 的定義如下:

struct net_buf_simple {

uint8_t *data;

uint16_t len;

const uint16_t size;

uint8_t __buf[0] __net_buf_align;

};

主要包含四個成員:

data:指向 buffer 中資料部分的起始位址。

len:buffer 中已存儲資料的長度,機關是位元組。

size:buffer 總共允許存儲的資料的長度,機關是位元組。

__buf:存儲資料的起始位址。

data 和 __buf 都指向資料部分的起始位址,但是它們可能不同。

還有一點需要注意,__buf[0] 是一個數組,其數組元素個數為 0,這樣的數組又叫做柔性數組,用來表示元素個數不确定的數組。柔性數組隻能出現在結構體中,其數組長度為 0,我們可以将其看做是數組中的占位符。

此外,Zephyr OS 還提供了一個宏。在編寫代碼時,我們應該使用該宏來定義一個簡單buffer。

define NET_BUF_SIMPLE(_size) \

((struct net_buf_simple *)(&(struct {        \
    struct net_buf_simple buf;           \
    uint8_t data[_size] __net_buf_align; \
}) {                                         \
    .buf.size = _size,                   \
}))
           

這是一個比較複雜的、少見的但卻非常巧妙的宏,我們逐漸分解。

先定義了一個匿名結構體:

struct { \

struct net_buf_simple buf; \

uint8_t data[_size] __net_buf_align; \

}

該匿名結構體包含兩個元素,一個是 strcut net_buf_simple,另一個是長度為 _size 的數組。我們需要關注它們在記憶體中的存儲情況(将 struct net_buf_simple “展開”):先存儲 data 指針,然後依次存儲 len、size 和 data[_size]。

再通過符合“&”擷取該匿名結構體的位址

&(struct { \

struct net_buf_simple buf; \

uint8_t data[_size] __net_buf_align; \

})

再将該匿名結構體的 buf 成員的 size 成員指派為 _size。

(&(struct { \

struct net_buf_simple buf; \

uint8_t data[_size] __net_buf_align; \

}) { \

.buf.size = _size, \

})

最後再将該匿名結構體的位址強制轉化為 struct net_buf_simple * 類型的指針

((struct net_buf_simple *)(&(struct { \

struct net_buf_simple buf; \

uint8_t data[_size] __net_buf_align; \

}) { \

.buf.size = _size, \

}))

舉個例子,我們進行如下定義:

struct net_buf_simple *my_buf = NET_BUF_SIMPLE(10);

那麼它所做之事就是:配置設定一片存儲空間,将該空間的起始位址指派給指針 my_buf, 該存儲空間依次存儲指向實際資料的指針 data、已使用 buffer 長度 len、buffer 可容納資料的總長度 size 和實際待存儲的資料 data[10]。

Buffer 的初始化

簡化版 Buffer 初始化函數如下:

static inline void net_buf_simple_init(struct net_buf_simple *buf,

size_t reserve_head)

{

// 将 data 指針指向資料(不包含資料的保留頭)的首位址

buf->data = buf->__buf + reserve_head;

// 初始化已使用資料(不包含資料的保留頭)的長度為 0。

buf->len = 0;

}

有兩個參數:

buf:一個指向需要初始化的 buffer 的指針。

reserve_head:由于特定場景的保留頭部的大小。

在實際編寫代碼時,Buffer 的定義和初始化必須配套使用,例如:

struct net_buf_simple *my_buf;

// 定義一個簡單buffer,其容量為 10 位元組

my_buf = NET_BUF_SIMPLE(10);

// 初始化這個buffer,将其前 2 個位元組作為保留的頭部。剩下的 8 位元組由于存放實際的資料

net_buf_simple_init(my_buf, 2);

Buffer 的記憶體模型

通過上面的分析,可以抽象出簡化版 Buffer 的模型,用一張圖表示。

圖:簡化版 Buffer 的記憶體模型

未考慮記憶體對齊問題

主要關注以下要點:

存儲的順序依次為描述 buffer 的結構體中各成員、buffer 的資料

描述 buffer 的結構體中各成員的含義

指針 data 指向已使用 buffer 的首位址

len 表示已使用 buffer 的長度,機關是位元組

size 表示 buffer 的總長度,機關是位元組

__buf 指向 buffer 的保留頭部的首位址,即 data[0] 的位址

buffer 的資料由三部分組成:

未使用的、保留的頭部 buffer (如果存在),即 reserved_head 所表示的空間

已使用的 buffer,即 len 所表示的空間

未使用的尾部的 buffer,即 tailroom 所表示的空間

資料部分可能有保留頭部(reserved_head > 0),也可能沒有保留頭部(reserved_head = 0)

Buffer 的接口

net_buf_simple_tail

static inline uint8_t *net_buf_simple_tail(struct net_buf_simple *buf)

{

return buf->data + buf->len;

}

擷取并傳回 buffer 的尾部(未使用部分)的首位址

net_buf_simple_headroom

size_t net_buf_simple_headroom(struct net_buf_simple *buf)

{

return buf->data - buf->__buf;

}

擷取并傳回 buffer 的頭部(保留的、未使用的空間)的大小

net_buf_simple_tailroom

size_t net_buf_simple_tailroom(struct net_buf_simple *buf)

{

return buf->size - net_buf_simple_headroom(buf) - buf->len;

}

擷取并傳回 buffer 的尾部(未使用空間)的大小

net_buf_simple_add

void *net_buf_simple_add(struct net_buf_simple *buf, size_t len)

{

uint8_t *tail = net_buf_simple_tail(buf);

NET_BUF_ASSERT(net_buf_simple_tailroom(buf) >= len);

buf->len += len;
return tail;
           

}

向 buffer 中添加資料前的準備工作:

判斷 buf 是否還有足夠的空間來儲存 len 個位元組的資料。

将 buf 中的 len 預增加

傳回未使用部分的首位址

這個函數存在一個安全隐患。NET_BUF_ASSERT 将判斷條件 net_buf_simple_tailroom(buf) >= len 是否正确,如果不正确,即 len 大于 buffer 中未使用部分的空間,它僅僅列印一條提示消息。也就是說,如果條件不正确,那麼向該 buf 中添加資料時将導緻緩沖越界!一個很嚴重的錯誤!!

net_buf_simple_add_u8

uint8_t *net_buf_simple_add_u8(struct net_buf_simple *buf, uint8_t val)

{

uint8_t *u8;

u8 = net_buf_simple_add(buf, 1);
*u8 = val;

return u8;
           

}

向 buf 中添加 1 個位元組的無符号整形資料。

net_buf_simple_add_le16

void net_buf_simple_add_le16(struct net_buf_simple *buf, uint16_t val)

{

val = sys_cpu_to_le16(val);

memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val));

}

向 buf 中添加 2 個位元組的 unsigned short 類型的資料,且該資料在 buf 中以小端的格式存儲。

net_buf_simple_add_be16

void net_buf_simple_add_be16(struct net_buf_simple *buf, uint16_t val)

{

NET_BUF_DBG(“buf %p val %u\n”, buf, val);

val = sys_cpu_to_be16(val);
memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val));
           

}

向 buf 中添加 2 個位元組的 unsigned short 類型的資料,且該資料在 buf 中以大端的格式存儲。

net_buf_simple_add_le32

void net_buf_simple_add_le32(struct net_buf_simple *buf, uint32_t val)

{

val = sys_cpu_to_le32(val);

memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val));

}

向 buf 中添加 4 個位元組的 unsigned int類型的資料,且該資料在 buf 中以小端的格式存儲。

net_buf_simple_add_be32

void net_buf_simple_add_be32(struct net_buf_simple *buf, uint32_t val)

{

val = sys_cpu_to_be32(val);

memcpy(net_buf_simple_add(buf, sizeof(val)), &val, sizeof(val));

}

向 buf 中添加 4 個位元組的 unsigned int類型的資料,且該資料在 buf 中以大端的格式存儲。

net_buf_simple_push

void *net_buf_simple_push(struct net_buf_simple *buf, size_t len)

{

NET_BUF_ASSERT(net_buf_simple_headroom(buf) >= len);

buf->data -= len;
buf->len += len;
return buf->data;
           

}

該函數也是為向buf添加資料做準備工作,但與 net_buf_simple_add 不同的是,使用 push 添加資料時是将資料添加到 buf 中已使用資料的前面(即 reserved_head 中),使用add 添加資料時是将資料添加到 buf 中已使用資料的後面(即 buf 的未使用部分)。

所做的準備工作:

判斷頭部空間是否足夠用來存儲 len 個位元組

将 data 指針前移 len 位元組

将 buf 中的 len 預增加

同樣地,也可能造成緩沖溢出

net_buf_simple_push_u8

void net_buf_simple_push_u8(struct net_buf_simple *buf, uint8_t val)

{

uint8_t *data = net_buf_simple_push(buf, 1);

*data = val;
           

}

向 buf 的頭部中添加 1 個位元組的 unsigned char 類型的資料。

net_buf_simple_push_le16

void net_buf_simple_push_le16(struct net_buf_simple *buf, uint16_t val)

{

val = sys_cpu_to_le16(val);

memcpy(net_buf_simple_push(buf, sizeof(val)), &val, sizeof(val));

}

向 buf 的頭部添加 2 個位元組的 unsigned short 類型的資料,且該資料在 buf 中以小端的格式存儲。

net_buf_simple_push_le16

void net_buf_simple_push_be16(struct net_buf_simple *buf, uint16_t val)

{

val = sys_cpu_to_be16(val);

memcpy(net_buf_simple_push(buf, sizeof(val)), &val, sizeof(val));

}

向 buf 的頭部添加 2 個位元組的 unsigned short 類型的資料,且該資料在 buf 中以大端的格式存儲。

net_buf_simple_pull

void *net_buf_simple_pull(struct net_buf_simple *buf, size_t len)

{

NET_BUF_ASSERT(buf->len >= len);

buf->len -= len;
return buf->data += len;
           

}

從 buf 中取出資料後的收尾工作,包括:

判斷 buf 中資料的長度大于 len

buf 的 len 減小

buf 的 data 指針後移 len

net_buf_simple_pull_u8

uint8_t net_buf_simple_pull_u8(struct net_buf_simple *buf)

{

uint8_t val;

val = buf->data[0]; // 先取出資料
net_buf_simple_pull(buf, 1); // 再處理 len, data 指針

return val;
           

}

從 buf 中的有效資料的首位址取出 1 個位元組并傳回。

net_buf_simple_pull_le16

uint16_t net_buf_simple_pull_le16(struct net_buf_simple *buf)

{

uint16_t val;

val = UNALIGNED_GET((uint16_t *)buf->data);
net_buf_simple_pull(buf, sizeof(val));

return sys_le16_to_cpu(val);
           

}

從 buf 中的有效資料的首位址處按小端的方式取出 2 位元組構成一個 unsigned short 類型的資料并傳回。

net_buf_simple_pull_be16

uint16_t net_buf_simple_pull_be16(struct net_buf_simple *buf)

{

uint16_t val;

val = UNALIGNED_GET((uint16_t *)buf->data);
net_buf_simple_pull(buf, sizeof(val));

return sys_be16_to_cpu(val);
           

}

從 buf 中的有效資料的首位址處按大端的方式取出 1 位元組構成一個 unsigned short 類型的資料并傳回。

net_buf_simple_pull_le32

uint32_t net_buf_simple_pull_le32(struct net_buf_simple *buf)

{

uint32_t val;

val = UNALIGNED_GET((uint32_t *)buf->data);
net_buf_simple_pull(buf, sizeof(val));

return sys_le32_to_cpu(val);
           

}

從 buf 中的有效資料的首位址處按小端的方式取出 4 位元組構成一個 unsigned int 類型的資料并傳回

net_buf_simple_pull_be32

uint32_t net_buf_simple_pull_be32(struct net_buf_simple *buf)

{

uint32_t val;

val = UNALIGNED_GET((uint32_t *)buf->data);
net_buf_simple_pull(buf, sizeof(val));

return sys_be32_to_cpu(val);
           

}

總結

簡化版 Buffer 的模型設計得很不好,一不小心就可能造成緩沖溢出,是以在使用這些接口是一定要小心。

繼續閱讀