zephyr學習筆記---單向鏈表slist

看了下zephyr所支持的開發板,有一個TI公司的,CC3200。低功耗wifi芯片,淘寶了一下,200出頭,可以接受。當即買了一塊,先弄TCP/IP再學6LowPan會好些。現在等開發板到貨。

開發環境已經裝好,ubuntu下開發,請參考大牛寫的:
最好對照原文:

開發板沒到,先搞些基礎工作,從簡單的入手吧。先分析幾個基礎數據結構。首先就是單向鏈表slist。

slist.h文件存在於zephyr / include / misc文件夾下,全部使用內聯函數,無相應的.c文件。slist爲單向非循環鏈表,具備頭指針和尾指針,下面說說它的常用函數:

static inline void sys_slist_init(sys_slist_t *list)
初始化鏈表,其實就是將頭指針和尾指針置空。

static inline bool sys_slist_is_empty(sys_slist_t *list)
判斷list是否爲空鏈表,返回true爲空,false爲非空。

static inline sys_snode_t *sys_slist_peek_head(sys_slist_t *list)
返回list的頭節點。

static inline sys_snode_t *sys_slist_peek_tail(sys_slist_t *list)
返回list的尾節點

static inline sys_snode_t *sys_slist_peek_next_no_check(sys_snode_t *node)
返回node的下一個節點,注意,必須已確定node不爲空。此函數是不檢查node是否爲空的,所以速度會快些

static inline sys_snode_t *sys_slist_peek_next(sys_snode_t *node)
和上一個函數功能相同,在無法確定node是否爲空的情況下使用。如果node爲空,則返回NULL。

static inline void sys_slist_append(sys_slist_t *list,
                    sys_snode_t *node)
將節點node追加至list鏈表的尾部

static inline void sys_slist_prepend(sys_slist_t *list,
                     sys_snode_t *node)
將節點node添加至list鏈表的頭部,成爲頭節點。鏈表有這樣的功能估計是爲將來成爲棧或隊列作準備的。

static inline void sys_slist_append_list(sys_slist_t *list,
        void *head, void *tail)
{
    if (!list->tail)
    {
        list->head = (sys_snode_t *)head;
        list->tail = (sys_snode_t *)tail;
    }
    else
    {
        list->tail->next = (sys_snode_t *)head;
        list->tail = (sys_snode_t *)tail;
    }
}
參數list:源鏈表
參數head:要追加鏈表的頭節點
參數tail:要追加鏈表的尾節點
函數功能:將頭節點爲head、尾節點爲tail的鏈表追加到源鏈表list後面。

static inline void sys_slist_merge_slist(sys_slist_t *list,
        sys_slist_t *list_to_append)
{
    sys_slist_append_list(list, list_to_append->head,
                          list_to_append->tail);
    sys_slist_init(list);
}
將鏈表list和鏈表list_to_append合併,list_to_append追加至list尾部。添加完後,list被清空。
這裏有些看不懂,故將源碼放上。list被清空,新鏈表就找不到了,因爲list_to_append指向的還是追加前的鏈表,追加後的鏈表再也找不到。除非原本另有指針指向list的頭尾節點。個人感覺,被清空的應該是list_to_append。

static inline void sys_slist_insert(sys_slist_t *list,
                    sys_snode_t *prev,
                    sys_snode_t *node)
在鏈表list的prev節點後插入新節點node。

static inline sys_snode_t *sys_slist_get_not_empty(sys_slist_t *list)
刪除鏈表的頭節點,必須確定list不爲空鏈表才能使用此函數。

static inline sys_snode_t *sys_slist_get(sys_slist_t *list)
跟上個函數一樣,刪除鏈表頭節點,list可以爲空鏈表。

static inline void sys_slist_remove(sys_slist_t *list,
                    sys_snode_t *prev_node,
                    sys_snode_t *node)
在鏈表list中刪除節點node。必須給出node的上一個節點prev_node作爲參數。如果不想給出上一個節點,請使用下面這個函數。

