在学习OpenHarmony
鸿蒙轻内核源代码的时候,常常会遇到一些数据结构的使用。如果没有掌握它们的用法,会导致阅读源代码时很费解、很吃力。本文会给读者介绍源码中重要的数据结构,双向循环链表Doubly Linked List
。在讲解时,会结合数据结构相关绘图,培养读者们的数据结构的平面想象能力,帮助更好的学习和理解这些数据结构的用法。
本文中所涉及的源码,以OpenHarmony LiteOS-A
内核为例,均可以在开源站点 https://gitee.com/openharmony/kernel_liteos_a 获取。
1 双向循环链表
双向循环链表Doubly Linked List
的源代码在kernel\include\los_list.h
双向链表头文件中,包含LOS_DL_LIST
结构体定义、inline
内联函数LOS_ListXXX
,还有相关的函数宏定义LOS_DL_LIST_XXXX
。双向链表头文件可以网页访问 kernel/include/los_list.h ,也可以检出到本地阅读。
1.1 双向链表结构体
双向链表节点结构体LOS_DL_LIST
定义如下。双向链表的定义,如何使用,这在OpenHarmony LiteOS-A
内核和OpenHarmony LiteOS-M
内核里是一致的,不再赘述。
typedef struct LOS_DL_LIST {
struct LOS_DL_LIST *pstPrev; /** 指向当前链表节点的前驱节点的指针 */
struct LOS_DL_LIST *pstNext; /** 指向当前链表节点的后继节点的指针 */
} LOS_DL_LIST;
2 初始化双向链表
OpenHarmony LiteOS-A
内核和OpenHarmony LiteOS-M
内核中的双向链表的初始化也是一致的,都提供了内联函数LOS_ListInit()
,还提供了一个相同功能的函数式宏LOS_DL_LIST_HEAD
。可以查看OpenHarmony LiteOS-M
内核源码剖析的双向链表部分。
3 判断空链表
3.1 LOS_ListEmpty(LOS_DL_LIST *list)
该内联函数用于判断链表是否为空。和OpenHarmony LiteOS-M
内核的函数一致。
源码如下:
LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *list)
{
return (BOOL)(list->pstNext == list);
}
4 插入双向链表节点
双向链表提供三种链表节点插入方法,在指定链表节点后面插入LOS_ListAdd
、尾部插入LOS_ListTailInsert
、头部插入LOS_ListHeadInsert
。在头部插入的节点,从头部开始遍历时第一个遍历到,从尾部插入的节点,最后一个遍历到。OpenHarmony LiteOS-A
内核除了提供链表节点插入还提供了方法把一个链表插入另外一个双向链表的方法,在指定链表节点后面插入新链表LOS_ListAddList
、尾部插入新链表LOS_ListTailInsertList
、头部插入新链表LOS_ListHeadInsertList
。
4.1 INLINE VOID LOS_ListAddList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
该内联函数往双向链表节点*oldList
所在的双向链表中插入链表节点*newList
所属的双向链表,插入后老双向链表的头结点和新双向链表的尾节点相连,老双向链表的尾节点与新双向链表的头节点相连。使用该方法插入的新的双向链表靠近双向链表的头部,即从头部开始遍历时会首先遍历到新链表的头部节点。如下图所示:
图示:
源码如下:
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAddList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
{
LOS_DL_LIST *oldListHead = oldList->pstNext;
LOS_DL_LIST *oldListTail = oldList;
LOS_DL_LIST *newListHead = newList;
LOS_DL_LIST *newListTail = newList->pstPrev;
oldListTail->pstNext = newListHead;
newListHead->pstPrev = oldListTail;
oldListHead->pstPrev = newListTail;
newListTail->pstNext = oldListHead;
}
4.2 INLINE VOID LOS_ListTailInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
该内联函数往链表节点*oldList
所在的双向链表中插入链表节点*newList
所属的链表,插入后老双向链表的尾结点和新双向链表的头节点相连,老双向链表的头节点与新双向链表的尾节点相连。使用该方法插入的新的双向链表的尾部靠近老双向链表的头部,即从头部开始遍历时会首先遍历到新链表的尾部节点。。如下图所示:
图示:
源码如下:
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
{
LOS_ListAddList(oldList->pstPrev, newList);
}
4.3 INLINE VOID LOS_ListHeadInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
该内联函数和LOS_ListAddList()
实现同样的功能。
源码如下:
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListHeadInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
{
LOS_ListAddList(oldList, newList);
}
5 删除双向链表节点
双向链表提供两种链表节点的删除方法,删除指定节点LOS_ListDelete()
、删除并初始化为一个新链表LOS_ListDelInit()
。
6 获取双向链表节点
双向链表还提供宏定义获取指定链表节点的前驱节点LOS_DL_LIST_LAST
,获取指定链表节点的后继节点LOS_DL_LIST_FIRST
。OpenHarmony LiteOS-M
内核源码分析时,已经了解这2个宏,我们继续看下2个判断的宏,即LOS_DL_LIST_IS_END
和LOS_DL_LIST_IS_ON_QUEUE
。
6.1 LOS_DL_LIST_IS_END
通过判断两个链表节点是否相等,通常第一个为尾节点,第二个是变化的链表节点,判断是否遍历到尾节点。
源码如下:
#define LOS_DL_LIST_IS_END(list, node) ((list) == (node) ? TRUE : FALSE)
6.2 LOS_DL_LIST_IS_ON_QUEUE
用于判断链表节点是否在循环链表中,即前序、后继节点都不为空。
源码如下:
#define LOS_DL_LIST_IS_ON_QUEUE(node) ((node)->pstPrev != NULL && (node)->pstNext != NULL)
7 遍历双向循环链表节点
双向循环链表提供两种遍历双向链表的方法,LOS_DL_LIST_FOR_EACH
和LOS_DL_LIST_FOR_EACH_SAFE
。OpenHarmony LiteOS-M
内核源码分析时,已经分析过这2个宏。
8 获取链表节点所在结构体
通过宏LOS_DL_LIST_ENTRY
可以获取成员变量相对于结构体的内存地址偏移量。通过宏LOS_DL_LIST_ENTRY``即可以获取双向链表节点所在的业务结构体的内存地址。
OpenHarmony LiteOS-M内核源码分析时,已经分析过这2个宏。我们分析下
OpenHarmony LiteOS-A内核中独有的三个宏,即
LOS_ListPeekHeadType、
LOS_ListRemoveHeadType和
LOS_ListNextType`。
8.1 LOS_ListPeekHeadType
函数宏中的三个参数分别为:业务结构体类型名称type
,作为结构体成员的双向链表成员变量名称element
,双向链表头节点指针list
。通过调用该宏函数LOS_ListPeekHeadType
即可以获取双向链表第一个业务链表节点所在的业务结构体的内存地址。
⑴处表示如果是只有头链表结点的空双向循环链表,则返回NULL。⑵如果链表不为空,调用宏LOS_DL_LIST_ENTRY
获取业务结构体的内存地址。该宏相对宏LOS_DL_LIST_ENTRY
更安全,如果是空的双向链表,调用后者,返回的内存地址不是业务结构体的地址,会发生踩内存错误。
源码如下:
#define LOS_ListPeekHeadType(list, type, element) ({ \
type *__t; \
⑴ if ((list)->pstNext == list) { \
__t = NULL; \
} else { \
⑵ __t = LOS_DL_LIST_ENTRY((list)->pstNext, type, element); \
} \
__t; \
})
8.2 LOS_ListRemoveHeadType
函数宏中的三个参数分别为:业务结构体类型名称type
,作为结构体成员的双向链表成员变量名称element
,双向链表头节点指针list
。通过调用该宏函数LOS_ListRemoveHeadType
即可以获取双向链表第一个业务链表节点所在的业务结构体的内存地址,并把第一个业务链表节点从双向链表中删除。
⑴处表示如果是只有头链表结点的空双向循环链表,则返回NULL。⑵如果链表不为空,获取业务结构体的内存地址,然后调用LOS_ListDelete
删除链表节点。
源码如下:
#define LOS_ListRemoveHeadType(list, type, element) ({ \
type *__t; \
if ((list)->pstNext == list) { \
⑴ __t = NULL; \
} else { \
⑵ __t = LOS_DL_LIST_ENTRY((list)->pstNext, type, element); \
LOS_ListDelete((list)->pstNext); \
} \
__t; \
})
8.3 LOS_ListNextType
函数宏中的四个参数分别为:业务结构体类型名称type
,作为结构体成员的双向链表成员变量名称element
,双向链表头节点指针list
,双向链表中的一个节点item
。通过调用该宏函数LOS_ListNextType
即可以获取双向链表指定链表节点的下一个节点所在的业务结构体的内存地址。
⑴处表示如果指定链表节点的下一个节点是头结点,则返回NULL。否则执行⑵,获取指定链表节点的下一个节点所在的业务结构体的内存地址。
源码如下:
#define LOS_ListNextType(list, item, type, element) ({ \
type *__t; \
if ((item)->pstNext == list) { \
⑴ __t = NULL; \
} else { \
⑵ __t = LOS_DL_LIST_ENTRY((item)->pstNext, type, element); \
} \
__t; \
})
9 遍历包含双向链表的结构体
双向链表提供三个宏定义来遍历包含双向链表成员的结构体,LOS_DL_LIST_FOR_EACH_ENTRY
、LOS_DL_LIST_FOR_EACH_ENTRY_SAFE
和LOS_DL_LIST_FOR_EACH_ENTRY_HOOK
。OpenHarmony LiteOS-M
内核源码分析时,已经分析过这3个宏。
小结
掌握鸿蒙轻内核的双向循环链表LOS_DL_LIST
这一重要的数据结构,会给进一步学习、分析鸿蒙轻内核源代码打下了基础,让后续的学习更加容易。
如果想更深入的学习 OpenHarmony (鸿蒙南向)全栈开发的内容,可以参考以下学习文档:
OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源码解析》:https://qr18.cn/CgxrRy
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
系统架构分析:https://qr18.cn/CgxrRy
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……
OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy
OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy
写在最后
- 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
- 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:
https://gitee.com/MNxiaona/733GH