0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內(nèi)不再提示

淺談鴻蒙內(nèi)核代碼調(diào)度隊列

鴻蒙系統(tǒng)HarmonyOS ? 來源:oschina ? 作者:鴻蒙內(nèi)核發(fā)燒友 ? 2020-10-23 11:00 ? 次閱讀

為何單獨講調(diào)度隊列?

鴻蒙內(nèi)核代碼中有兩個源文件是關于隊列的,一個是用于調(diào)度的隊列,另一個是用于線程間通訊的IPC隊列。

本文詳細講述調(diào)度隊列,詳見代碼: kernel_liteos_a/kernel/base/sched/sched_sq/los_priqueue.c

IPC隊列后續(xù)有專門的博文講述,這兩個隊列的數(shù)據(jù)結構實現(xiàn)采用的都是雙向循環(huán)鏈表,LOS_DL_LIST實在是太重要了,是理解鴻蒙內(nèi)核的關鍵,說是最重要的代碼一點也不為過,源碼出現(xiàn)在 sched_sq模塊,說明是用于任務的調(diào)度的,sched_sq模塊只有兩個文件,另一個los_sched.c就是調(diào)度代碼。

涉及函數(shù)

功能分類 接口 描述
創(chuàng)建隊列 OsPriQueueInit 創(chuàng)建了32個就緒隊列
獲取最高優(yōu)先級隊列 OsPriQueueTop 查最高優(yōu)先級任務
從頭部入隊列 OsPriQueueEnqueueHead 從頭部插入某個就緒隊列
從尾部入隊列 OsPriQueueEnqueue 默認是從尾部插入某個就緒隊列
出隊列 OsPriQueueDequeue 從最高優(yōu)先級的就緒隊列中刪除
OsPriQueueProcessDequeue 從進程隊列中刪除
OsPriQueueProcessSize 用進程查隊列中元素個數(shù)
OsPriQueueSize 用任務查隊列中元素個數(shù)
OsTaskPriQueueTop 查最高優(yōu)先級任務
OsDequeEmptySchedMap 進程出列
OsGetTopTask 獲取被調(diào)度選擇的task

鴻蒙內(nèi)核進程和線程各有32個就緒隊列,進程隊列用全局變量存放,創(chuàng)建進程時入隊,任務隊列放在進程的threadPriQueueList中。

映射張大爺?shù)墓适拢壕途w隊列就是在外面排隊的32個通道,按優(yōu)先級0-31依次排好,張大爺?shù)霓k公室有個牌子,類似打籃球的記分牌,一共32個,一字排開,隊列里有人時對應的牌就是1,沒有就是0 ,這樣張大爺每次從0位開始看,看到的第一個1那就是最高優(yōu)先級的那個人。辦公室里的記分牌就是位圖調(diào)度器。

位圖調(diào)度器

//*kfy 0x80000000U = 10000000000000000000000000000000(32位,1是用于移位的,設計之精妙,點贊) 
#define PRIQUEUE_PRIOR0_BIT   0x80000000U 

#ifndef CLZ
#define CLZ(value)                                  (__clz(value)) //匯編指令
#endif

LITE_OS_SEC_BSS LOS_DL_LIST *g_priQueueList = NULL; //所有的隊列 原始指針
LITE_OS_SEC_BSS UINT32 g_priQueueBitmap; // 位圖調(diào)度
// priority = CLZ(bitmap); // 獲取最高優(yōu)先級任務隊列 調(diào)度位

整個los_priqueue.c就只有兩個全部變量,一個是LOS_DL_LIST *g_priQueueList是32個進程就緒隊列的頭指針,在就緒隊列中會講另一個UINT32 g_priQueueBitmap 估計很多人會陌生,是一個32位的變量,叫位圖調(diào)度器。怎么理解它呢?

鴻蒙系統(tǒng)的調(diào)度是搶占式的,task分成32個優(yōu)先級,如何快速的知道哪個隊列是空的,哪個隊列里有任務需要一個標識,而且要極高效的實現(xiàn)?答案是:位圖調(diào)度器。