static inline void sys_slist_find_and_remove(sys_slist_t *list,
                         sys_snode_t *node)
在鏈表list中刪除節點node。

下面是幾個遍歷用的宏:
#ifndef __SLIST_H__
#define __SLIST_H__

/**
 * @brief 遍歷整張鏈表
 * Note: 循環不安全,因此__sn不能被,簡而言之就是不能用於刪除操作。
 *
 * 用戶必須自行添加大括號以指定循環體
 *
 *     SYS_SLIST_FOR_EACH_NODE(l, n) {
 *         <user code>
 *     }
 *
 * @param __sl 指向sys_slist_t類型的指針,表示將進行迭代的鏈表
 * @param __sn sys_snode_t類型指針,用於遍歷鏈表中的每個節點
 */

#define SYS_SLIST_FOR_EACH_NODE(__sl, __sn)             \
    for (__sn = sys_slist_peek_head(__sl); __sn;            \
         __sn = sys_slist_peek_next(__sn))

/**
 * @brief 從鏈表指定節點處遍歷到結尾
 * Note: 循環不安全,因此__sn不能被刪除,簡而言之就是不能用於刪除操作。
 *
 * 用戶自行加大括號
 *
 *     SYS_SLIST_ITERATE_FROM_NODE(l, n) {
 *         <user code>
 *     }
 *
 * 和SYS_SLIST_FOR_EACH_NODE()一樣, 但如__sn指定爲鏈表中的一個節點,
 * 則遍歷從__sn的下一個節點開始。如果__sn爲空,則從頭節點開始遍歷。
 *
 * @param __sl 指向sys_slist_t類型的指針,表示將進行迭代的鏈
 * @param __sn sys_snode_t類型指針,指定的開始節點,爲空則從頭開始
 */

#define SYS_SLIST_ITERATE_FROM_NODE(__sl, __sn)             \
    for (__sn = __sn ? sys_slist_peek_next_no_check(__sn)       \
             : sys_slist_peek_head(__sl);           \
         __sn;                          \
         __sn = sys_slist_peek_next(__sn))

/**
 * @brief 安全地遍歷整個鏈表
 * Note: __sn可被刪除, 它不會打斷循環.
 *
 * 用戶必須自行添加大括號:
 *
 *     SYS_SLIST_FOR_EACH_NODE_SAFE(l, n, s) {
 *         <user code>
 *     }
 *
 * @param __sl sys_slist_t類型指針,指向被遍歷鏈表
 * @param __sn sys_snode_t類型指針,依次等於鏈表中的每個節點
 * @param __sns sys_snode_t類型指針,用於安全遍歷
 */

#define SYS_SLIST_FOR_EACH_NODE_SAFE(__sl, __sn, __sns)         \
    for (__sn = sys_slist_peek_head(__sl),              \
             __sns = sys_slist_peek_next(__sn);         \
         __sn; __sn = __sns,                    \
             __sns = sys_slist_peek_next(__sn))


上面兩個遍歷一般用不到,只是爲下兩個遍歷提供服務
/**
 * @brief 提供根據容器建立的鏈表的遍歷原語
 * Note: 此循環不安全,因此不能被刪除
 *
 * 用戶必須自行加大括號:
 *
 *     SYS_SLIST_FOR_EACH_CONTAINER(l, c, n) {
 *         <user code>
 *     }
 *
 * @param __sl sys_slist_t指針,將遍歷此鏈表
 * @param __cn 用於遍歷鏈表元素的臨時指針變量,爲容器類型
 * @param __n sys_node_t在容器結構體中的類型名稱
 */

#define SYS_SLIST_FOR_EACH_CONTAINER(__sl, __cn, __n)           \
    for (__cn = SYS_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n); __cn; \
         __cn = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n))

