提示:本文基于开源鸿蒙内核分析,官方源码【kernel_liteos_a】官方文档【docs】参考文档【Huawei LiteOS】
本文作者:鸿蒙内核发烧友,将持续研究鸿蒙内核,更新博文,敬请关注。内容仅代表个人观点,错误之处,欢迎大家指正完善。本系列全部文章进入 查看 鸿蒙系统源码分析(总目录)
本文详细讲述双向循环链表,详见源码: los_list.h
目录
为何鸿蒙内核源码分析系列开篇就说 LOS_DL_LIST?
因为它在鸿蒙 LOS 内核中无处不在,可以说在整个内核占了极大的比重,基本通过它把所有的结构体像胶水一样粘在一起,豪不夸张的说理解LOS_DL_LIST及相关函数 是读懂鸿蒙内核的关键。前后指针就像人的两只左右手一样灵活的指挥着系统精准的运行,越是深入分析内核源码,越是能体会在内核开发者对LOS_DL_LIST的非凡的驾驭能力,笔者仿佛看到了无数双手前后相连,拉起了无数个双向循环链表,把指针妙处运用到了极致,这也许就是编程的艺术吧!
致敬鸿蒙内核开发者,鸿蒙内核源码可以作为大学操作系统,数据结构两门课的教学项目。
/**
* @ingroup los_list
* Structure of a node in a doubly linked list.
*/
typedef struct LOS_DL_LIST {
struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node */
struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node */
} LOS_DL_LIST;
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
{
list->pstNext = list;
list->pstPrev = list;
}
真的是无处不在吗?答:是真的,看看使用它的源码吧,无处不在。
基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。
二重リンクリスト内の任意のノードから始めて、その先行ノードと後続ノードに簡単にアクセスできます。この形式のデータ構造により、特に大量のデータトラバーサルの場合に、二重リンクリストを検索しやすくなります。二重リンクリストの対称性により、挿入や削除などのさまざまな操作を手軽に完了できますが、前後方向の操作には注意が必要です。
関数インターフェース
Huawei LiteOSシステムの二重リンクリストモジュールは、ユーザーに次のインターフェースを提供します。
関数分類 |
インターフェース名 |
解説 |
---|---|---|
リンクリストを初期化する |
LOS_ListInit |
リンクリストを初期化します。 |
ノードを追加 |
LOS_ListAdd |
リンクリストに新しいノードを追加します。 |
リンクリストの最後にノードを挿入する |
LOS_ListTailInsert |
二重リンクリストの最後にノードを挿入します。 |
ノードを削除 |
LOS_ListDelete |
リンクリストから指定されたノードを削除します。 |
二重リンクリストが空かどうかを判別 |
LOS_ListEmpty |
リンクリストが空かどうかを確認します。 |
ノードを削除し、リンクリストを初期化します |
LOS_ListDelInit |
リンクリストから指定されたノードを削除し、このノードを使用してリンクリストを初期化します。 |
リンクリストをリンクリストに挿入する | LOS_ListAddList | 2つの循環リンクリストから大きな循環リンクリスト |
末尾からノードを挿入 | LOS_ListTailInsert | (LOS_DL_LIST *リスト、LOS_DL_LIST *ノード) |
頭からノードを挿入 | LOS_ListHeadInsert | (LOS_DL_LIST *リスト、LOS_DL_LIST *ノード) |
リンクされたリストを最後から挿入する | LOS_ListTailInsertList | (LOS_DL_LIST * oldList、LOS_DL_LIST * newList) |
先頭からリンクリストを挿入 | LOS_ListTailInsertList | (LOS_DL_LIST * oldList、LOS_DL_LIST * newList) |
Hongmengは、双方向の循環リンクリストを使用して、構造データ構造間の関連付けを実現し、単一ノードの先頭と末尾の挿入をサポートします。さらに、リンクリストが別のリンクリストの挿入をサポートし、2つの循環リンクリストを大きな円形リンクリストに結合することは非常に微妙です。独創的でシンプル。詳細については、コードを参照してください
//双向链表初始化
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
{
list->pstNext = list; // 前后指针都指向自己
list->pstPrev = list;
}
//链表判空,检查前后指针是否指向自己
LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *list)
{
return (BOOL)(list->pstNext == list);
}
//从链表中删除节点
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;
}
//指针互换,具体向双向循环链表中插入节点
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;
}
// 两个循环链表合成一个大循环列表
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;
}
// 这里与其说插入不如说合并,同样支持从头或尾部合并
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
{
LOS_ListAddList(oldList->pstPrev, newList);
}
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListHeadInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
{
LOS_ListAddList(oldList, newList);
}
Hongmengカーネルのソースコードを読み取るときは、LOS_DL_LISTを使用してコード間の関係をリアルタイムで理解し、ランタイムシーンを想像することで、カーネルコードの美しさを体験できます。
特定の使用シナリオ
その使用シナリオの1つを見て、デザイナーのすばらしい意図を体験し、コードをアップロードします。
typedef struct ProcessCB {
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */
UINT32 processID; /**< process ID = leader thread ID */
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
running in the process */
LOS_DL_LIST pendList; /**< Block list to which the process belongs */
LOS_DL_LIST childrenList; /**< my children process list */
LOS_DL_LIST exitChildList; /**< my exit children process list */
LOS_DL_LIST siblingList; /**< linkage in my parent's children list */
ProcessGroup *group; /**< Process group to which a process belongs */
LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */
UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */
UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
process */
LOS_DL_LIST threadSiblingList; /**< List of threads under this process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
LOS_DL_LIST waitList; /**< The process holds the waitLits to support
} LosProcessCB;
これはLosProcessCB(プロセス制御ブロック)です。これは、構造が非常に複雑であり、他の定義が省略されているため、LOS_DL_LISTは関連しています。カーネルソースコードは自分で読み取ることができます。LosProcessCBには、7つの双方向循環リンクリストが含まれ、プロセスグループのキューは配列です、32個の準備完了キューの二重循環リンクリストが含まれています。これらのリンクされたリストには、ライフサイクル内のプロセスの実行中のプロセスロジック、プロセスとスレッド間の関係ロジック、スレッドの実行中のプロセスロジックなどが含まれます。はい、このような複雑なデータ構造は、誕生から死に至るプロセスを記述するために必要ですのプロセス。
タスクキューに関連するコード
これらは、タスクキューがデキューしてキューに入る操作です。その背後には、LOS_DL_LISTの追加と削除のプロセスがあります。落ち着いてこれらのマクロを理解してください。
#define OS_PROCESS_PRI_QUEUE_SIZE(processCB) OsPriQueueProcessSize(g_priQueueList, (processCB)->priority)
#define OS_TASK_PRI_QUEUE_ENQUEUE(processCB, taskCB) \
OsPriQueueEnqueue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), \
&((taskCB)->pendList), (taskCB)->priority)
#define OS_TASK_PRI_QUEUE_ENQUEUE_HEAD(processCB, taskCB) \
OsPriQueueEnqueueHead((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), \
&((taskCB)->pendList), (taskCB)->priority)
#define OS_TASK_PRI_QUEUE_DEQUEUE(processCB, taskCB) \
OsPriQueueDequeue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), &((taskCB)->pendList))
#define OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, status) OsTaskSchedQueueEnqueue(taskCB, status)
#define OS_TASK_SCHED_QUEUE_DEQUEUE(taskCB, status) OsTaskSchedQueueDequeue(taskCB, status)
#define OS_PROCESS_PRI_QUEUE_ENQUEUE(processCB) \
OsPriQueueEnqueue(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
#define OS_PROCESS_PRI_QUEUE_ENQUEUE_HEAD(processCB) \
OsPriQueueEnqueueHead(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
#define OS_PROCESS_PRI_QUEUE_DEQUEUE(processCB) OsPriQueueProcessDequeue(&((processCB)->pendList))
#define OS_TASK_PRI_QUEUE_SIZE(processCB, taskCB) OsPriQueueSize((processCB)->threadPriQueueList, (taskCB)->priority)
#define OS_TASK_GET_NEW(processCB) LOS_DL_LIST_ENTRY(OsPriQueueTop((processCB)->threadPriQueueList, \
&((processCB)->threadScheduleMap)), \
LosTaskCB, pendList)
インライン関数インライン
Hongmengカーネルは多くのインライン関数を使用していますが、インライン関数の利点は何ですか?わからない場合はご自身で確認してください。ここでは基本的な知識は普及していません。ソースlos_list.h、wood .cファイルのみ!最も頻繁に呼び出されるこれらのインライン関数は、通常の関数のようにスタックからポップアウトするために必要な時間とスペースを排除し、非常に効率的です。
/* Define OS code data sections */
/* The indicator function is inline */
#ifndef LITE_OS_SEC_ALW_INLINE
#define LITE_OS_SEC_ALW_INLINE /* __attribute__((always_inline)) */
#endif
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;
}
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
LOS_ListAdd(list->pstPrev, node);
}
このシリーズのすべての記事は、Hongmengシステムのソースコード分析(総合カタログ)ビューに入ります