Zephyr OS 所有的學習筆記已託管到 Github,CSDN 博客裏的內容只是 Github 裏內容的拷貝,因此鏈接會有錯誤,請諒解。
最新的學習筆記請移步 GitHub:https://github.com/tidyjiang8/zephyr-inside
本文先簡單地介紹了一些內聯函數的知識,然後再詳細分析 Zephyr OS 內核中的鏈表的源碼。
- 內聯(inline)函數
- 鏈表的定義
- 鏈表的初始化
- 判斷某節點是否是鏈表的頭節點
- 判斷某節點是否是鏈表的尾節點
- 判斷鏈表是否爲空
- 獲取鏈表頭節點
- 獲取節點的下一個節點
- 在鏈表尾部插入節點
- 在鏈表頭部插入節點
- 在某節點後插入節點
- 在某節點前插入節點
- 刪除某節點
- 取出第一個節點
內聯(inline)函數
在 Zephyr OS 中,實現了一個雙鏈表,其代碼位於頭文件 inclue/misc/dlist.h 中。
居然將函數實現在頭文件中!!再仔細一看,這些函數不是普通的函數,而是內聯函數!
爲什麼?這樣有什麼好處?在網上搜了一通,總結如下:
- 什麼是內聯函數:
- 簡單地講,內聯函數就是用關鍵字 inline 修飾的函數。編譯器在編譯含有內聯函數的文件時,會在調用內聯函數的地方將該函數展開,這一點與處理宏的過程類似。但是,在將內聯函數展開的時候,編譯器會對該內聯函數的類型進行檢查,比如檢查參數、返回值類型等。
- 什麼時候使用內聯函數:
- 如果一段代碼需要重用,且重用的頻率很高,且代碼很短,那麼推薦使用內聯函數。
- 內聯函數 vs 宏:
- 編譯器會對內聯函數進行類型檢查,而對宏只是進行簡單的替換。
- 內聯函數在編譯時被展開,宏在預編譯時被展開
- 內聯函數 vs 普通函數:
- 前面已經說了,內聯函數的調用頻率高,代碼短小,在這種情況下,如果不使用內聯函數而使用普通函數,會降低運行效率,因爲函數調用有開銷,比如參數、返回值、返回地址等的壓棧、出棧操作。
- 內聯函數的缺點:
- 增加了編譯後的代碼的大小。
在 Zephyr OS 的源碼中,還會看到另外一套鏈表,位於 net/ip/contiki/os/list.[ch]。這套鏈表是移植於Contiki,只在ip協議棧中有使用。
鏈表的定義
struct _dnode {
union {
struct _dnode *head;
struct _dnode *next;
};
union {
struct _dnode *tail;
struct _dnode *prev;
};
};
第一眼就蒙了,爲什麼節點的定義裏面居然有兩個聯合體。直到看到後來,恍惚間想明白了。Zephyr OS 中對該結構體使用 tpyedef 定義了兩種類型:
typedef struct _dnode sys_dlist_t;
typedef struct _dnode sys_dnode_t;
sys_dlist_t 定義了一個雙鏈表,sys_dnode_t 定義了一個節點。在使用 sys_dlist_t 時,使用的是結構體中的 head, tail 兩個成員;在使用 sys_dnode_t 時,使用的是結構體中的 next, prev 兩個成語。
其實我們可以對上面的代碼展開成兩個結構體:
typedef struct _dnode { // 定義節點
struct _dnode *next; // 後繼節點
struct _donde *prev; // 前驅節點
} sys_dnode_t;
typedef struct _dlist { // 定義鏈表
struct _dlist *head; // 鏈表頭
struct _dlist *tail; // 鏈表尾
} sys_dlist_t;
有人可能會被鏈表和節點兩個概念搞暈了,比如曾經的我。我們只需要記住一點,在使用鏈表的時候,我們都是先定義一個鏈表,然後往鏈表裏進行添加節點、刪除節點、查找節點等操作。比如:
sys_dlist_t mylist; // 定義鏈表 sys_dlist_init(&mylist); // 鏈表初始化 sys_dnode_t mynode1, mynode2; // 定義節點 ... // 對節點初始化 sys_dlist_append(&mylist, &mynode1); // 插入節點1 sys_dlist_append(&mylist, &mynode2); // 插入節點2
鏈表的初始化
static inline void sys_dlist_init(sys_dlist_t *list)
{
list->head = (sys_dnode_t *)list;
list->tail = (sys_dnode_t *)list;
}
理解了前面所說的東西,再看具體的代碼實現,so easy.
進行鏈表的初始化,此時鏈表是空的,沒有任何節點,所以將 head, tail 兩個指針都指向 list 自身。
判斷某節點是否是鏈表的頭節點
static inline int sys_dlist_is_head(sys_dlist_t *list, sys_dnode_t *node)
{
return list->head == node;
}
判斷某節點是否是鏈表的尾節點
static inline int sys_dlist_is_tail(sys_dlist_t *list, sys_dnode_t *node)
{
return list->tail == node;
}
判斷鏈表是否爲空
static inline int sys_dlist_is_empty(sys_dlist_t *list)
{
return list->head == list;
}
獲取鏈表頭節點
static inline sys_dnode_t *sys_dlist_peek_head(sys_dlist_t *list)
{
return sys_dlist_is_empty(list) ? NULL : list->head;
}
先判斷鏈表是否爲空,如果爲空,則返回 NULL,否則返回頭結點。所以在使用才函數時,需要判斷返回值是否爲 NULL,再做處理。
獲取節點的下一個節點
static inline sys_dnode_t *sys_dlist_peek_next(sys_dlist_t *list,
sys_dnode_t *node)
{
return node == list->tail ? NULL : node->next;
}
先判斷傳入的節點是不是鏈表的尾節點,如果是,則返回 NULL,否則返回下一個節點。所以在使用才函數時,需要判斷返回值是否爲 NULL,再做處理。
在鏈表尾部插入節點
static inline void sys_dlist_append(sys_dlist_t *list, sys_dnode_t *node)
{
node->next = list;
node->prev = list->tail;
list->tail->next = node;
list->tail = node;
}
在鏈表頭部插入節點
static inline void sys_dlist_prepend(sys_dlist_t *list, sys_dnode_t *node)
{
node->next = list->head;
node->prev = list;
list->head->prev = node;
list->head = node;
}
在某節點後插入節點
static inline void sys_dlist_insert_after(sys_dlist_t *list,
sys_dnode_t *insert_point, sys_dnode_t *node)
{
if (!insert_point) {
sys_dlist_prepend(list, node);
} else {
node->next = insert_point->next;
node->prev = insert_point;
insert_point->next->prev = node;
insert_point->next = node;
}
}
在某節點前插入節點
static inline void sys_dlist_insert_before(sys_dlist_t *list,
sys_dnode_t *insert_point, sys_dnode_t *node)
{
if (!insert_point) {
sys_dlist_append(list, node);
} else {
node->prev = insert_point->prev;
node->next = insert_point;
insert_point->prev->next = node;
insert_point->prev = node;
}
}
刪除某節點
static inline void sys_dlist_remove(sys_dnode_t *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
取出第一個節點
static inline sys_dnode_t *sys_dlist_get(sys_dlist_t *list)
{
sys_dnode_t *node;
if (sys_dlist_is_empty(list)) {
return NULL;
}
node = list->head;
sys_dlist_remove(node);
return node;
}
取出頭節點後將其重鏈表中刪除。