下面這個是安全遍歷
/**
 * @brief 提供根據容器建立的鏈表的安全遍歷原語
 * Note: __cn 可以被刪除,不會打斷循環.
 *
 * User 用戶必須自行添加大括號:
 *
 *     SYS_SLIST_FOR_EACH_NODE_SAFE(l, c, cn, n) {
 *         <user code>
 *     }
 *
 * @param __sl  sys_slist_t類型指針,將遍歷此鏈表
 * @param __cn   用於遍歷鏈表元素的臨時指針變量,爲容器類型
 * @param __cns  用於安全遍歷的臨時變量,和__cn同類型
 * @param __n  sys_node_t在容器結構體中的類型名稱
 */

#define SYS_SLIST_FOR_EACH_CONTAINER_SAFE(__sl, __cn, __cns, __n)   \
    for (__cn = SYS_SLIST_PEEK_HEAD_CONTAINER(__sl, __cn, __n), \
         __cns = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n); __cn;    \
         __cn = __cns, __cns = SYS_SLIST_PEEK_NEXT_CONTAINER(__cn, __n))

讀完代碼寫程序,這時才發現使用CCS進行TI-RTOS是一件多麼幸福的事情,用VS開發C#那簡直是在天堂了。開發環境沒搭太好,qemu又不懂如何關閉,總之各種折磨。總算寫完,看來得花時間找找有什麼辦法心善下開發環境。

先上代碼:

#include <zephyr.h>
#include <misc/printk.h>
#include <misc/slist.h>

static sys_slist_t list;
struct container_node
{
    sys_snode_t node;
    int id;
};

void PrintList(sys_slist_t *list) //依次打印所有節點
{
    struct container_node *container;
    printk("print list node:\n");
    SYS_SLIST_FOR_EACH_CONTAINER(list, container, node)
    {
        printk("node%d   ", container->id);
    }
    printk("\n\n");
}

void main(void)
{   
    struct container_node node1, node2, node3, node4, node5;
    node1.id = 1;
    node2.id = 2;
    node3.id = 3;
    node4.id = 4;
    node5.id = 5;
    sys_slist_init(&list);
    //將5個節點加入鏈表
    sys_slist_append(&list, &node1.node);
    sys_slist_append(&list, &node2.node);
    sys_slist_append(&list, &node3.node);
    sys_slist_append(&list, &node4.node);
    sys_slist_append(&list, &node5.node);
    PrintList(&list);
    
    printk("move node3 to head\n");
    sys_slist_find_and_remove(&list, &node3.node);//刪除節點3
    sys_slist_prepend(&list, &node3.node);//將節點3變爲頭節點
    PrintList(&list);
    
    printk("switch node4 and node2\n");
    sys_slist_find_and_remove(&list, &node4.node);//刪除節點4
    sys_slist_insert(&list, &node1.node, &node4.node);//將節點4加到節點1後面
    PrintList(&list);
}
先記下環境變量設置命令,方便以後回來對照:
export ZEPHYR_GCC_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=~/zephyr-sdk
cd zephyr-project/
source zephyr-env.sh
由於關閉不了qemu,每編譯一次都要打一輪。之後再解決這個問題
經tidyjiang大神指點,已解決qemu關閉問題:先按Ctrl+a,再按x退出。不要忘記他的博客,上篇日誌提到過:http://blog.csdn.net/tidyjiang/article/details/51622186

然後通過以下命令在qemu上執行程序:
make BOARD=qemu_x86 qemu
所有命令及運行結果如下圖所示:

本例演示了slist的添加、刪除、插入及遍歷操作。要使用鏈表,必須在鏈表所存儲元素所使用結構體中添加一個sys_snode_t類型成員,這個結構體被爲容器(container)。這樣使用比較麻煩,在這點上contiki中的鏈表比zephyr更先進、好用些。另外我始終認爲鏈表如果進出操作太多,會產生大量零碎空間,應當慎用。C#中基本沒見哪個地方使用鏈表,有使用的地方基本都用靜態鏈表代替。



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