鴻蒙輕內(nèi)核M核源碼分析系列四中斷Hwi
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
鴻蒙輕內(nèi)核M核源碼分析系列五 中斷Hwi
在鴻蒙輕內(nèi)核源碼分析系列前幾篇文章中,剖析了重要的數(shù)據(jù)結(jié)構(gòu)。本文,我們講述一下中斷,會(huì)給讀者介紹中斷的概念,鴻蒙輕內(nèi)核的中斷模塊的源代碼。
1、中斷概念介紹
中斷是指出現(xiàn)需要時(shí),CPU暫停執(zhí)行當(dāng)前程序,轉(zhuǎn)而執(zhí)行新程序的過程。當(dāng)外設(shè)需要CPU時(shí),將通過產(chǎn)生中斷信號(hào)使CPU立即中斷當(dāng)前任務(wù)來響應(yīng)中斷請求。在剖析中斷源代碼之前,下面介紹些中斷相關(guān)的硬件、中斷相關(guān)的概念。
1.1 中斷相關(guān)的硬件介紹
與中斷相關(guān)的硬件可以劃分為三類:設(shè)備、中斷控制器、CPU本身。
- 設(shè)備
 
發(fā)起中斷的源,當(dāng)設(shè)備需要請求CPU時(shí),產(chǎn)生一個(gè)中斷信號(hào),該信號(hào)連接至中斷控制器。
- 中斷控制器
 
中斷控制器是CPU眾多外設(shè)中的一個(gè),它一方面接收其它外設(shè)中斷引腳的輸入。另一方面,它會(huì)發(fā)出中斷信號(hào)給CPU??梢酝ㄟ^對中斷控制器編程來打開和關(guān)閉中斷源、設(shè)置中斷源的優(yōu)先級(jí)和觸發(fā)方式。
- CPU
 
CPU會(huì)響應(yīng)中斷源的請求,中斷當(dāng)前正在執(zhí)行的任務(wù),轉(zhuǎn)而執(zhí)行中斷處理程序。
1.2 中斷相關(guān)的概念
- 中斷號(hào)
 
每個(gè)中斷請求信號(hào)都會(huì)有特定的標(biāo)志,使得計(jì)算機(jī)能夠判斷是哪個(gè)設(shè)備提出的中斷請求,這個(gè)標(biāo)志就是中斷號(hào)。
- 中斷優(yōu)先級(jí)
 
為使系統(tǒng)能夠及時(shí)響應(yīng)并處理所有中斷,系統(tǒng)根據(jù)中斷時(shí)間的重要性和緊迫程度,將中斷源分為若干個(gè)級(jí)別,稱作中斷優(yōu)先級(jí)。
- 中斷處理程序
 
當(dāng)外設(shè)產(chǎn)生中斷請求后,CPU暫停當(dāng)前的任務(wù),轉(zhuǎn)而響應(yīng)中斷申請,即執(zhí)行中斷處理程序。產(chǎn)生中斷的每個(gè)設(shè)備都有相應(yīng)的中斷處理程序。
- 中斷向量
 
中斷服務(wù)程序的入口地址。
- 中斷向量表
 
存儲(chǔ)中斷向量的存儲(chǔ)區(qū),中斷向量與中斷號(hào)對應(yīng),中斷向量在中斷向量表中按照中斷號(hào)順序存儲(chǔ)。
- 中斷共享
 