簡單說就是一個變量的位來標記對應隊列中是否有任務,在位圖調(diào)度下,任務優(yōu)先級的值越小則代表具有越高的優(yōu)先級,每當需要進行調(diào)度時,從最低位向最高位查找出第一個置 1 的位的所在位置,即為當前最高優(yōu)先級,然后從對應優(yōu)先級就緒隊列獲得相應的任務控制塊,整個調(diào)度器的實現(xiàn)復雜度是 O(1),即無論任務多少,其調(diào)度時間是固定的。

進程就緒隊列機制

CPU執(zhí)行速度是很快的,其運算速度和內(nèi)存的讀寫速度是數(shù)量級的差異,與硬盤的讀寫更是指數(shù)級。鴻蒙內(nèi)核默認一個時間片是 10ms,資源很寶貴,它不斷在眾多任務中來回的切換,所以絕不能讓CPU等待任務,CPU時間很寶貴,沒準備好的任務不要放進來。這就是進程和線程就緒隊列的機制,一共有32個任務就緒隊列,因為線程的優(yōu)先級是默認32個, 每個隊列中放同等優(yōu)先級的task.

隊列初始化做了哪些工作?詳細看代碼

#define OS_PRIORITY_QUEUE_NUM 32

UINT32 OsPriQueueInit(VOID)
{
    UINT32 priority;

    /* system resident resource */
    g_priQueueList = (LOS_DL_LIST *)LOS_MemAlloc(m_aucSysMem0, (OS_PRIORITY_QUEUE_NUM * sizeof(LOS_DL_LIST)));
    if (g_priQueueList == NULL) {
        return LOS_NOK;
    }

    for (priority = 0; priority < OS_PRIORITY_QUEUE_NUM; ++priority) {
        LOS_ListInit(&g_priQueueList[priority]);
    }
    return LOS_OK;
}

因TASK 有32個優(yōu)先級,在初始化時內(nèi)核一次性創(chuàng)建了32個雙向循環(huán)鏈表,每種優(yōu)先級都有一個隊列來記錄就緒狀態(tài)的tasks的位置,g_priQueueList分配的是一個連續(xù)的內(nèi)存塊,存放了32個LOS_DL_LIST,再看一下LOS_DL_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;

幾個常用函數(shù)

還是看入隊和出隊的源碼吧,注意bitmap的變化!

從代碼中可以知道,調(diào)用了LOS_ListTailInsert(&priQueueList[priority], priqueueItem); 注意是從循環(huán)鏈表的尾部插入的,也就是同等優(yōu)先級的TASK被排在了最后一個執(zhí)行,只要每次都是從尾部插入,就形成了一個按順序執(zhí)行的隊列。鴻蒙內(nèi)核的設計可謂非常巧妙,用極少的代碼,極高的效率實現(xiàn)了隊列功能。

VOID OsPriQueueEnqueue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
    /*
     * Task control blocks are inited as zero. And when task is deleted,
     * and at the same time would be deleted from priority queue or
     * other lists, task pend node will restored as zero.
     */
    LOS_ASSERT(priqueueItem->pstNext == NULL);

    if (LOS_ListEmpty(&priQueueList[priority])) {
        *bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;//對應優(yōu)先級位 置1
    }

    LOS_ListTailInsert(&priQueueList[priority], priqueueItem);
}

VOID OsPriQueueEnqueueHead(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem, UINT32 priority)
{
    /*
     * Task control blocks are inited as zero. And when task is deleted,
     * and at the same time would be deleted from priority queue or
     * other lists, task pend node will restored as zero.
     */
    LOS_ASSERT(priqueueItem->pstNext == NULL);

    if (LOS_ListEmpty(&priQueueList[priority])) {
        *bitMap |= PRIQUEUE_PRIOR0_BIT >> priority;//對應優(yōu)先級位 置1
    }

    LOS_ListHeadInsert(&priQueueList[priority], priqueueItem);
}

VOID OsPriQueueDequeue(LOS_DL_LIST *priQueueList, UINT32 *bitMap, LOS_DL_LIST *priqueueItem)
{
    LosTaskCB *task = NULL;
    LOS_ListDelete(priqueueItem);

    task = LOS_DL_LIST_ENTRY(priqueueItem, LosTaskCB, pendList);
    if (LOS_ListEmpty(&priQueueList[task->priority])) {
        *bitMap &= ~(PRIQUEUE_PRIOR0_BIT >> task->priority);//隊列空了,對應優(yōu)先級位 置0
    }
}

