本文介紹 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 的模型設計得很不好,一不小心就可能造成緩衝溢出,因此在使用這些接口是一定要小心。