當(dāng)外設(shè)較少時(shí),可以實(shí)現(xiàn)一個(gè)外設(shè)對應(yīng)一個(gè)中斷號(hào),但為了支持更多的硬件設(shè)備,可以讓多個(gè)設(shè)備共享一個(gè)中斷號(hào),共享同一個(gè)中斷號(hào)的中斷處理程序形成一個(gè)鏈表。當(dāng)外部設(shè)備產(chǎn)生中斷申請時(shí),系統(tǒng)會(huì)遍歷中斷號(hào)對應(yīng)的中斷處理程序鏈表,直到找到對應(yīng)設(shè)備的中斷處理程序。在遍歷執(zhí)行過程中,各中斷處理程序可以通過檢測設(shè)備ID,判斷是否是這個(gè)中斷處理程序?qū)?yīng)的設(shè)備產(chǎn)生的中斷。
接下來,我們再看看鴻蒙輕內(nèi)核中斷源代碼。
2、鴻蒙輕內(nèi)核中斷源代碼
2.1 中斷相關(guān)的聲明和定義
在文件kernel\arch\arm\cortex-m7\gcc\los_interrupt.c中定義了一些結(jié)構(gòu)體、全局變量、內(nèi)聯(lián)函數(shù),在分析源碼之前,我們先看下這些定義和聲明。全部變量g_intCount表示正在處理的中斷數(shù)量,每次進(jìn)入中斷處理程序時(shí),都會(huì)把該變量數(shù)值加1,完成中斷處理退出時(shí),該數(shù)值減1。對應(yīng)的內(nèi)聯(lián)函數(shù)HalIsIntActive()用于獲取是否正在處理中斷,返回值大于0,則表示正在處理中斷。
- UINT32 g_intCount = 0;
 - inline UINT32 HalIsIntActive(VOID)
 - {
 - return (g_intCount > 0);
 - }
 
我們在再看看中斷向量表定義。⑴處代碼為系統(tǒng)支持的中斷定義了數(shù)組g_hwiForm[OS_VECTOR_CNT],對于每一個(gè)中斷號(hào)hwiNum,對應(yīng)的數(shù)組元素g_hwiForm[hwiNum]表示每一個(gè)中斷對應(yīng)的中斷處理執(zhí)行入口程序。⑵處的宏OS_HWI_WITH_ARG表示中斷處理程序是否支持參數(shù)傳入,默認(rèn)關(guān)閉。如果支持傳參,定義⑶處的結(jié)構(gòu)體HWI_HANDLER_FUNC來維護(hù)中斷處理函數(shù)及其參數(shù),還需要定義⑷處g_hwiHandlerForm數(shù)組。如果不支持傳參,使用⑹處定義的g_hwiHandlerForm數(shù)組。對于每一個(gè)中斷號(hào)hwiNum,對應(yīng)的數(shù)組元素g_hwiHandlerForm[hwiNum]表示每一個(gè)中斷對應(yīng)的中斷處理程序。⑸、⑺處定義個(gè)函數(shù)OsSetVector()用于設(shè)置指定中斷號(hào)對應(yīng)的中斷處理執(zhí)行入口程序和中斷處理程序。中斷處理執(zhí)行入口程序和中斷處理程序的關(guān)系是,當(dāng)中斷發(fā)生時(shí),會(huì)執(zhí)行中斷處理執(zhí)行入口程序,這個(gè)函數(shù)會(huì)進(jìn)一步調(diào)用中斷處理程序。
- ⑴ STATIC HWI_PROC_FUNC __attribute__((aligned(0x100))) g_hwiForm[OS_VECTOR_CNT] = {0};
 - ⑵ #if (OS_HWI_WITH_ARG == 1)
 - ⑶ typedef struct {
 - HWI_PROC_FUNC pfnHandler;
 - VOID *pParm;
 - } HWI_HANDLER_FUNC;
 - ⑷ STATIC HWI_HANDLER_FUNC g_hwiHandlerForm[OS_VECTOR_CNT] = {{ (HWI_PROC_FUNC)0, (HWI_ARG_T)0 }};
 - ⑸ VOID OsSetVector(UINT32 num, HWI_PROC_FUNC vector, VOID *arg)
 - {
 - if ((num + OS_SYS_VECTOR_CNT) < OS_VECTOR_CNT) {
 - g_hwiForm[num + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalInterrupt;
 - g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT].pfnHandler = vector;
 - g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT].pParm = arg;
 - }
 - }
 - #else
 - ⑹ STATIC HWI_PROC_FUNC g_hwiHandlerForm[OS_VECTOR_CNT] = {0};
 - ⑺ VOID OsSetVector(UINT32 num, HWI_PROC_FUNC vector)
 - {
 - if ((num + OS_SYS_VECTOR_CNT) < OS_VECTOR_CNT) {
 - g_hwiForm[num + OS_SYS_VECTOR_CNT] = HalInterrupt;
 - g_hwiHandlerForm[num + OS_SYS_VECTOR_CNT] = vector;
 - }
 - }
 - #endif
 