同一個進程下的線程的優(yōu)先級可以不一樣嗎?

請先想一下這個問題。

進程和線程是一對多的父子關系,內(nèi)核調(diào)度的單元是任務(線程),鴻蒙內(nèi)核中任務和線程是一個東西,只是不同的身份。一個進程可以有多個線程,線程又有各自獨立的狀態(tài),那進程狀態(tài)該怎么界定?例如:ProcessA有 TaskA(阻塞狀態(tài)),TaskB(就緒狀態(tài)) 兩個線程,ProcessA是屬于阻塞狀態(tài)還是就緒狀態(tài)呢?

先看官方文檔的說明后再看源碼。

進程狀態(tài)遷移說明:

Init→Ready:

進程創(chuàng)建或fork時,拿到該進程控制塊后進入Init狀態(tài),處于進程初始化階段,當進程初始化完成將進程插入調(diào)度隊列,此時進程進入就緒狀態(tài)。

Ready→Running:

進程創(chuàng)建后進入就緒態(tài),發(fā)生進程切換時,就緒列表中最高優(yōu)先級的進程被執(zhí)行,從而進入運行態(tài)。若此時該進程中已無其它線程處于就緒態(tài),則該進程從就緒列表刪除,只處于運行態(tài);若此時該進程中還有其它線程處于就緒態(tài),則該進程依舊在就緒隊列,此時進程的就緒態(tài)和運行態(tài)共存。

Running→Pend:

進程內(nèi)所有的線程均處于阻塞態(tài)時,進程在最后一個線程轉(zhuǎn)為阻塞態(tài)時,同步進入阻塞態(tài),然后發(fā)生進程切換。

Pend→Ready / Pend→Running:

阻塞進程內(nèi)的任意線程恢復就緒態(tài)時,進程被加入到就緒隊列,同步轉(zhuǎn)為就緒態(tài),若此時發(fā)生進程切換,則進程狀態(tài)由就緒態(tài)轉(zhuǎn)為運行態(tài)。

Ready→Pend:

進程內(nèi)的最后一個就緒態(tài)線程處于阻塞態(tài)時,進程從就緒列表中刪除,進程由就緒態(tài)轉(zhuǎn)為阻塞態(tài)。

Running→Ready:

進程由運行態(tài)轉(zhuǎn)為就緒態(tài)的情況有以下兩種:

有更高優(yōu)先級的進程創(chuàng)建或者恢復后,會發(fā)生進程調(diào)度,此刻就緒列表中最高優(yōu)先級進程變?yōu)檫\行態(tài),那么原先運行的進程由運行態(tài)變?yōu)榫途w態(tài)。

若進程的調(diào)度策略為SCHED_RR,且存在同一優(yōu)先級的另一個進程處于就緒態(tài),則該進程的時間片消耗光之后,該進程由運行態(tài)轉(zhuǎn)為就緒態(tài),另一個同優(yōu)先級的進程由就緒態(tài)轉(zhuǎn)為運行態(tài)。

Running→Zombies:

當進程的主線程或所有線程運行結束后,進程由運行態(tài)轉(zhuǎn)為僵尸態(tài),等待父進程回收資源。

注意看上面紅色的部分,一個進程竟然可以兩種狀態(tài)共存!

UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently

                                                            running in the process */

    processCB->processStatus &= ~(status | OS_PROCESS_STATUS_PEND);//取反后的與位運算
    processCB->processStatus |= OS_PROCESS_STATUS_READY;//或位運算

一個變量存兩種狀態(tài),怎么做到的?答案還是按位保存啊。還記得上面的位圖調(diào)度g_priQueueBitmap嗎,那可是存了32種狀態(tài)的。其實這在任何一個系統(tǒng)的內(nèi)核源碼中都很常見,類似的還有左移 <<,右移 >>等等

繼續(xù)說進程和線程的關系,線程的優(yōu)先級必須和進程一樣嗎?他們可以不一樣嗎?答案是:可以不一樣,否則怎么會有設置task優(yōu)先級的函數(shù)。

線程調(diào)度器

