摘要:本文帶領你們一塊兒剖析了鴻蒙輕內核的中斷模塊的源代碼,掌握中斷相關的概念,中斷初始化操做,中斷建立、刪除,開關中斷操做等。git
本文分享自華爲雲社區《鴻蒙輕內核M核源碼分析系列五 中斷Hwi》,原文做者:zhushy。編程
本文,咱們講述一下中斷,會給讀者介紹中斷的概念,鴻蒙輕內核的中斷模塊的源代碼。本文中所涉及的源碼,以OpenHarmony LiteOS-M
內核爲例,都可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。數組
1、中斷概念介
中斷是指出現須要時,CPU暫停執行當前程序,轉而執行新程序的過程。當外設須要CPU
時,將經過產生中斷信號
使CPU
當即中斷當前任務來響應中斷請求
。在剖析中斷源代碼以前,下面介紹些中斷相關的硬件、中斷相關的概念。函數
1.1 中斷相關的硬件介紹
與中斷相關的硬件能夠劃分爲三類:設備、中斷控制器、CPU自己。源碼分析
- 設備
發起中斷的源,當設備須要請求CPU
時,產生一箇中斷信號,該信號鏈接至中斷控制器。學習
- 中斷控制器
中斷控制器是CPU
衆多外設中的一個,它一方面接收其它外設中斷引腳的輸入。另外一方面,它會發出中斷信號給CPU
。能夠經過對中斷控制器編程來打開和關閉中斷源、設置中斷源的優先級和觸發方式。url
- CPU
CPU
會響應中斷源的請求,中斷當前正在執行的任務,轉而執行中斷處理程序。spa
1.2 中斷相關的概念
- 中斷號
每一箇中斷請求信號都會有特定的標誌,使得計算機可以判斷是哪一個設備提出的中斷請求,這個標誌就是中斷號。.net
- 中斷優先級
爲使系統可以及時響應並處理全部中斷,系統根據中斷時間的重要性和緊迫程度,將中斷源分爲若干個級別,稱做中斷優先級。code
- 中斷處理程序
當外設產生中斷請求後,CPU
暫停當前的任務,轉而響應中斷申請,即執行中斷處理程序。產生中斷的每一個設備都有相應的中斷處理程序。
- 中斷向量
中斷服務程序的入口地址。
- 中斷向量表
存儲中斷向量的存儲區,中斷向量與中斷號對應,中斷向量在中斷向量表中按照中斷號順序存儲。
- 中斷共享
當外設較少時,能夠實現一個外設對應一箇中斷號,但爲了支持更多的硬件設備,可讓多個設備共享一箇中斷號,共享同一個中斷號的中斷處理程序造成一個鏈表。當外部設備產生中斷申請時,系統會遍歷中斷號對應的中斷處理程序鏈表,直到找到對應設備的中斷處理程序。在遍歷執行過程當中,各中斷處理程序能夠經過檢測設備ID
,判斷是不是這個中斷處理程序對應的設備產生的中斷。
接下來,咱們再看看鴻蒙輕內核中斷源代碼。
2、鴻蒙輕內核中斷源代碼
2.1 中斷相關的聲明和定義
在文件kernel\arch\arm\cortex-m7\gcc\los_interrupt.c
中定義了一些結構體、全局變量、內聯函數,在分析源碼以前,咱們先看下這些定義和聲明。所有變量g_intCount
表示正在處理的中斷數量,每次進入中斷處理程序時,都會把該變量數值加1,完成中斷處理退出時,該數值減1。對應的內聯函數HalIsIntActive()
用於獲取是否正在處理中斷,返回值大於0,則表示正在處理中斷。
UINT32 g_intCount = 0; inline UINT32 HalIsIntActive(VOID) { return (g_intCount > 0); }
咱們在再看看中斷向量表定義。⑴處代碼爲系統支持的中判定義了數組g_hwiForm[OS_VECTOR_CNT]
,對於每個中斷號hwiNum
,對應的數組元素g_hwiForm[hwiNum]
表示每個中斷對應的中斷處理執行入口程序。⑵處的宏OS_HWI_WITH_ARG
表示中斷處理程序是否支持參數傳入,默認關閉。若是支持傳參,定義⑶處的結構體HWI_HANDLER_FUNC
來維護中斷處理函數及其參數,還須要定義⑷處g_hwiHandlerForm
數組。若是不支持傳參,使用⑹處定義的g_hwiHandlerForm
數組。對於每個中斷號hwiNum
,對應的數組元素g_hwiHandlerForm[hwiNum]
表示每個中斷對應的中斷處理程序。⑸、⑺處定義個函數OsSetVector()
用於設置指定中斷號對應的中斷處理執行入口程序和中斷處理程序。中斷處理執行入口程序和中斷處理程序的關係是,當中斷髮生時,會執行中斷處理執行入口程序,這個函數會進一步調用中斷處理程序。
⑴ 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()
在系統啓動時,在kernel\src\los_init.c
中調用HalArchInit()
進行中斷初始化。這個函數定義在kernel\arch\arm\cortex-m7\gcc\los_context.c
,而後進一步調用定義在kernel\arch\arm\cortex-m7\gcc\los_interrupt.c
文件中HalHwiInit()
函數完成中斷向量初始化。咱們分析下代碼。
宏LOSCFG_USE_SYSTEM_DEFINED_INTERRUPT
表示是否使用系統預約義的向量基地址和中斷處理程序,默認開啓。⑴處開始,中斷向量表的0號中斷設置爲空,1號中斷對應復位處理程序Reset_Handler
。⑵處把其他的中斷設置爲默認的中斷處理執行入口程序HalHwiDefaultHandler()
。⑶處設置系統中斷(異常是中斷的一種,系統中斷也稱爲異常),系統中斷的執行入口函數定義在kernel\arch\arm\cortex-m7\gcc\los_exc.S
,使用匯編語言實現。系統中斷中,14號中斷對應HalPendSV
處理程序,用於任務上下文切換,15號中斷是tick
中斷。
執行⑷處代碼把中斷向量表賦值給SCB->VTOR
。對於Cortex-M3
及以上的CPU
核,還須要執行⑸設置優先級組。⑹處代碼使能指定的異常。
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 建立中斷UINT32 HalHwiCreate()
開發者能夠調用函數UINT32 HalHwiCreate()
建立中斷,註冊中斷處理程序。咱們先看看這個函數的參數,HWI_HANDLE_T hwiNum
是硬件中斷號,HWI_PRIOR_T hwiPrio
中斷的優先級,HWI_MODE_T mode
中斷模式,保留暫時沒有使用。HWI_PROC_FUNC handler
是須要註冊的中斷處理程序,中斷被觸發後會調用這個函數。HWI_ARG_T arg
是中斷處理程序的參數。
一塊兒剖析下這個函數的源代碼,⑴處代碼開始,對入參進行校驗,中斷處理程序不能爲空,中斷號不能大於支持的最大中斷號,中斷優先級不能超過指定優先級的大小。若是待建立的中斷號對應的中斷執行入口程序不等於HalHwiDefaultHandler
,說明已經建立過,返回錯誤碼。關中斷,而後執行⑵處的OsSetVector()
函數設置指定中斷號的中斷處理程序。⑶處調用CMSIS
函數使能中斷、設置中斷的優先級,打開中斷,完成中斷的建立。
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()
中斷刪除操做是建立操做的反向操做,也比較好理解。開發者能夠調用函數UINT32 HalHwiDelete(HWI_HANDLE_T hwiNum)
來刪除中斷。函數須要指定中斷號參數HWI_HANDLE_T hwiNum
。一塊兒剖析下這個函數的源代碼,⑴處代碼對入參進行校驗,不能大於支持的最大中斷號。⑵處調用CMSIS
函數來失能中斷,而後鎖中斷,執行⑶把中斷向量表指定中斷號的中斷執行入口程序設置爲默認程序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 中斷處理執行入口程序
咱們再來看看中斷處理執行入口程序。默認的函數HalHwiDefaultHandler()
以下,調用函數HalIntNumGet()
獲取中斷號,打印輸出,而後進行死循環。其中函數HalIntNumGet()
讀取寄存器ipsr
來獲取觸發的中斷的中斷號。
LITE_OS_SEC_TEXT_MINOR VOID HalHwiDefaultHandler(VOID) { UINT32 irqNum = HalIntNumGet(); PRINT_ERR("%s irqNum:%d\n", __FUNCTION__, irqNum); while (1) {} }
繼續來看中斷處理執行入口程序HalInterrupt()
,源碼以下。
⑴處把全局變量g_intCount
表示的正在處理的中斷數量加1,在中斷執行完畢後,在⑹處再把正在處理的中斷數量減1。⑵處調用函數HalIntNumGet()
獲取中斷號,⑶、⑸處調用的函數HalPreInterruptHandler()
,HalAftInterruptHandler()
在執行中斷處理程序前、後能夠處理些其餘操做,當前默認爲空函數。⑷處根據中斷號從中斷處理程序數組中獲取中斷處理程序,不爲空就調用執行。
3、開關中斷
最後,分享下開、關中斷的相關知識,開、關中斷分別指的是:
- 開中斷
執行完畢特定的短暫的程序,打開中斷,能夠響應中斷。
- 關中斷
爲了保護執行的程序不被打斷,關閉相應外部的中斷。
對應的開、關中斷的函數定義在文件kernel\arch\include\los_context.h
中,代碼以下。⑴處的UINT32 LOS_IntLock(VOID)
會關閉中斷,暫停響應中斷。⑵處的函數VOID LOS_IntRestore(UINT32 intSave)
能夠用來恢復UINT32 LOS_IntLock(VOID)
函數關閉的中斷,UINT32 LOS_IntLock(VOID)
的返回值做爲VOID LOS_IntRestore(UINT32 intSave)
的參數進行恢復中斷。⑶處的函數UINT32 LOS_IntUnLock(VOID)
會使能中斷,能夠響應中斷。
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
是定義的宏,他們對應定義在文件kernel\arch\arm\cortex-m7\gcc\los_dispatch.S
中的彙編函數,源碼以下。咱們分析下這些彙編函數。寄存器PRIMASK
是單一bit
位的寄存器,置爲1後,就關掉全部可屏蔽異常,只剩下NMI
和硬故障HardFault
異常能夠響應。默認值是0,表示沒有關閉中斷。彙編指令CPSID I
會設置PRIMASK=1
,關閉中斷,指令CPSIE I
設置PRIMASK=0
,開啓中斷。
⑴處HalIntLock
函數把寄存器PRIMASK
數值寫入寄存器R0
返回,並執行CPSID I
關閉中斷。⑵處HalIntUnLock
函數把寄存器PRIMASK
數值寫入寄存器R0
返回,並執行指令CPSIE I
開啓中斷。兩個函數的返回結果能夠傳遞給⑶處HalIntRestore
函數,把寄存器狀態數值寫入寄存器PRIMASK
,用於恢復以前的中斷狀態。不論是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
小結
本文帶領你們一塊兒剖析了鴻蒙輕內核的中斷模塊的源代碼,掌握中斷相關的概念,中斷初始化操做,中斷建立、刪除,開關中斷操做等。後續也會陸續推出更多的分享文章,敬請期待,也歡迎你們分享學習、使用鴻蒙輕內核的心得,有任何問題、建議,均可以留言給咱們: https://gitee.com/openharmony/kernel_liteos_m/issues 。爲了更容易找到鴻蒙輕內核代碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch
、點贊Star
、並Fork
到本身帳戶下,謝謝。