1
Abstract: Doubly Linked List is one of the most important data structures of Hongmeng Light Core, which is widely used in various modules.

This article is shared from Huawei Cloud Community " Light Kernel M Core Source Code Analysis Series Two Data Structure-Two-way Circular Linked List ", the original author: zhushy.

When learning the OpenHarmony Hongmeng light kernel source code, we often encounter the use of some data structures. If you do not master their usage, it will lead to very difficult and difficult to read the source code. This article will introduce readers to the important data structure in the source code, Doubly Linked List. In the explanation, it will combine the data structure related drawing to cultivate the readers' planar imagination ability of the data structure, and help better learn and understand the usage of these data structures.

The source code involved in this article, taking the OpenHarmony LiteOS-M kernel as an example, can be obtained from the open source site https://gitee.com/openharmony/kernel_liteos_m .

1 Two-way circular linked list

The source code of the doubly linked list LOS_DL_LIST is in the utils\los_list.h doubly linked list header file, including the LOS_DL_LIST structure definition, the inline function LOS_ListXXX, and the related function macro definition LOS_DL_LIST_XXXX. The doubly linked list header file can be accessed from the web page utils/los_list.h, or it can be checked out and read locally.

1.1 Doubly linked list structure

The structure of the doubly linked list node LOS_DL_LIST is defined as follows. Its structure is very simple, general, and abstract, containing only two nodes, the predecessor and the successor, and is responsible for the role of a doubly linked list. The doubly linked list does not contain any business data information and is generally not used alone. Generally, doubly linked list nodes and business data information are used as structural members, and they form a business structure together. Examples of use will be described later.

typedef struct LOS_DL_LIST {
    struct LOS_DL_LIST *pstPrev; /** 指向当前链表节点的前驱节点的指针 */
    struct LOS_DL_LIST *pstNext; /** 指向当前链表节点的后继节点的指针 */
} LOS_DL_LIST;

Starting from any node in the doubly linked list, you can easily access its predecessor and successor nodes. This kind of circular data structure makes the doubly linked list very convenient in operations such as search, insert, and delete. When a doubly linked list is used in a business scenario, a global variable of type LOS_DL_LIST can be defined as the Head node of the doubly circular linked list, and the linked list member nodes of the business structure are mounted on the head node in turn. There are also doubly linked list nodes of some business structures as head nodes, and linked list member nodes of other business structures are mounted in turn. From the Head node, the next node can be traversed in turn. The predecessor node of the Head node is the Tail tail node.

Let's understand how to use the doubly linked list structure through the definition of the mutex structure LosMuxCB in the Hongmeng light kernel code:

typedef struct {
    UINT8 muxStat;       /**< 互斥锁状态  */
    UINT16 muxCount;     /**< 互斥锁当前被持有的次数 */
    UINT32 muxID;        /**< 互斥锁编号ID */
    LOS_DL_LIST muxList; /**< 互斥锁的双向链表 */
    LosTaskCB *owner;    /**< 当前持有锁的任务TCB */
    UINT16 priority;     /**< 持有互斥锁的任务优先级 */
} LosMuxCB;

The mutex structure includes the doubly linked list LOS_DL_LIST muxList member variable and other member variables containing the mutex business information. Here, each mutex is linked through the doubly linked list and mounted at the head node LOS_DL_LIST g_unusedMuxList; through other business members Variables carry business data, and the relationship between the linked list and other business members is shown in the following figure:
image.png

2 Initialize the doubly linked list

2.1 LOS_ListInit(LOS_DL_LIST *list)

The two members of LOS_DL_LIST, pstPrev and pstNext, are pointers to the LOS_DL_LIST structure type. Need to apply for a section of memory space of sizeof(LOS_DL_LIST) for the doubly linked list node. After applying for the memory for the linked list node, you can call the initialization LOS_ListInit(LOS_DL_LIST *list) method to link this node into a circular doubly linked list. When initializing the linked list, there is only one linked list node, and the predecessor and successor nodes of this node are both themselves. The linked list node is initialized as a linked list, as shown in the figure:
image.png

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
{
    list->pstNext = list;
    list->pstPrev = list;
}

2.2 LOS_DL_LIST_HEAD(LOS_DL_LIST list)

In addition to LOS_ListInit(), a functional macro LOS_DL_LIST_HEAD with the same function is also provided. By directly defining a doubly linked list node, the node is initialized as a doubly linked list. Different from LOS_ListInit(), there is no need to dynamically apply for memory space before calling the functional macro.

#define LOS_DL_LIST_HEAD(list) LOS_DL_LIST list = { &(list), &(list) }

3 Determine empty linked list

3.1 LOS_ListEmpty(LOS_DL_LIST *list)

This inline function is used to determine whether the linked list is empty. If the predecessor/successor nodes of the doubly linked list are both themselves, there is only one link node, and there is no linked list node with a business structure attached, the linked list is called an empty linked list.

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC_INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *node)
{
    return (BOOL)(node->pstNext == node);
}