真正讓CPU工作的是線程,進程只是個裝線程的容器,線程有任務??臻g,是獨立運行于內(nèi)核空間,而進程只有用戶空間,具體在后續(xù)的內(nèi)存篇會講,這里不展開說,但進程結構體LosProcessCB有一個這樣的定義??疵志椭懒耍鞘歉{(diào)度相關的。

    UINT32               threadScheduleMap;            /**< The scheduling bitmap table for the thread group of the
                                                            process */
    LOS_DL_LIST          threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
                                                                         priority hash table */

咋一看怎么進程的結構體里也有32個隊列,其實這就是線程的就緒狀態(tài)隊列。threadScheduleMap就是進程自己的位圖調(diào)度器。具體看進程入隊和出隊的源碼。調(diào)度過程是先去進程就緒隊列里找最高優(yōu)先級的進程,然后去該進程找最高優(yōu)先級的線程來調(diào)度。具體看筆者認為的內(nèi)核最美函數(shù)OsGetTopTask,能欣賞到他的美就讀懂了就緒隊列是怎么管理的。

LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
    UINT32 priority, processPriority;
    UINT32 bitmap;
    UINT32 processBitmap;
    LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
    UINT32 cpuid = ArchCurrCpuid();
#endif
    LosProcessCB *processCB = NULL;
    processBitmap = g_priQueueBitmap;
    while (processBitmap) {
        processPriority = CLZ(processBitmap);
        LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
            bitmap = processCB->threadScheduleMap;
            while (bitmap) {
                priority = CLZ(bitmap);
                LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
                    if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
                        newTask->taskStatus &= ~OS_TASK_STATUS_READY;
                        OsPriQueueDequeue(processCB->threadPriQueueList,
                                          &processCB->threadScheduleMap,
                                          &newTask->pendList);
                        OsDequeEmptySchedMap(processCB);
                        goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
                    }
#endif
                }
                bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
            }
        }
        processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
    }

