鴻蒙內核源碼分析 (并發并行篇) | 內核如何管理多個 CPU?

理解并發概念

  • 并發(Concurrent): 多個線程在單個核心運行,同一時間只能一個線程運行,內核不停切換線程,看起來像同時運行,實際上是線程被高速的切換.

  • 通俗好理解的比喻就是高速單行道,單行道指的是 CPU 的核數,跑的車就是線程 (任務),進程就是管理車的公司,一個公司可以有很多臺車。并發和并行跟 CPU 的核數有關。車道上同時只能跑一輛車,但因為指揮系統很牛,夠快,在毫秒級內就能換車跑,人根本感知不到切換。所以外部的感知會是同時在進行,實現了微觀上的串行,宏觀上的并行.

線程切換的本質是 CPU 要換場地上班,去哪里上班由哪里提供場地,那個場地就是任務棧,每個任務棧中保存了上班的各種材料,來了就行立馬干活。那些材料就是任務上下文。簡單的說就是上次活干到那里了,回來繼續接著干。上下文由任務棧自己保存,CPU 不管的,它來了只負責任務交過來的材料,材料顯示去哪里搬磚它就去哪里搬磚.

記住一個單詞就能記住并行并發的區別, 發單,發單 (并發單行).

理解并行概念

并行(Parallel)每個線程分配給獨立的 CPU 核心,線程真正的同時運行.

通俗好理解的比喻就是高速多行道,實現了微觀和宏觀上同時進行。并行當然是快,人多了干活就不那么累,但干活人多了必然會帶來人多的管理問題,會把問題變復雜,請想想會出現哪些問題?

理解協程概念

這里說下協程,例如 go 語言是有協程支持的,其實協程跟內核層沒有關系,是應用層的概念。是在線程之上更高層的封裝,用通俗的比喻來說就是在車內另外搞了幾條車道玩。其對內核來說沒有新東西,內核只負責車的調度,至于車內你想怎么弄那是應用程序自己的事。本質的區別是 CPU 根本沒有換地方上班 (沒有被調度),而并發 / 并行都是換地方上班了.

內核如何描述 CPU

    typedef struct {
        SortLinkAttribute taskSortLink;             /* task sort link */ //每個CPU core 都有一個task排序鏈表
        SortLinkAttribute swtmrSortLink;            /* swtmr sort link */ //每個CPU core 都有一個定時器排序鏈表

        UINT32 idleTaskID;                          /* idle task id */  //空閑任務ID 見于 OsIdleTaskCreate
        UINT32 taskLockCnt;                         /* task lock flag */ //任務鎖的數量,當 > 0 的時候,需要重新調度了
        UINT32 swtmrHandlerQueue;                   /* software timer timeout queue id */ //軟時鐘超時隊列句柄
        UINT32 swtmrTaskID;                         /* software timer task id */ //軟時鐘任務ID

        UINT32 schedFlag;                           /* pending scheduler flag */ //調度標識 INT_NO_RESCH INT_PEND_RESCH
    #if (LOSCFG_KERNEL_SMP == YES)
        UINT32 excFlag;                             /* cpu halt or exc flag */ //CPU處于停止或運行的標識
    #endif
    } Percpu;

    Percpu g_percpu[LOSCFG_KERNEL_CORE_NUM];//全局CPU數組

這是內核對 CPU 的描述,主要是兩個排序鏈表,一個是任務的排序,一個是定時器的排序。什么意思?在系列篇中多次提過,任務是內核的調度單元,注意可不是進程,雖然調度也需要進程參與,也需要切換進程,切換用戶空間。但調度的核心是切換任務,每個任務的代碼指令才是 CPU 的糧食,它吃的是一條條的指令。每個任務都必須指定取糧地址 (即入口函數).

另外還有一個東西能提供入口函數,就是定時任務。很重要也很常用,沒它某寶每晚 9 點的準時秒殺實現不了。在內核每個 CPU 都有自己獨立的任務和定時器鏈表.

