理解并發概念
并發(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