2.2 中斷初始化HalHwiInit()
在系統(tǒng)啟動(dòng)時(shí),在kernel\src\los_init.c中調(diào)用HalArchInit()進(jìn)行中斷初始化。這個(gè)函數(shù)定義在kernel\arch\arm\cortex-m7\gcc\los_context.c,然后進(jìn)一步調(diào)用定義在kernel\arch\arm\cortex-m7\gcc\los_interrupt.c文件中HalHwiInit()函數(shù)完成中斷向量初始化。我們分析下代碼。
宏LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT表示是否使用系統(tǒng)預(yù)定義的向量基地址和中斷處理程序,默認(rèn)開啟。⑴處開始,中斷向量表的0號(hào)中斷設(shè)置為空,1號(hào)中斷對應(yīng)復(fù)位處理程序Reset_Handler。⑵處把其余的中斷設(shè)置為默認(rèn)的中斷處理執(zhí)行入口程序HalHwiDefaultHandler()。⑶處設(shè)置系統(tǒng)中斷(異常是中斷的一種,系統(tǒng)中斷也稱為異常),系統(tǒng)中斷的執(zhí)行入口函數(shù)定義在kernel\arch\arm\cortex-m7\gcc\los_exc.S,使用匯編語言實(shí)現(xiàn)。系統(tǒng)中斷中,14號(hào)中斷對應(yīng)HalPendSV處理程序,用于任務(wù)上下文切換,15號(hào)中斷是tick中斷。
執(zhí)行⑷處代碼把中斷向量表賦值給SCB->VTOR。對于Cortex-M3及以上的CPU核,還需要執(zhí)行⑸設(shè)置優(yōu)先級(jí)組。⑹處代碼使能指定的異常。
- LITE_OS_SEC_TEXT_INIT VOID HalHwiInit()
 - {
 - #if (LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT == 1)
 - UINT32 index;
 - ⑴ g_hwiForm[0] = 0; /* [0] Top of Stack */
 - g_hwiForm[1] = Reset_Handler; /* [1] reset */
 - ⑵ for (index = 2; index < OS_VECTOR_CNT; index++) { /* 2: The starting position of the interrupt */
 - g_hwiForm[index] = (HWI_PROC_FUNC)HalHwiDefaultHandler;
 - }
 - /* Exception handler register */
 - ⑶ g_hwiForm[NonMaskableInt_IRQn + OS_SYS_VECTOR_CNT] = HalExcNMI;
 - g_hwiForm[HARDFAULT_IRQN + OS_SYS_VECTOR_CNT] = HalExcHardFault;
 - g_hwiForm[MemoryManagement_IRQn + OS_SYS_VECTOR_CNT] = HalExcMemFault;
 - g_hwiForm[BusFault_IRQn + OS_SYS_VECTOR_CNT] = HalExcBusFault;
 - g_hwiForm[UsageFault_IRQn + OS_SYS_VECTOR_CNT] = HalExcUsageFault;
 - g_hwiForm[SVCall_IRQn + OS_SYS_VECTOR_CNT] = HalExcSvcCall;
 - g_hwiForm[PendSV_IRQn + OS_SYS_VECTOR_CNT] = HalPendSV;
 - g_hwiForm[SysTick_IRQn + OS_SYS_VECTOR_CNT] = SysTick_Handler;
 - /* Interrupt vector table location */
 - ⑷ SCB->VTOR = (UINT32)(UINTPTR)g_hwiForm;
 - #endif
 - #if (__CORTEX_M >= 0x03U) /* only for Cortex-M3 and above */
 - ⑸ NVIC_SetPriorityGrouping(OS_NVIC_AIRCR_PRIGROUP);
 - #endif
 - /* Enable USGFAULT, BUSFAULT, MEMFAULT */
 - ⑹ *(volatile UINT32 *)OS_NVIC_SHCSR |= (USGFAULT | BUSFAULT | MEMFAULT);
 - /* Enable DIV 0 and unaligned exception */
 - *(volatile UINT32 *)OS_NVIC_CCR |= DIV0FAULT;
 - return;
 - }
 