OUT:
    return newTask;
}
映射張大爺?shù)墓适拢簭埓鬆敽暗綇埲皶r進場時表演時,張全蛋要決定自己的哪個節(jié)目先表演,也要查下他的清單上優(yōu)先級,它同樣也有個張大爺同款記分牌,就這么簡單。
編輯:hfy
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
收藏 人收藏

    評論

    相關推薦

    鴻蒙內(nèi)核源碼Task/線程技術分析

    、使用內(nèi)存空間等系統(tǒng)資源,并獨立于其它線程運行。 鴻蒙內(nèi)核每個進程內(nèi)的線程獨立運行、獨立調(diào)度,當前進程內(nèi)線程的調(diào)度不受其它進程內(nèi)線程的影響。 鴻蒙
    的頭像 發(fā)表于 10-18 10:42 ?2218次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼Task/線程技術分析

    (轉(zhuǎn))HarmonyOS(鴻蒙OS)發(fā)布,聊聊操作系統(tǒng)的調(diào)度

    內(nèi)核,但不是這篇。 本文想再談談關于人機交互操作系統(tǒng)本身以及微內(nèi)核,調(diào)度等操作系統(tǒng)比較核心的問題。 也許,鴻蒙內(nèi)核確實對
    發(fā)表于 08-20 08:00

    【HarmonyOS】鴻蒙內(nèi)核源碼分析(調(diào)度機制篇)

    看task從哪些渠道產(chǎn)生:渠道很多,可能是shell 的一個命令,也可能由內(nèi)核創(chuàng)建,更多的是大家編寫應用程序new出來的一個線程。調(diào)度的內(nèi)容已經(jīng)有了,那他們?nèi)绾斡行虻谋?b class='flag-5'>調(diào)度?答案:是32個進程和線程就緒
    發(fā)表于 10-14 14:00

    鴻蒙源碼分析系列(總目錄) | 給HarmonyOS源碼逐行加上中文注釋

    |-鴻蒙內(nèi)核源碼分析(調(diào)度機制篇) | 任務是如何被調(diào)度執(zhí)行的|-鴻蒙內(nèi)核源碼分析(
    發(fā)表于 11-20 11:24

    鴻蒙內(nèi)核源碼分析(調(diào)度機制篇):Task是如何被調(diào)度執(zhí)行的

    本文分析任務調(diào)度機制源碼 詳見:代碼庫建議先閱讀閱讀之前建議先讀本系列其他文章,進入鴻蒙系統(tǒng)源碼分析(總目錄),以便對本文任務調(diào)度機制的理解。為什么學一個東西要學那么多的概念?
    發(fā)表于 11-23 10:53

    鴻蒙內(nèi)核源碼分析(調(diào)度隊列篇):進程和Task的就緒隊列調(diào)度的作用

    為何單獨講調(diào)度隊列?鴻蒙內(nèi)核代碼中有兩個源文件是關于隊列的,一個是用于
    發(fā)表于 11-23 11:09

    鴻蒙內(nèi)核源碼分析(Task管理篇):task是內(nèi)核調(diào)度的單元

    獨立運行、獨立調(diào)度,當前進程內(nèi)線程的調(diào)度不受其它進程內(nèi)線程的影響。鴻蒙內(nèi)核中的線程采用搶占式調(diào)度機制,同時支持時間片輪轉(zhuǎn)
    發(fā)表于 11-23 14:01

    鴻蒙內(nèi)核源碼分析(Task管理篇):task是內(nèi)核調(diào)度的單元

    )代碼 ,這是怎么回事?其實在鴻蒙內(nèi)核中, task就是線程, 初學者完全可以這么理解,但二者還是有區(qū)別,否則干嘛要分兩個詞描述。到底有什么區(qū)別?是管理上的區(qū)別,task是調(diào)度層面的概
    發(fā)表于 11-24 10:24

    VxWorks實時內(nèi)核調(diào)度的研究分析

    VxWorks實時內(nèi)核調(diào)度的研究分析論述了0S中調(diào)度的概念、類型、調(diào)度隊列模型,并著重對VxWorks實時
    發(fā)表于 12-16 14:07 ?13次下載

    Vx Works實時內(nèi)核調(diào)度的研究分析

    論述了OS 中調(diào)度的概念、類型、調(diào)度隊列模型,并著重對VxWorks 實時內(nèi)核進行了分析。關鍵詞:嵌入式實時操作系統(tǒng)(RTOS) ;VxWorks ;
    發(fā)表于 03-25 10:36 ?33次下載

    VxWorks實時內(nèi)核調(diào)度的研究分析

    論述了0S中調(diào)度的概念、類型、調(diào)度隊列模型,并著重對VxWorks實時內(nèi)核進行了分析。
    發(fā)表于 11-27 16:22 ?16次下載

    淺談鴻蒙內(nèi)核源碼的棧

    上面的代碼鴻蒙內(nèi)核用棧方式一樣,都采用了遞減滿棧的方式, 什么是遞減滿棧?
    的頭像 發(fā)表于 04-24 11:21 ?1443次閱讀
    <b class='flag-5'>淺談</b><b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼的棧

    鴻蒙內(nèi)核源碼:誰來觸發(fā)調(diào)度工作?

    鴻蒙內(nèi)核中 Task 和 線程 在廣義上可以理解為是一個東西,但狹義上肯定會有區(qū)別,區(qū)別在于管理體系的不同,Task是調(diào)度層面的概念,線程是進程層面概念。
    的頭像 發(fā)表于 04-24 10:50 ?1503次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼:誰來觸發(fā)<b class='flag-5'>調(diào)度</b>工作?

    鴻蒙內(nèi)核源碼分析:task是內(nèi)核調(diào)度的單元

    從系統(tǒng)的角度看,線程是競爭系統(tǒng)資源的最小運行單元。線程可以使用或等待CPU、使用內(nèi)存空間等系統(tǒng)資源,并獨立于其它線程運行。 鴻蒙內(nèi)核每個進程內(nèi)的線程獨立運行、獨立調(diào)度,當前進程內(nèi)線程的調(diào)度
    發(fā)表于 11-23 15:51 ?22次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼分析:task是<b class='flag-5'>內(nèi)核</b><b class='flag-5'>調(diào)度</b>的單元

    鴻蒙內(nèi)核源碼分析:進程和Task的就緒隊列調(diào)度的作用

    鴻蒙內(nèi)核代碼中有兩個源文件是關于隊列的,一個是用于調(diào)度隊列,另一個是用于線程間通訊的IPC
    發(fā)表于 11-23 15:48 ?31次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>源碼分析:進程和Task的就緒<b class='flag-5'>隊列</b>對<b class='flag-5'>調(diào)度</b>的作用