前言
在我們嵌入式中,可能會有些人認爲數據結構與算法相關知識沒什麼用,很少用得上。
以前,我也是這麼認爲的,那東西那麼難學,好像又用不上,學了有什麼用,乾脆就不學了。
直到後面深入學習一些東西之後發現,原來那些知識並不是沒有用,只是當時我們的認知還不足。廢話不多說,下面進入正題:
對象容器與雙鏈表
1、RT-Thread中的對象容器
RT-Thread
內核對象包括:線程,信號量,互斥量,事件,郵箱,消息隊列和定時器,內存池,設備驅動等。
對象容器中包含了每類內核對象的信息,包括對象類型,大小等。
對象容器給每類內核對象分配了一個鏈表,所有的內核對象都被鏈接到該鏈表上, RT-Thread 的內核對象容器及鏈表如下圖所示:
這個對象容器對應到代碼上是一個結構體數組,這個結構體數組在object.c
裏定義,其內容如下(已做刪減):
static struct rt_object_information
rt_object_container[RT_Object_Info_Unknown] =
{
/* 初始化對象容器 - 線程 */
{
RT_Object_Class_Thread,
_OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread),
sizeof(struct rt_thread)
},
#ifdef RT_USING_SEMAPHORE
/* 初始化對象容器 - 信號量 */
{
RT_Object_Class_Semaphore,
_OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore),
sizeof(struct rt_semaphore)
},
#endif
/* 省略部分代碼...*/
/* 初始化對象容器 - 定時器 */
{
RT_Object_Class_Timer,
_OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer),
sizeof(struct rt_timer)
},
/* 省略部分代碼...*/
};
其中,內核對象信息結構體 struct rt_object_information
的定義如下:
struct rt_object_information
{
enum rt_object_class_type type; /**< 對象類型 */
rt_list_t object_list; /**< 對象鏈表節點頭 */
rt_size_t object_size; /**< 對象大小 */
};
RT-Thread 的內核對象的繼承關係如:
在代碼閱讀器Source Insight
中也可以看出這樣的關係:
每個內核對象的初始化函數裏都有調用對象初始化函數rt_object_init
。
而對象初始化函數裏做了什麼呢?看其內部實現(已做刪減):
void rt_object_init(struct rt_object *object,
enum rt_object_class_type type,
const char *name)
{
register rt_base_t temp;
struct rt_object_information *information;
/* 獲取對象信息,即從容器裏拿到對應對象列表頭指針 */
information = rt_object_get_information(type);
RT_ASSERT(information != RT_NULL);
/* 設置對象類型爲靜態 */
object->type = type | RT_Object_Class_Static;
/* 拷貝名字 */
rt_strncpy(object->name, name, RT_NAME_MAX);
/* 關中斷 */
temp = rt_hw_interrupt_disable();
/* 把對象信息插入到對象鏈表中 */
rt_list_insert_after(&(information->object_list), &(object->list));
/* 開中斷 */
rt_hw_interrupt_enable(temp);
}
這裏用到的rt_list_insert_after
函數就是雙鏈表插入函數。
2、RT-Thread中雙鏈表的操作
RT-Thread的對象容器是依賴於雙鏈表(雙向循環鏈表
)的,其雙鏈表的相關操作在文件rtservice.h
中:
其節點結構體爲:
struct rt_list_node
{
struct rt_list_node *next; /**< 指向後一個節點 */
struct rt_list_node *prev; /**< 指向後一個節點 */
};
typedef struct rt_list_node rt_list_t;
下面介紹幾個基本的操作接口(截圖來自野火的RT-Thread書籍):
(1)初始化鏈表節點:
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;/* 節點的next指針與prev指針都指向其本身 */
}
(2) 在雙向鏈表表頭後面插入一個節點:
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
l->next->prev = n; /* 第(1)步 */
n->next = l->next; /* 第(2)步 */
l->next = n; /* 第(3)步 */
n->prev = l; /* 第(4)步 */
}
(3)在雙向鏈表表頭前面(表尾後面)插入一個節點:
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
l->prev->next = n; /* 第(1)步 */
n->prev = l->prev; /* 第(2)步 */
l->prev = n; /* 第(3)步 */
n->next = l; /* 第(4)步 */
}
(4)從雙向鏈表刪除一個節點:
rt_inline void rt_list_remove(rt_list_t *n)
{
n->next->prev = n->prev; /* 第(1)步 */
n->prev->next = n->next; /* 第(2)步 */
n->next = n->prev = n; /* 第(3)步 */
}
初始化對象列表節點頭裏面的 next 和 prev 兩個節點指針分別指向自身,如:
若創建兩個led線程對象,則對象容器裏變爲:
順便提一個關於鏈表的一個名詞上的疑惑,是節點
還是結點
?我之前也看過一些書,有的書寫爲節點,有的書寫爲結點,我也不知道哪個更準確。不糾結了,知道這麼一回事就可以了,不影響我們學習。
最後
看到了吧,數據結構在嵌入式中還是很有用的吧,更多的細節可以閱讀其源碼。
參考資料:
1、官方的《RT-THREAD 編程指南》
2、野火的《RT-Thread 內核實現與應用開發實戰指南》