2.3 創(chuàng)建中斷UINT32 HalHwiCreate()
開發(fā)者可以調(diào)用函數(shù)UINT32 HalHwiCreate()創(chuàng)建中斷,注冊中斷處理程序。我們先看看這個(gè)函數(shù)的參數(shù),HWI_HANDLE_T hwiNum是硬件中斷號(hào),HWI_PRIOR_T hwiPrio中斷的優(yōu)先級(jí),HWI_MODE_T mode中斷模式,保留暫時(shí)沒有使用。HWI_PROC_FUNC handler是需要注冊的中斷處理程序,中斷被觸發(fā)后會(huì)調(diào)用這個(gè)函數(shù)。HWI_ARG_T arg是中斷處理程序的參數(shù)。
一起剖析下這個(gè)函數(shù)的源代碼,⑴處代碼開始,對入?yún)⑦M(jìn)行校驗(yàn),中斷處理程序不能為空,中斷號(hào)不能大于支持的最大中斷號(hào),中斷優(yōu)先級(jí)不能超過指定優(yōu)先級(jí)的大小。如果待創(chuàng)建的中斷號(hào)對應(yīng)的中斷執(zhí)行入口程序不等于HalHwiDefaultHandler,說明已經(jīng)創(chuàng)建過,返回錯(cuò)誤碼。關(guān)中斷,然后執(zhí)行⑵處的OsSetVector()函數(shù)設(shè)置指定中斷號(hào)的中斷處理程序。⑶處調(diào)用CMSIS函數(shù)使能中斷、設(shè)置中斷的優(yōu)先級(jí),打開中斷,完成中斷的創(chuàng)建。
- LITE_OS_SEC_TEXT_INIT UINT32 HalHwiCreate(HWI_HANDLE_T hwiNum,
 - HWI_PRIOR_T hwiPrio,
 - HWI_MODE_T mode,
 - HWI_PROC_FUNC handler,
 - HWI_ARG_T arg)
 - {
 - UINTPTR intSave;
 - ⑴ if (handler == NULL) {
 - return OS_ERRNO_HWI_PROC_FUNC_NULL;
 - }
 - if (hwiNum >= OS_HWI_MAX_NUM) {
 - return OS_ERRNO_HWI_NUM_INVALID;
 - }
 - if (g_hwiForm[hwiNum + OS_SYS_VECTOR_CNT] != (HWI_PROC_FUNC)HalHwiDefaultHandler) {
 - return OS_ERRNO_HWI_ALREADY_CREATED;
 - }
 - if (hwiPrio > OS_HWI_PRIO_LOWEST) {
 - return OS_ERRNO_HWI_PRIO_INVALID;
 - }
 - intSave = LOS_IntLock();
 - #if (OS_HWI_WITH_ARG == 1)
 - OsSetVector(hwiNum, handler, arg);
 - #else
 - ⑵ OsSetVector(hwiNum, handler);
 - #endif
 - ⑶ NVIC_EnableIRQ((IRQn_Type)hwiNum);
 - NVIC_SetPriority((IRQn_Type)hwiNum, hwiPrio);
 - LOS_IntRestore(intSave);
 - return LOS_OK;
 - }
 
