首頁>技術>

鴻蒙核心原始碼中文註解 >> 精讀核心原始碼,中文註解分析,深挖地基工程,大腦永久記憶,四大原始碼倉每日同步更新

理解併發概念

併發(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,正式開始工作,跑任務

多CPU核還有哪些問題?

1.CPU之間搶資源的情況要怎麼處理?

2.CPU之間通訊(也叫核間通訊)怎麼解決?

3.如果確保兩個CPU不會同時執行同一個任務?

4.彙編程式碼如何實現對各CPU的調動

請前往系列篇或直接前往核心註解程式碼檢視.這裡不再做說明.

16
最新評論
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • 軟體架構模式之事件驅動架構