4 Insert a doubly linked list node

The doubly linked list provides three linked list node insertion methods, inserting LOS_ListAdd after the specified linked list node, inserting LOS_ListTailInsert at the end, and inserting LOS_ListHeadInsert at the head. The node inserted at the head is the first to be traversed when traversing from the head, and the node inserted from the end is traversed to the last.

4.1 LOS_ListAdd(LOS_DL_LIST list, LOS_DL_LIST node)
This inline function node into the doubly linked list where the list is located, and the insertion position is behind the As shown, during insertion, will successor node of node set list-> pstNext, node's predecessor nodes List, and list-> pstNext predecessor node from List modify node, list is modified from list->pstNext to node.

Icon:
image.png

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
    node->pstNext = list->pstNext;
    node->pstPrev = list;
    list->pstNext->pstPrev = node;
    list->pstNext = node;
}

4.2 LOS_ListTailInsert(LOS_DL_LIST list, LOS_DL_LIST node)

This inline function node into the doubly linked list where the list is located, the insertion position is in front of the linked list node *list and behind the list->pstPrev node.

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
    LOS_ListAdd(list->pstPrev, node);
}

4.3 LOS_ListHeadInsert(LOS_DL_LIST list, LOS_DL_LIST node)

This inline function implements the same function as LOS_ListAdd() node into the doubly linked list where the list is located, and the insertion position is behind the linked list node *list.

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListHeadInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
    LOS_ListAdd(list, node);
}

5 Delete the doubly linked list node

The doubly linked list provides two methods for deleting the nodes of the linked list, deleting the designated node LOS_ListDelete(), deleting and initializing it as a new linked list LOS_ListDelInit().

5.1 LOS_ListDelete(LOS_DL_LIST *node)

This inline function linked list node 1609f6a6fb173b node from the doubly linked list where it is located. After the node is deleted, it may be necessary to actively release the memory occupied by the node. As shown, the process of deleting the node, will predecessor to successor nodes of node node's predecessor nodes, successor node to the predecessor node node of the successor node, and the predecessor node to node, The successor node is set to null, so that the *node node is out of the doubly linked list.

Icon:
image.png

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
{
    node->pstNext->pstPrev = node->pstPrev;
    node->pstPrev->pstNext = node->pstNext;
    node->pstNext = NULL;
    node->pstPrev = NULL;
}

5.2 LOS_ListDelInit(LOS_DL_LIST *list)

The inline function deletes the linked list node *list from the doubly linked list, and reinitializes the deleted node into a new doubly linked list.

Similar to LOS_ListDelete(), this function will also change the of the successor node of the list to the predecessor of the list, and the successor of the predecessor node of the list to the successor of the 1609f6a6fb17b1 list, but the difference is that it needs to be reinitialized to the new one. Doubly linked list, so this function does not list to null, but reinitializes the deleted node as a new doubly linked list

The source code is as follows:

LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelInit(LOS_DL_LIST *list)
{
    list->pstNext->pstPrev = list->pstPrev;
    list->pstPrev->pstNext = list->pstNext;
    LOS_ListInit(list);
}

6 Get the doubly linked list node

The doubly linked list also provides operations for obtaining the node of the linked list and obtaining the address of the structure containing the linked list.

6.1 LOS_DL_LIST_LAST(object)
Get the predecessor node of the specified linked list node.

The source code is as follows:

#define LOS_DL_LIST_LAST(object) ((object)->pstPrev)

6.2 LOS_DL_LIST_FIRST(object)

Get the successor node of the specified linked list node.

The source code is as follows:

#define LOS_DL_LIST_FIRST(object) ((object)->pstNext)

7 Traverse the nodes of the doubly circular linked list

The doubly circular linked list provides two ways to traverse the doubly linked list, LOS_DL_LIST_FOR_EACH and LOS_DL_LIST_FOR_EACH_SAFE.

7.1 LOS_DL_LIST_FOR_EACH(item, list)

This macro defines LOS_DL_LIST_FOR_EACH to traverse the doubly linked list, and save the linked list node obtained in each cycle in the first input parameter, and the second input parameter is the starting node of the doubly linked list to be traversed. This macro is a for loop condition. In each loop, get the next linked list node and save it to the input parameter item. The business code is written in the code block {} after the macro.

The source code is as follows:

#define LOS_DL_LIST_FOR_EACH(item, list) \
    for ((item) = (list)->pstNext; (item) != (list); (item) = (item)->pstNext)
我们以实例演示如何使用LOS_DL_LIST_FOR_EACH。在kernel\src\los_task.c文件中,UINT32 

The fragment of the OsPriqueueSize (UINT32 priority) function is as follows:

STATIC UINT32 OsPriqueueSize(UINT32 priority)
{
    UINT32 itemCnt = 0;
    LOS_DL_LIST *curPQNode = (LOS_DL_LIST *)NULL;

⑴  LOS_DL_LIST_FOR_EACH(curPQNode, &g_losPriorityQueueList[priority]) {
        ++itemCnt;
    }

    return itemCnt;
}

In the code at ⑴, g_losPriorityQueueList[priority] is the doubly linked list to be traversed circularly, and curPQNode points to the linked list node in the traversal process.

7.2 LOS_DL_LIST_FOR_EACH_SAFE(item, next, list)

The only difference between this macro definition LOS_DL_LIST_FOR_EACH_SAFE and LOS_DL_LIST_FOR_EACH is that there is an additional input parameter next, which represents the next node of the traversed doubly linked list node. This macro is used for safe deletion. If the traversed item is deleted, it will not affect the continued traversal.

The source code is as follows:

#define LOS_DL_LIST_FOR_EACH_SAFE(item, next, list) \
    for ((item) = (list)->pstNext, (next) = (item)->pstNext; (item) != (list); \
            (item) = (next), (next) = (item)->pstNext)

8 Get the structure where the linked list node is located

8.1 LOS_OFF_SET_OF(type, member)

According to the structure type name type and the member variable name member, the memory address offset of the member member variable relative to the structure type is obtained. In the application scenario of the linked list, the business structure contains a doubly linked list as a member. When the memory address of the doubly linked list member variable and the offset relative to the business structure are known, the memory address of the business structure can be further obtained, see below for details Macro implementation of LOS_DL_LIST_ENTRY.

The source code is as follows:

#define LOS_OFF_SET_OF(type, member) ((UINTPTR)&((type *)0)->member)

8.2 LOS_DL_LIST_ENTRY(item, type, member)

The three parameters in the function macro are: business structure type name type, doubly linked list member variable name member as a member of the structure, and doubly linked list node pointer item as a structure member. By calling the macro function LOS_DL_LIST_ENTRY, the memory address of the business structure where the doubly linked list node is located can be obtained.

The source code is as follows:

Based on the memory address of the doubly linked list node and the address offset of the doubly linked list member variable in the structure, the memory address of the structure can be calculated.

#define LOS_DL_LIST_ENTRY(item, type, member) \
    ((type *)(VOID *)((CHAR *)(item) - LOS_OFF_SET_OF(type, member)))

9 Traverse the structure containing the doubly linked list

The doubly linked list provides three macro definitions to traverse the structure containing the members of the doubly linked list, LOS_DL_LIST_FOR_EACH_ENTRY, LOS_DL_LIST_FOR_EACH_ENTRY_SAFE and LOS_DL_LIST_FOR_EACH_ENTRY_HOOK.

9.1 LOS_DL_LIST_FOR_EACH_ENTRY(item, list, type, member)

The macro definition LOS_DL_LIST_FOR_EACH_ENTRY traverses the doubly linked list, obtains the structure variable containing the doubly linked list members in each cycle and saves it in the first input parameter. The second input parameter is the starting node of the doubly linked list to be traversed, the third input parameter is the name of the structure type to be obtained, and the fourth input parameter is the name of the doubly linked list member variable in the structure. This macro is a for loop condition, and the business code is written in the code block {} after the macro.

The source code is as follows:

The initialization statement item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member) of the for loop means to get the structure containing the first valid node of the doubly linked list and save it to the pointer variable item. The conditional test statement &(item)->member != (list) means that when the doubly linked list traverses a circle to its own node, stop the loop. In the loop update statement item = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member)), use (item)->member.pstNext to traverse to the next linked list node, and then get the corresponding next structure according to this node The pointer variable item until the traversal is completed.

#define LOS_DL_LIST_FOR_EACH_ENTRY(item, list, type, member)             \
    for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member);        \
         &(item)->member != (list);                                      \
         item = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member))

9.2 LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, next, list, type, member)

The only difference between this macro definition and LOS_DL_LIST_FOR_EACH_ENTRY is that there is an additional input parameter next, which represents the next structure of the traversed structure. This macro is used for safe deletion. If the traversed item is deleted, the traversal will not be affected.

The source code is as follows:

#define LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, next, list, type, member)               \
    for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member),                     \
         next = LOS_DL_LIST_ENTRY((item)->member->pstNext, type, member);             \
         &(item)->member != (list);                                                   \
         item = next, next = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member))

9.3 LOS_DL_LIST_FOR_EACH_ENTRY_HOOK(item, list, type, member, hook)

The difference between this macro definition and LOS_DL_LIST_FOR_EACH_ENTRY is that there is one more input parameter hook, which means hook function. In each traversal loop, the hook function will be called to realize the customization of user tasks.

The source code is as follows:

#define LOS_DL_LIST_FOR_EACH_ENTRY_HOOK(item, list, type, member, hook)  \
    for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member), hook;  \
         &(item)->member != (list);                                      \
         item = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member), hook)

Click to follow and learn about Huawei Cloud's fresh technology for the first time~


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量