2.4 刪除中斷UINT32 HalHwiDelete()
中斷刪除操作是創(chuàng)建操作的反向操作,也比較好理解。開發(fā)者可以調(diào)用函數(shù)UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)來刪除中斷。函數(shù)需要指定中斷號(hào)參數(shù)HWI_HANDLE_T hwiNum。一起剖析下這個(gè)函數(shù)的源代碼,⑴處代碼對入?yún)⑦M(jìn)行校驗(yàn),不能大于支持的最大中斷號(hào)。⑵處調(diào)用CMSIS函數(shù)來失能中斷,然后鎖中斷,執(zhí)行⑶把中斷向量表指定中斷號(hào)的中斷執(zhí)行入口程序設(shè)置為默認(rèn)程序HalHwiDefaultHandler。
- LITE_OS_SEC_TEXT_INIT UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)
 - {
 - UINT32 intSave;
 - ⑴ if (hwiNum >= OS_HWI_MAX_NUM) {
 - return OS_ERRNO_HWI_NUM_INVALID;
 - }
 - ⑵ NVIC_DisableIRQ((IRQn_Type)hwiNum);
 - intSave = LOS_IntLock();
 - ⑶ g_hwiForm[hwiNum + OS_SYS_VECTOR_CNT] = (HWI_PROC_FUNC)HalHwiDefaultHandler;
 - LOS_IntRestore(intSave);
 - return LOS_OK;
 - }
 
2.5 中斷處理執(zhí)行入口程序
我們再來看看中斷處理執(zhí)行入口程序。默認(rèn)的函數(shù)HalHwiDefaultHandler()如下,調(diào)用函數(shù)HalIntNumGet()獲取中斷號(hào),打印輸出,然后進(jìn)行死循環(huán)。其中函數(shù)HalIntNumGet()讀取寄存器ipsr來獲取觸發(fā)的中斷的中斷號(hào)。
- LITE_OS_SEC_TEXT_MINOR VOID HalHwiDefaultHandler(VOID)
 - {
 - UINT32 irqNum = HalIntNumGet();
 - PRINT_ERR("%s irqNum:%d\n", __FUNCTION__, irqNum);
 - while (1) {}
 - }
 
繼續(xù)來看中斷處理執(zhí)行入口程序HalInterrupt(),源碼如下。
⑴處把全局變量g_intCount表示的正在處理的中斷數(shù)量加1,在中斷執(zhí)行完畢后,在⑹處再把正在處理的中斷數(shù)量減1。⑵處調(diào)用函數(shù)HalIntNumGet()獲取中斷號(hào),⑶、⑸處調(diào)用的函數(shù)HalPreInterruptHandler(),HalAftInterruptHandler()在執(zhí)行中斷處理程序前、后可以處理些其他操作,當(dāng)前默認(rèn)為空函數(shù)。⑷處根據(jù)中斷號(hào)從中斷處理程序數(shù)組中獲取中斷處理程序,不為空就調(diào)用執(zhí)行。
- LITE_OS_SEC_TEXT VOID HalInterrupt(VOID)
 - {
 - UINT32 hwiIndex;
 - UINT32 intSave;
 - #if (LOSCFG_KERNEL_RUNSTOP == 1)
 - SCB->SCR &= (UINT32) ~((UINT32)SCB_SCR_SLEEPDEEP_Msk);
 - #endif
 - intSave = LOS_IntLock();
 - ⑴ g_intCount++;
 - LOS_IntRestore(intSave);
 - ⑵ hwiIndex = HalIntNumGet();
 - OsHookCall(LOS_HOOK_TYPE_ISR_ENTER, hwiIndex);
 - ⑶ HalPreInterruptHandler(hwiIndex);
 - #if (OS_HWI_WITH_ARG == 1)
 - if (g_hwiHandlerForm[hwiIndex].pfnHandler != 0) {
 - g_hwiHandlerForm[hwiIndex].pfnHandler((VOID *)g_hwiHandlerForm[hwiIndex].pParm);
 - }
 - #else
 - if (g_hwiHandlerForm[hwiIndex] != 0) {
 - ⑷ g_hwiHandlerForm[hwiIndex]();
 - }
 - #endif
 - ⑸ HalAftInterruptHandler(hwiIndex);
 - OsHookCall(LOS_HOOK_TYPE_ISR_EXIT, hwiIndex);
 - intSave = LOS_IntLock();
 - ⑹ g_intCount--;
 - LOS_IntRestore(intSave);
 - }
 