每次 Tick 的到來,處理函數會去掃描這兩個鏈表,看有沒有定時器超時的任務需要執行,有則立即執行定時任務,定時任務是所有任務中優先級最高的,0 號優先級,在系列篇中有專門講定時器任務,可自行翻看.

LOSCFG_KERNEL_SMP

# if (LOSCFG_KERNEL_SMP == YES)
# define LOSCFG_KERNEL_CORE_NUM                          LOSCFG_KERNEL_SMP_CORE_NUM //多核情況下支持的CPU核數
# else
# define LOSCFG_KERNEL_CORE_NUM                          1 //單核配置
# endif

多 CPU 核的操作系統有 3 種處理模式 (SMP+AMP+BMP) 鴻蒙實現的是 SMP 的方式

  • 非對稱多處理(Asymmetric multiprocessing,AMP)每個 CPU 內核運行一個獨立的操作系統或同一操作系統的獨立實例(instantiation)。
  • 對稱多處理(Symmetric multiprocessing,SMP)一個操作系統的實例可以同時管理所有 CPU 內核,且應用并不綁定某一個內核。
  • 混合多處理(Bound multiprocessing,BMP)一個操作系統的實例可以同時管理所有 CPU 內核,但每個應用被鎖定于某個指定的核心。

宏 LOSCFG_KERNEL_SMP 表示對多 CPU 核的支持,鴻蒙默認是打開 LOSCFG_KERNEL_SMP 的。

多 CPU 核支持

鴻蒙內核對 CPU 的操作見于 los_mp.c ,因文件不大,這里把代碼都貼出來了.

    #if (LOSCFG_KERNEL_SMP == YES)
    //給參數CPU發送調度信號
    VOID LOS_MpSchedule(UINT32 target)//target每位對應CPU core 
    {
        UINT32 cpuid = ArchCurrCpuid();
        target &= ~(1U << cpuid);//獲取除了自身之外的其他CPU
        HalIrqSendIpi(target, LOS_MP_IPI_SCHEDULE);//向目標CPU發送調度信號,核間中斷(Inter-Processor Interrupts),IPI
    }
    //硬中斷喚醒處理函數
    VOID OsMpWakeHandler(VOID)
    {
        /* generic wakeup ipi, do nothing */
    }
    //硬中斷調度處理函數
    VOID OsMpScheduleHandler(VOID)
    {//將調度標志設置為與喚醒功能不同,這樣就可以在硬中斷結束時觸發調度程序。
        /*
        * set schedule flag to differ from wake function,
        * so that the scheduler can be triggered at the end of irq.
        */
        OsPercpuGet()->schedFlag = INT_PEND_RESCH;//給當前Cpu貼上調度標簽
    }
    //硬中斷暫停處理函數
    VOID OsMpHaltHandler(VOID)
    {
        (VOID)LOS_IntLock();
        OsPercpuGet()->excFlag = CPU_HALT;//讓當前Cpu停止工作

        while (1) {}//陷入空循環,也就是空閑狀態
    }
    //MP定時器處理函數, 遞歸檢查所有可用任務
    VOID OsMpCollectTasks(VOID)
    {
        LosTaskCB *taskCB = NULL;
        UINT32 taskID = 0;
        UINT32 ret;

        /* recursive checking all the available task */
        for (; taskID <= g_taskMaxNum; taskID++) { //遞歸檢查所有可用任務
            taskCB = &g_taskCBArray[taskID];

            if (OsTaskIsUnused(taskCB) || OsTaskIsRunning(taskCB)) {
                continue;
            }

            /* 雖然任務狀態不是原子的,但此檢查可能成功,但無法完成刪除,此刪除將在下次運行之前處理
            * though task status is not atomic, this check may success but not accomplish
            * the deletion; this deletion will be handled until the next run.
            */
            if (taskCB->signal & SIGNAL_KILL) {//任務收到被干掉信號
                ret = LOS_TaskDelete(taskID);//干掉任務,回歸任務池
                if (ret != LOS_OK) {
                    PRINT_WARN("GC collect task failed err:0x%x\n", ret);
                }
            }
        }
    }
    //MP(multiprocessing) 多核處理器初始化
    UINT32 OsMpInit(VOID)
    {
        UINT16 swtmrId;

        (VOID)LOS_SwtmrCreate(OS_MP_GC_PERIOD, LOS_SWTMR_MODE_PERIOD, //創建一個周期性,持續時間為 100個tick的定時器
                            (SWTMR_PROC_FUNC)OsMpCollectTasks, &swtmrId, 0);//OsMpCollectTasks為超時回調函數
        (VOID)LOS_SwtmrStart(swtmrId);//開始定時任務

        return LOS_OK;
    }
    #endif

