【RT-Thread筆記】對象容器與雙鏈表

前言

在我們嵌入式中,可能會有些人認爲數據結構與算法相關知識沒什麼用,很少用得上。

以前,我也是這麼認爲的,那東西那麼難學,好像又用不上,學了有什麼用,乾脆就不學了。

直到後面深入學習一些東西之後發現,原來那些知識並不是沒有用,只是當時我們的認知還不足。廢話不多說,下面進入正題:

對象容器與雙鏈表

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 內核實現與應用開發實戰指南》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章