3、開關(guān)中斷
最后,分享下開、關(guān)中斷的相關(guān)知識(shí),開、關(guān)中斷分別指的是:
- 開中斷
 
執(zhí)行完畢特定的短暫的程序,打開中斷,可以響應(yīng)中斷。
- 關(guān)中斷
 
為了保護(hù)執(zhí)行的程序不被打斷,關(guān)閉相應(yīng)外部的中斷。
對應(yīng)的開、關(guān)中斷的函數(shù)定義在文件kernel\arch\include\los_context.h中,代碼如下。⑴處的UINT32 LOS_IntLock(VOID)會(huì)關(guān)閉中斷,暫停響應(yīng)中斷。⑵處的函數(shù)VOID LOS_IntRestore(UINT32 intSave)可以用來恢復(fù)UINT32 LOS_IntLock(VOID)函數(shù)關(guān)閉的中斷,UINT32 LOS_IntLock(VOID)的返回值作為VOID LOS_IntRestore(UINT32 intSave)的參數(shù)進(jìn)行恢復(fù)中斷。⑶處的函數(shù)UINT32 LOS_IntUnLock(VOID)會(huì)使能中斷,可以響應(yīng)中斷。
- UINTPTR HalIntLock(VOID);
 - define LOS_IntLock HalIntLock
 - VOID HalIntRestore(UINTPTR intSave);
 - define LOS_IntRestore HalIntRestore
 - UINTPTR HalIntUnLock(VOID);
 - define LOS_IntUnLock HalIntUnLock
 
可以看出,LOS_IntLock、LOS_IntRestore和LOS_IntUnLock是定義的宏,他們對應(yīng)定義在文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S中的匯編函數(shù),源碼如下。我們分析下這些匯編函數(shù)。寄存器PRIMASK是單一bit位的寄存器,置為1后,就關(guān)掉所有可屏蔽異常,只剩下NMI和硬故障HardFault異??梢皂憫?yīng)。默認(rèn)值是0,表示沒有關(guān)閉中斷。匯編指令CPSID I會(huì)設(shè)置PRIMASK=1,關(guān)閉中斷,指令CPSIE I設(shè)置PRIMASK=0,開啟中斷。
⑴處HalIntLock函數(shù)把寄存器PRIMASK數(shù)值寫入寄存器R0返回,并執(zhí)行CPSID I關(guān)閉中斷。⑵處HalIntUnLock函數(shù)把寄存器PRIMASK數(shù)值寫入寄存器R0返回,并執(zhí)行指令CPSIE I開啟中斷。兩個(gè)函數(shù)的返回結(jié)果可以傳遞給⑶處HalIntRestore函數(shù),把寄存器狀態(tài)數(shù)值寫入寄存器PRIMASK,用于恢復(fù)之前的中斷狀態(tài)。不管是HalIntLock還是HalIntUnLock,都可以和ArchIntRestore配對使用。
- .type HalIntLock, %function
 - .global HalIntLock
 - HalIntLock:
 - .fnstart
 - .cantunwind
 - ⑴ MRS R0, PRIMASK
 - CPSID I
 - BX LR
 - .fnend
 - .type HalIntUnLock, %function
 - .global HalIntUnLock
 - HalIntUnLock:
 - .fnstart
 - .cantunwind
 - ⑵ MRS R0, PRIMASK
 - CPSIE I
 - BX LR
 - .fnend
 - .type HalIntRestore, %function
 - .global HalIntRestore
 - HalIntRestore:
 - .fnstart
 - .cantunwind
 - ⑶ MSR PRIMASK, R0
 - BX LR
 - .fnend
 
小結(jié)
本文帶領(lǐng)大家一起剖析了鴻蒙輕內(nèi)核的中斷模塊的源代碼,掌握中斷相關(guān)的概念,中斷初始化操作,中斷創(chuàng)建、刪除,開關(guān)中斷操作等。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
















 
 
 





 
 
 
 