代碼一一都加上了注解,這里再一一說明下:

1.OsMpInit

多 CPU 核的初始化, 多核情況下每個 CPU 都有各自的編號, 內核有分成主次 CPU, 0 號默認為主 CPU, OsMain () 由主 CPU 執行,被匯編代碼調用。初始化只開了個定時任務,只干一件事就是回收不用的任務。回收的條件是任務是否收到了被干掉的信號。例如 shell 命令 kill 9 14 ,意思是干掉 14 號線程的信號,這個信號會被線程保存起來。可以選擇自殺也可以等著被殺。這里要注意,鴻蒙有兩種情況下任務不能被干掉, 一種是系統任務不能被干掉的, 第二種是正在運行狀態的任務.

2. 次級 CPU 的初始化

同樣由匯編代碼調用,通過以下函數執行,完成每個 CPU 核的初始化

    //次級CPU初始化,本函數執行的次數由次級CPU的個數決定. 例如:在四核情況下,會被執行3次, 0號通常被定義為主CPU 執行main
    LITE_OS_SEC_TEXT_INIT VOID secondary_cpu_start(VOID)
    {
    #if (LOSCFG_KERNEL_SMP == YES)
        UINT32 cpuid = ArchCurrCpuid();

        OsArchMmuInitPerCPU();//每個CPU都需要初始化MMU

        OsCurrTaskSet(OsGetMainTask());//設置CPU的當前任務

        /* increase cpu counter */
        LOS_AtomicInc(&g_ncpu); //統計CPU的數量

        /* store each core's hwid */
        CPU_MAP_SET(cpuid, OsHwIDGet());//存儲每個CPU的 hwid
        HalIrqInitPercpu(); //CPU硬件中斷初始化

        OsCurrProcessSet(OS_PCB_FROM_PID(OsGetKernelInitProcessID())); //設置內核進程為CPU進程
        OsSwtmrInit();  //定時任務初始化,每個CPU維護自己的定時器隊列
        OsIdleTaskCreate(); //創建空閑任務,每個CPU維護自己的任務隊列
        OsStart(); //本CPU正式啟動在內核層的工作
        while (1) {
            __asm volatile("wfi");//wait for Interrupt 等待中斷,即下一次中斷發生前都在此hold住不干活
        }//類似的還有 WFE: wait for Events 等待事件,即下一次事件發生前都在此hold住不干活
    #endif
    }

可以看出次級 CPU 有哪些初始化步驟:

  • 初始化 MMU,OsArchMmuInitPerCPU
  • 設置當前任務 OsCurrTaskSet
  • 初始化硬件中斷 HalIrqInitPercpu
  • 初始化定時器隊列 OsSwtmrInit
  • 創建空任務 OsIdleTaskCreate, 外面沒有任務的時 CPU 就待在這個空任務里自己轉圈圈.
  • 開始自己的工作流程 OsStart,正式開始工作,跑任務

寫在最后

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
  • 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容