elasticsearch入門系列">elasticsearch入門系列
979
2022-05-29
本文我們來一起學習下LiteOS中斷模塊的源代碼,文中所涉及的源代碼,均可以在LiteOS開源站點https://gitee.com/LiteOS/LiteOS 獲取。中斷源代碼、開發文檔,示例程序代碼如下:
LiteOS內核中斷源代碼
包括中斷模塊的私有頭文件kernel\base\include\los_hwi_pri.h、頭文件kernel\include\los_hwi.h、C源代碼文件kernel\base\los_hwi.c。
中斷控制器實現代碼
開源LiteOS支持的中斷控制器有通用中斷控制器GIC(General Interrupt Controller)、嵌套向量中斷控制器NVIC(Nested Vectored Interrupt Controller),本文以STM32F769IDISCOVERY為例,分析一下適用于Cortex-M核的NVIC。各中斷控制器的源代碼包含頭文件、源文件,代碼路徑如下:https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/include/、https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/arm/interrupt/gic/、https://gitee.com/LiteOS/LiteOS/blob/master/targets/bsp/hw/arm/interrupt/nvic/。
開關中斷匯編實現代碼
開關中斷的函數UINT32 ArchIntLock(VOID)、UINT32 ArchIntUnlock(VOID)、ArchIntRestore(UINT32 intSave)是基于匯編語言實現的,根據不同的CPU架構,分布在下述文件里: arch\arm\cortex_a_r\include\arch\interrupt.h、arch\arm64\include\arch\interrupt.h、arch\arm\cortex_m\src\dispatch.S。
開發指南中斷文檔
在線文檔https://gitee.com/LiteOS/LiteOS/blob/master/doc/LiteOS_Kernel_Developer_Guide.md#%E4%B8%AD%E6%96%AD。
我們先看看中斷的相關概念,詳細的介紹,請參考LiteOS開發指南中斷文檔。
1、中斷概念介紹
中斷是指出現需要時,CPU暫停執行當前程序,轉而執行新程序的過程。當外設需要CPU時,將通過產生中斷信號使CPU立即中斷當前任務來響應中斷請求。在剖析中斷源代碼之前,下面介紹些中斷相關的硬件、中斷相關的概念。
1.1 中斷相關的硬件介紹
與中斷相關的硬件可以劃分為三類:設備、中斷控制器、CPU本身。
設備
發起中斷的源,當設備需要請求CPU時,產生一個中斷信號,該信號連接至中斷控制器。
中斷控制器
中斷控制器是CPU眾多外設中的一個,它一方面接收其它外設中斷引腳的輸入。另一方面,它會發出中斷信號給CPU。可以通過對中斷控制器編程來打開和關閉中斷源、設置中斷源的優先級和觸發方式。
CPU
CPU會響應中斷源的請求,中斷當前正在執行的任務,轉而執行中斷處理程序。
1.2 中斷相關的概念
中斷號
每個中斷請求信號都會有特定的標志,使得計算機能夠判斷是哪個設備提出的中斷請求,這個標志就是中斷號。
中斷優先級
為使系統能夠及時響應并處理所有中斷,系統根據中斷時間的重要性和緊迫程度,將中斷源分為若干個級別,稱作中斷優先級。
中斷處理程序
當外設產生中斷請求后,CPU暫停當前的任務,轉而響應中斷申請,即執行中斷處理程序。產生中斷的每個設備都有相應的中斷處理程序。
中斷向量
中斷服務程序的入口地址 。
中斷向量表
存儲中斷向量的存儲區,中斷向量與中斷號對應,中斷向量在中斷向量表中按照中斷號順序存儲。
中斷共享
當外設較少時,可以實現一個外設對應一個中斷號,但為了支持更多的硬件設備,可以讓多個設備共享一個中斷號,共享同一個中斷號的中斷處理程序形成一個鏈表。當外部設備產生中斷申請時,系統會遍歷中斷號對應的中斷處理程序鏈表,直到找到對應設備的中斷處理程序。在遍歷執行過程中,各中斷處理程序可以通過檢測設備ID,判斷是否是這個中斷處理程序對應的設備產生的中斷。
我們再看看LiteOS內核中斷源代碼。
2、LiteOS內核中斷源代碼
2.1 中斷相關的結構體
在文件kernel\base\include\los_hwi_pri.h中定義了2個結構體,HwiHandleInfo和HwiControllerOps。HwiHandleInfo結構體記錄中斷處理程序的相關信息,包括中斷處理程序、中斷共享模式或中斷處理程序參數、中斷處理程序執行的次數等。開啟共享中斷時,還包含指向下一個中斷處理程序結構體的鏈表指針,如⑴所示。中斷控制器操作項結構體HwiControllerOps包括中斷操作相關的函數,如觸發中斷、清除中斷、使能中斷、失能中斷、設置中斷優先級、獲取當前中斷號、獲取中斷版本、根據中斷號獲取中斷處理程序信息、處理調用中斷程序。對于SMP多核,還包括設置中斷CPU親和性,發送核間中斷等函數,如⑵所示。
⑴ typedef struct tagHwiHandleForm { HWI_PROC_FUNC hook; /* 中斷處理函數 */ union { HWI_ARG_T shareMode; /* 共享中斷時,頭節點使用此成員表示共享標志位 */ HWI_ARG_T registerInfo; /* 共享模式的設備節點,非共享模式時,表示中斷處理函數的參數*/ }; #ifndef LOSCFG_NO_SHARED_IRQ struct tagHwiHandleForm *next; #endif UINT32 respCount; /* 中斷程序執行次數 */ } HwiHandleInfo; ⑵ typedef struct { VOID (*triggerIrq)(HWI_HANDLE_T hwiNum); VOID (*clearIrq)(HWI_HANDLE_T hwiNum); VOID (*enableIrq)(HWI_HANDLE_T hwiNum); VOID (*disableIrq)(HWI_HANDLE_T hwiNum); UINT32 (*setIrqPriority)(HWI_HANDLE_T hwiNum, UINT8 priority); UINT32 (*getCurIrqNum)(VOID); CHAR *(*getIrqVersion)(VOID); HwiHandleInfo *(*getHandleForm)(HWI_HANDLE_T hwiNum); VOID (*handleIrq)(VOID); #ifdef LOSCFG_KERNEL_SMP VOID (*setIrqCpuAffinity)(HWI_HANDLE_T hwiNum, UINT32 cpuMask); VOID (*sendIpi)(UINT32 target, UINT32 ipi); #endif } HwiControllerOps;
kernel\include\los_hwi.h定義的結構體HWI_IRQ_PARAM_S,用于處理中斷處理程序的參數,包含中斷號、設備Id、中斷名稱等。在創建、刪除中斷時,需要提供這個參數。
typedef struct tagIrqParam { int swIrq; /**< 中斷號 */ VOID *pDevId; /**< 發起中斷的設備Id */ const CHAR *pName; /**< 中斷名稱 */ } HWI_IRQ_PARAM_S;
2.2 中斷初始化OsHwiInit()
在系統啟動時,在kernel\init\los_init.c中調用OsHwiInit()進行中斷初始化。這個函數定義在kernel\base\los_hwi.c,然后進一步調用定義在targets\bsp\hw\arm\interrupt\nvic\nvic.c文件中HalIrqInit()函數完成中斷向量初始化,并調用OsHwiControllerReg()注冊中斷控制器操作項。在下文分析NVIC時再分析該函數HalIrqInit()。
/* Initialization of the hardware interrupt */ LITE_OS_SEC_TEXT_INIT VOID OsHwiInit(VOID) { HalIrqInit(); return; }
2.3 創建中斷UINT32 LOS_HwiCreate()
開發者可以調用函數UINT32 LOS_HwiCreate()創建中斷,注冊中斷處理程序。我們先看看這個函數的參數,HWI_HANDLE_T hwiNum是硬件中斷號,HWI_PRIOR_T hwiPrio中斷的優先級,HWI_MODE_T hwiMode中斷模式,可以用來標記是否共享中斷。HWI_PROC_FUNC hwiHandler是需要注冊的中斷處理程序,中斷被觸發后會調用這個函數。HWI_IRQ_PARAM_S *irqParam是中斷處理程序的參數。
一起剖析下這個函數的源代碼,⑴處代碼獲取對應中斷號hwiNum的中斷處理程序信息,其中g_hwiOps是中斷初始化時注冊的中斷控制器操作項,后文分析NVIC時會分析該中斷控制器操作項。⑵、⑶處根據是否開啟了共享中斷LOSCFG_NO_SHARED_IRQ來分別配置創建中斷。⑷處在創建中斷成功,且支持配置中斷優先級時,調用g_hwiOps->setIrqPriority(hwiNum, hwiPrio)函數設置中斷的優先級。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiCreate(HWI_HANDLE_T hwiNum, HWI_PRIOR_T hwiPrio, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler, HWI_IRQ_PARAM_S *irqParam) { UINT32 ret; HwiHandleInfo *hwiForm = NULL; if (hwiHandler == NULL) { return OS_ERRNO_HWI_PROC_FUNC_NULL; } ⑴ hwiForm = g_hwiOps->getHandleForm(hwiNum); if (hwiForm == NULL) { return OS_ERRNO_HWI_NUM_INVALID; } LOS_TRACE(HWI_CREATE, hwiNum, hwiPrio, hwiMode, (UINTPTR)hwiHandler); #ifdef LOSCFG_NO_SHARED_IRQ ⑵ ret = OsHwiCreateNoShared(hwiForm, hwiMode, hwiHandler, irqParam); #else ⑶ ret = OsHwiCreateShared(hwiForm, hwiMode, hwiHandler, irqParam); LOS_TRACE(HWI_CREATE_SHARE, hwiNum, (UINTPTR)(irqParam != NULL ? irqParam->pDevId : NULL), ret); #endif if ((ret == LOS_OK) && (g_hwiOps->setIrqPriority != NULL)) { if (!HWI_PRI_VALID(hwiPrio)) { return OS_ERRNO_HWI_PRIO_INVALID; } ⑷ ret = g_hwiOps->setIrqPriority(hwiNum, hwiPrio); } return ret; }
2.3.1 不支持共享中斷時創建中斷UINT32 OsHwiCreateNoShared()
我們先看下沒有開啟共享中斷支持時,如何創建中斷。⑴處代碼表示如果創建的中斷的模式hwiMode等于共享模式IRQF_SHARED,則返回錯誤OS_ERRNO_HWI_SHARED_ERROR。⑵處代碼判斷中斷處理程序hwiForm->hook是否為空,為空時則把hwiHandler賦值賦值給它;hwiForm->hook不為空則執行⑹,說明中斷已經創建過,返回異常OS_ERRNO_HWI_ALREADY_CREATED;⑷處判斷irqParam是否為空,如果不為空,則為這個中斷處理程序的參數申請內存,設置數組,并賦值給hwiForm->registerInfo。⑸處表示如果為中斷處理程序的參數申請內存失敗,則返回異常。
STATIC UINT32 OsHwiCreateNoShared(HwiHandleInfo *hwiForm, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler, const HWI_IRQ_PARAM_S *irqParam) { UINT32 intSave; ⑴ if (hwiMode & IRQF_SHARED) { return OS_ERRNO_HWI_SHARED_ERROR; } HWI_LOCK(intSave); ⑵ if (hwiForm->hook == NULL) { ⑶ hwiForm->hook = hwiHandler; ⑷ if (irqParam != NULL) { hwiForm->registerInfo = OsHwiCpIrqParam(irqParam); ⑸ if (hwiForm->registerInfo == (HWI_ARG_T)NULL) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_NO_MEMORY; } } } else { HWI_UNLOCK(intSave); ⑹ return OS_ERRNO_HWI_ALREADY_CREATED; } HWI_UNLOCK(intSave); return LOS_OK; }
可以結合示意圖理解創建非共享中斷,對應的中斷處理信息表HwiHandleInfo *hwiForm結構體示意圖如下。創建中斷時,除了校驗,主要是設置中斷處理程序hwiForm->hook及其參數hwiForm->registerInfo。
2.3.2 支持共享時創建中斷UINT32 OsHwiCreateShared()
我們先看下開啟共享中斷支持時,如何創建創建中斷。支持共享中斷時,可以創建共享中斷,也可以創建非共享的中斷,如果⑴處的modeResult == 1表示創建的是共享中斷,否則是非共享中斷。沒有開啟共享中斷支持時,使用HwiHandleInfo *hwiForm單節點維護中斷處理程序信息,開啟共享中斷支持時,需要使用HwiHandleInfo *hwiForm單鏈表來維護中斷處理程序信息。
首先做些基礎的參數校驗,⑵處表示,如果創建的是共享中斷,但是參數irqParam或參數的設備Id為空,返回錯誤OS_ERRNO_HWI_SHARED_ERROR。也就是說,共享模式hwiMode和中斷處理程序的參數irqParam是必須的,因為需要指定特定的設備來共享同一個中斷號。⑶處判斷表示,如果head->next不為空,說明此中斷號已經有其他設備使用了,該中斷號需要被不同的設備共享,此時如果hwiMode或head->shareMode不是共享模式,則返回錯誤。⑷處while循環代碼處理此中斷號已經有其他設備使用的情況,依次循環中斷處理信息表的單鏈表,判斷是否已經存在同一個設備已經注冊的情況,如果存在則返回錯誤OS_ERRNO_HWI_ALREADY_CREATED。循環完畢之后,hwiForm為鏈表中的最后一個設備節點。如果創建的不是共享中斷,循環條件不滿足,此處代碼就不會執行。
做完參數校驗后,⑸處為一個HwiHandleInfo節點申請內存,申請的這個節點是中斷的設備節點,管理具體設備的中斷處理程序信息,參數列表中的節點HwiHandleInfo *head是中斷頭節點,只標記是不是共享中斷,可以參考下文的示意圖,來加深理解。如果申請失敗則返回錯誤OS_ERRNO_HWI_NO_MEMORY;申請成功繼續執行后面的語句,把hwiForm->respCount賦值為0,表示中斷處理程序還沒有執行過。⑹處代碼判斷參數irqParam,不為空時申請內存空間保存參數,否則參數賦值為0。⑺處把新增設備的中斷處理程序賦值給hwiFormNode->hook,然后把新增的節點掛載鏈表的尾部。⑻處表示更新頭節點的共享模式。
STATIC UINT32 OsHwiCreateShared(HwiHandleInfo *head, HWI_MODE_T hwiMode, HWI_PROC_FUNC hwiHandler, const HWI_IRQ_PARAM_S *irqParam) { UINT32 intSave; HwiHandleInfo *hwiFormNode = NULL; HWI_IRQ_PARAM_S *hwiParam = NULL; ⑴ HWI_MODE_T modeResult = hwiMode & IRQF_SHARED; HwiHandleInfo *hwiForm = NULL; ⑵ if (modeResult && ((irqParam == NULL) || (irqParam->pDevId == NULL))) { return OS_ERRNO_HWI_SHARED_ERROR; } HWI_LOCK(intSave); ⑶ if ((head->next != NULL) && ((modeResult == 0) || (!(head->shareMode & IRQF_SHARED)))) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_SHARED_ERROR; } hwiForm = head; ⑷ while (hwiForm->next != NULL) { hwiForm = hwiForm->next; hwiParam = (HWI_IRQ_PARAM_S *)(hwiForm->registerInfo); if (hwiParam->pDevId == irqParam->pDevId) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_ALREADY_CREATED; } } ⑸ hwiFormNode = (HwiHandleInfo *)LOS_MemAlloc(m_aucSysMem0, sizeof(HwiHandleInfo)); if (hwiFormNode == NULL) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_NO_MEMORY; } hwiForm->respCount = 0; ⑹ if (irqParam != NULL) { hwiFormNode->registerInfo = OsHwiCpIrqParam(irqParam); if (hwiFormNode->registerInfo == (HWI_ARG_T)NULL) { HWI_UNLOCK(intSave); (VOID) LOS_MemFree(m_aucSysMem0, hwiFormNode); return OS_ERRNO_HWI_NO_MEMORY; } } else { hwiFormNode->registerInfo = 0; } ⑺ hwiFormNode->hook = hwiHandler; hwiFormNode->next = (struct tagHwiHandleForm *)NULL; hwiForm->next = hwiFormNode; ⑻ head->shareMode = modeResult; HWI_UNLOCK(intSave); return LOS_OK; }
配置完畢中斷后,對應的中斷處理信息表HwiHandleInfo *hwiForm示意圖如下:
可以結合示意圖來理解在開啟共享中斷支持時,如何創建中斷。HwiHandleInfo *hwiForm結構體單鏈表示意圖如下。創建非共享中斷時,只需要兩個節點,左側頭結點中的head->shareMode等于0,表示非共享中斷,右側一個設備節點,表示指定設備的中斷處理程序,包含中斷處理程序hwiForm->hook及其參數hwiForm->registerInfo,hwiForm->next為空。創建共享中斷時,至少兩個節點,左側頭結點中的head->shareMode等于1,表示共享中斷,右側一到多個設備節點,表示不同設備的中斷處理程序,包含中斷處理程序hwiForm->hook及其參數hwiForm->registerInfo,hwiForm->next依次指向下一個設備的節點,最后一個節點的hwiForm->next為空。
2.4 刪除中斷UINT32 LOS_HwiDelete()
中斷刪除操作是創建操作的反向操作,也比較好理解。開發者可以調用函數UINT32 LOS_HwiDelete(HWI_HANDLE_T hwiNum, HWI_IRQ_PARAM_S *irqParam)刪除中斷。我們先看看這個函數的參數,HWI_HANDLE_T hwiNum是硬件中斷號,HWI_IRQ_PARAM_S *irqParam是中斷處理程序的參數,如果開啟了共享中斷需要該參數來刪除指定設備的中斷信息。
一起剖析下這個函數的源代碼,⑴處代碼獲取對應中斷號hwiNum的中斷處理程序信息,其中g_hwiOps是中斷初始化時注冊的中斷控制器操作項。⑵、⑶處根據是否開啟了共享中斷LOSCFG_NO_SHARED_IRQ來分別調用相應的函數刪除中斷。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiDelete(HWI_HANDLE_T hwiNum, HWI_IRQ_PARAM_S *irqParam) { UINT32 ret; ⑴ HwiHandleInfo *hwiForm = g_hwiOps->getHandleForm(hwiNum); if (hwiForm == NULL) { return OS_ERRNO_HWI_NUM_INVALID; } LOS_TRACE(HWI_DELETE, hwiNum); #ifdef LOSCFG_NO_SHARED_IRQ (VOID)irqParam; ⑵ ret = OsHwiDelNoShared(hwiForm); #else ⑶ ret = OsHwiDelShared(hwiForm, irqParam); LOS_TRACE(HWI_DELETE_SHARE, hwiNum, (UINTPTR)(irqParam != NULL ? irqParam->pDevId : NULL), ret); #endif return ret; }
沒有開啟共享中斷支持時,刪除中斷調用OsHwiDelNoShared(hwiForm)函數,中斷處理程序hwiForm->hook,釋放內存等,代碼簡單,讀者自行閱讀。我們主要來剖析下支持共享中斷時,是如何調用UINT32 OsHwiDelShared(HwiHandleInfo *head, const HWI_IRQ_PARAM_S *irqParam)刪除中斷的。
2.4.1 支持共享時刪除中斷UINT32 OsHwiDelShared()
我們來分析下開啟共享中斷支持時,刪除中斷的源代碼流程是什么樣的。⑴處表示,如果刪除的是共享中斷,但是參數irqParam或參數的設備Id為空,返回錯誤OS_ERRNO_HWI_SHARED_ERROR。⑵處表示刪除的是非共享中斷,如果wiForm->registerInfo不為空,則釋放參數占用的內存,然后釋放設備節點占用的內存,并把head->next置空。
⑶處代碼處理如何刪除共享中斷。⑷處while循環對中斷程序信息鏈表上的設備節點進行遍歷。⑸處表示如果沒有匹配到要刪除的設備,則繼續遍歷下一個設備節點。⑹處表示匹配到要刪除的設備中斷處理程序節點,則釋放參數的內存、釋放設備節點內存,并執行⑺,標記匹配到設備節點,然后跳出循環。⑻處,如果循環遍歷完畢,還沒有匹配到設備節點,則返回錯誤。⑼處表示如果刪除設備節點后,只有一個頭結點,則把共享模式改為非共享。
STATIC UINT32 OsHwiDelShared(HwiHandleInfo *head, const HWI_IRQ_PARAM_S *irqParam) { HwiHandleInfo *hwiFormtmp = NULL; HwiHandleInfo *hwiForm = NULL; UINT32 find = FALSE; UINT32 intSave; HWI_LOCK(intSave); ⑴ if ((head->shareMode & IRQF_SHARED) && ((irqParam == NULL) || (irqParam->pDevId == NULL))) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_SHARED_ERROR; } ⑵ if ((head->next != NULL) && !(head->shareMode & IRQF_SHARED)) { hwiForm = head->next; if (hwiForm->registerInfo) { (VOID) LOS_MemFree(m_aucSysMem0, (VOID *)hwiForm->registerInfo); } (VOID) LOS_MemFree(m_aucSysMem0, hwiForm); head->next = NULL; head->respCount = 0; HWI_UNLOCK(intSave); return LOS_OK; } ⑶ hwiFormtmp = head; hwiForm = head->next; ⑷ while (hwiForm != NULL) { ⑸ if (((HWI_IRQ_PARAM_S *)(hwiForm->registerInfo))->pDevId != irqParam->pDevId) { hwiFormtmp = hwiForm; hwiForm = hwiForm->next; } else { ⑹ hwiFormtmp->next = hwiForm->next; (VOID) LOS_MemFree(m_aucSysMem0, (VOID *)hwiForm->registerInfo); (VOID) LOS_MemFree(m_aucSysMem0, hwiForm); ⑺ find = TRUE; break; } } ⑻ if (!find) { HWI_UNLOCK(intSave); return OS_ERRNO_HWI_HWINUM_UNCREATE; } ⑼ if (head->next == NULL) { head->shareMode = 0; } HWI_UNLOCK(intSave); return LOS_OK; }
2.5 中斷控制器操作項函數
其他操作代碼結構非常類似,以UINT32 LOS_HwiEnable(HWI_HANDLE_T hwiNum)為例剖析一下,執行⑴處代碼,調用NVIC中定義的中斷控制器操作項g_hwiOps->enableIrq對應的函數HalIrqUnmask(),然后調用arch\arm\cortex_m\cmsis\core_cm7.h中的NVIC_EnableIRQ()函數,CMSIS中的代碼以后專門分析,此處不再深入剖析。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_HwiEnable(HWI_HANDLE_T hwiNum) { if (!HWI_NUM_VALID(hwiNum)) { return OS_ERRNO_HWI_NUM_INVALID; } OS_RETURN_ERR_FUNCPTR_IS_NULL(g_hwiOps->enableIrq, OS_ERRNO_HWI_PROC_FUNC_NULL); LOS_TRACE(HWI_ENABLE, hwiNum); ⑴ g_hwiOps->enableIrq(hwiNum); return LOS_OK; }
中斷控制器操作項中,其他的各個函數調用對應關系如下:
其中,第7行獲取中斷號的函數在arch\arm\cortex_m\cmsis\cmsis_armcc.h文件中定義的uint32_t __get_IPSR(void)。
第9-11行,只適用于SMP多核,在NVIC中不支持。
2.6 中斷的其他操作
我們再來看看其他常用的函數操作。
2.6.1 IntActive()判斷是否有處理中的中斷
其他模塊中經常使用OS_INT_ACTIVE或OS_INT_INACTIVE來判斷是否在處理中斷,下文源代碼⑴處的數組g_intCount[ArchCurrCpuid()]表示各個CPU核中正在處理的中斷的數目。返回值大于1,則表示正在處理中斷。
...... #define OS_INT_ACTIVE IntActive() #define OS_INT_INACTIVE (!(OS_INT_ACTIVE)) ...... size_t IntActive() { size_t intCount; UINT32 intSave = LOS_IntLock(); ⑴ intCount = g_intCount[ArchCurrCpuid()]; LOS_IntRestore(intSave); return intCount; }
2.6.2 中斷處理函數VOID OsIntHandle()
在LiteOS歸一化內核中,VOID OsIntEntry(VOID)中斷程序處理入口函數適用于arm(cortex-a/r)/arm64平臺,VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *handleForm)適用于arm(cortex-m), xtensa, riscv等平臺,我們主要來剖析下后者。
⑴處獲取全局變量指針&g_intCount[ArchCurrCpuid()],然后把它表示的中斷數量加1,在中斷執行完畢后,在⑸處再把活躍的中斷數量減1。
⑵、⑷處代碼在開啟中斷嵌套、中斷搶占LOSCFG_ARCH_INTERRUPT_PREEMPTION時,在執行中斷處理程序時,需要開、關中斷,具體的代碼就是調用
LOS_IntUnLock()、LOS_IntLock(),讀者可以訪問LiteOS開源站點自行查看。⑶處代碼調用InterruptHandle(hwiForm)來執行中斷處理程序,繼續往下看來剖析這個函數。
VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *hwiForm) { size_t *intCnt = NULL; #ifdef LOSCFG_CPUP_INCLUDE_IRQ OsCpupIrqStart(); #endif ⑴ intCnt = &g_intCount[ArchCurrCpuid()]; *intCnt = *intCnt + 1; #ifdef LOSCFG_DEBUG_SCHED_STATISTICS OsHwiStatistics(hwiNum); #endif #ifdef LOSCFG_KERNEL_LOWPOWER if (g_intWakeupHook != NULL) { g_intWakeupHook(hwiNum); } #endif LOS_TRACE(HWI_RESPONSE_IN, hwiNum); ⑵ OsIrqNestingActive(hwiNum); ⑶ InterruptHandle(hwiForm); ⑷ OsIrqNestingInactive(hwiNum); LOS_TRACE(HWI_RESPONSE_OUT, hwiNum); ⑸ *intCnt = *intCnt - 1; #ifdef LOSCFG_CPUP_INCLUDE_IRQ OsCpupIrqEnd(hwiNum); #endif }
我們來具體看下這個函數VOID InterruptHandle(HwiHandleInfo *hwiForm),⑴處把中斷處理程序的執行次數加1。⑵處代碼處理共享中斷的情況,會依次循環中斷號對應的中斷程序信息鏈表,一個中斷號對應的中斷發生時,所有注冊在這個中斷后下的中斷處理程序都會執行。
⑶處判斷中斷處理程序的參數是否為空,不為空時執行⑷處代碼,獲取中斷處理程序HWI_PROC_FUNC2 func,它需要2個參數,分別為中斷號,設備Id。然后執行⑸獲取中斷處理程序的參數,接著執行⑹處代碼調用中斷處理程序。如果中斷處理程序的參數為空,則獲取不需要參數的中斷處理程序HWI_PROC_FUNC0 func,然后執行。
STATIC INLINE VOID InterruptHandle(HwiHandleInfo *hwiForm) { ⑴ hwiForm->respCount++; #ifndef LOSCFG_NO_SHARED_IRQ ⑵ while (hwiForm->next != NULL) { hwiForm = hwiForm->next; #endif ⑶ if (hwiForm->registerInfo) { ⑷ HWI_PROC_FUNC2 func = (HWI_PROC_FUNC2)hwiForm->hook; if (func != NULL) { ⑸ UINTPTR *param = (UINTPTR *)(hwiForm->registerInfo); ⑹ func((INT32)(*param), (VOID *)(*(param + 1))); } } else { ⑺ HWI_PROC_FUNC0 func = (HWI_PROC_FUNC0)hwiForm->hook; if (func != NULL) { func(); } } #ifndef LOSCFG_NO_SHARED_IRQ } #endif }
我們下來看看LiteOS中封裝的NVIC代碼。
3、NVIC嵌套向量中斷控制器代碼
NVIC代碼包含一個頭文件targets\bsp\hw\include\nvic.h和C源代碼文件targets\bsp\hw\arm\interrupt\nvic\nvic.c。
3.1 nvic.h頭文件
NVIC頭文件nvic.h中,主要定義了一些宏,如中斷寄存器的地址、各個系統中斷號等,聲明了如下三個函數,其中函數IrqEntryV7M(VOID)在targets\bsp\hw\arm\interrupt\nvic\nvic.c文件中定義,用于處理中斷的程序,也叫做中斷向量;函數VOID Reset_Handler(VOID)在匯編啟動文件targets\STM32F769IDISCOVERY\los_startup_gcc.S中定義的,用于復位、啟動時的處理;函數VOID osPendSV(VOID)定義在arch\arm\cortex_m\src\dispatch.S,處理PendSV異常。
/* hardware interrupt entry */ extern VOID IrqEntryV7M(VOID); /* Reset handle entry */ extern VOID Reset_Handler(VOID); extern VOID osPendSV(VOID);
3.2 nvic.c源代碼文件
NVIC源代碼文件nvic.c中,定義中斷向量函數、中斷初始化函數。我們一起學習下源代碼。
⑴、⑵處代碼為系統支持的中斷定義了2個數組,對于每一個中斷號hwiNum,對應的數組元素g_hwiForm[hwiNum]表示每一個中斷對應的中斷處理程序的相關信息,g_hwiVec[hwiNum]表示中斷發生時需要執行的程序,該程序也叫中斷向量,這個數組有時候也叫做中斷向量表。⑵處代碼只定義了16個系統中斷號對應的中斷處理程序,其他在調用中斷初始化函數VOID HalIrqInit(VOID)時指定。其中1號中斷對應復位處理程序Reset_Handler,14號中斷對應osPendSV處理程序,15號中斷是tick中斷,還有些系統保留的中斷號。在LiteOS內核里對中斷處理進行接管,當中斷發生,執行的中斷處理函數在⑶處定義,這個函數會進一步調用用戶定義的中斷處理函數。
逐行分析下VOID IrqEntryV7M(VOID)函數,⑷處通過讀取ipsr寄存器獲取中斷號,__get_IPSR()定義在文件arch\arm\cortex_m\cmsis\cmsis_armcc.h。然后,根據中斷號從中斷處理程序信息表中獲取&g_hwiForm[hwiIndex],作為參數傳遞給函數VOID OsIntHandle(UINT32 hwiNum, HwiHandleInfo *hwiForm)進一步處理,該函數上文已經分析過。
⑴ LITE_OS_SEC_BSS HwiHandleInfo g_hwiForm[LOSCFG_PLATFORM_HWI_LIMIT]; ⑵ LITE_OS_SEC_DATA_VEC HWI_PROC_FUNC g_hwiVec[LOSCFG_PLATFORM_HWI_LIMIT] = { (HWI_PROC_FUNC)0, /* [0] Top of Stack */ (HWI_PROC_FUNC)Reset_Handler, /* [1] reset */ (HWI_PROC_FUNC)IrqEntryV7M, /* [2] NMI Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [3] Hard Fault Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [4] MPU Fault Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [5] Bus Fault Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [6] Usage Fault Handler */ (HWI_PROC_FUNC)0, /* [7] Reserved */ (HWI_PROC_FUNC)0, /* [8] Reserved */ (HWI_PROC_FUNC)0, /* [9] Reserved */ (HWI_PROC_FUNC)0, /* [10] Reserved */ (HWI_PROC_FUNC)IrqEntryV7M, /* [11] SVCall Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [12] Debug Monitor Handler */ (HWI_PROC_FUNC)0, /* [13] Reserved */ (HWI_PROC_FUNC)osPendSV, /* [14] PendSV Handler */ (HWI_PROC_FUNC)IrqEntryV7M, /* [15] SysTick Handler */ }; ⑶ LITE_OS_SEC_TEXT_MINOR VOID IrqEntryV7M(VOID) { UINT32 hwiIndex; ⑷ hwiIndex = __get_IPSR(); g_curIrqNum = hwiIndex; ⑸ OsIntHandle(hwiIndex, &g_hwiForm[hwiIndex]); if (OsTaskProcSignal() != 0) { OsSchedPreempt(); } }
繼續分析代碼,一起看下中斷初始化做了些什么,中斷控制器操作項有哪些。⑴處代碼定義的函數VOID HalIrqPending(UINT32 hwiNum)會請求觸發指定中斷號的中斷處理程序,先對中斷號進行校驗,確保中斷號合法,減去系統中斷數量OS_SYS_VECTOR_CNT,然后調用⑵處代碼執行定義在arch\arm\cortex_m\cmsis\core_cm7.h文件內的函數NVIC_SetPendingIRQ((IRQn_Type)hwiNum),請求觸發中斷。接著,這個函數VOID HalIrqPending(UINT32 hwiNum)在執行⑶處代碼時賦值給NVIC的中斷控制器操作項g_nvicOps的結構體成員.triggerIrq。除了觸發請求中斷,還有使能中斷、失能中斷、設置中斷優先級,獲取當前中斷號,獲取中斷版本,獲取中斷處理信息表等函數。這些HalXXXX函數,格式差不多,區別在調用不同的NVIC_XXX()函數,不再一一分析。
我們再看看中斷初始化函數,⑷處會把系統支持的系統中斷以后的中斷號對應的中斷處理程序都初始化為(HWI_PROC_FUNC)IrqEntryV7M這個中斷接管函數。如果是Cortex-M0核,執行⑸處代碼,使能時鐘,重新映射SRAM內存,對于其他核,執行⑹處代碼把中斷向量表賦值給SCB->VTOR。對于Cortex-M3及以上的CPU核,還需要執行⑺設置優先級組。最后,調用定義在kernel\base\los_hwi.c里的函數VOID OsHwiControllerReg(const HwiControllerOps *ops)注冊中斷控制器操作項,這樣LiteOS的中斷處理程序就可以調用NVIC里定義的中斷相關的操作。
...... ⑴ VOID HalIrqPending(UINT32 hwiNum) { UINT32 intSave; if ((hwiNum > OS_USER_HWI_MAX) || (hwiNum < OS_USER_HWI_MIN)) { return; } hwiNum -= OS_SYS_VECTOR_CNT; intSave = LOS_IntLock(); ⑵ NVIC_SetPendingIRQ((IRQn_Type)hwiNum); LOS_IntRestore(intSave); } ...... ⑶ STATIC const HwiControllerOps g_nvicOps = { .triggerIrq = HalIrqPending, .enableIrq = HalIrqUnmask, .disableIrq = HalIrqMask, .setIrqPriority = HalIrqSetPriority, .getCurIrqNum = HalCurIrqGet, .getIrqVersion = HalIrqVersion, .getHandleForm = HalIrqGetHandleForm, }; VOID HalIrqInit(VOID) { UINT32 i; ⑷ for (i = OS_SYS_VECTOR_CNT; i < LOSCFG_PLATFORM_HWI_LIMIT; i++) { g_hwiVec[i] = (HWI_PROC_FUNC)IrqEntryV7M; } ⑸ #if (__CORTEX_M == 0x0U) /* only for Cortex-M0*/ __HAL_RCC_SYSCFG_CLK_ENABLE(); __HAL_SYSCFG_REMAPMEMORY_SRAM(); #else ⑹ SCB->VTOR = (UINT32)g_hwiVec; #endif #if (__CORTEX_M >= 0x03U) /* only for Cortex-M3 and above */ ⑺ NVIC_SetPriorityGrouping(OS_NVIC_AIRCR_PRIGROUP); #endif /* register interrupt controller's operations */ ⑻ OsHwiControllerReg(&g_nvicOps); return; }
4、開關中斷
最后,分享下開關中斷的相關知識,開、關中斷分別指的是:
開中斷
執行完畢特定的短暫的程序,打開中斷,可以響應中斷了。
關中斷
為了保護執行的程序不被打斷,關閉相應外部的中斷。
對應的開關中斷的函數定義在kernel\include\los_hwi.h:
ArchIntLock(),ArchIntUnlock()這些基于匯編語言實現的,讀者們自行閱讀查看。我們看下開關斷函數的使用場景。⑴處的UINT32 LOS_IntLock(VOID)會關閉所有的中斷,與之對應,⑵處的函數UINT32 LOS_IntUnLock(VOID)會使能所有的中斷。⑶處的函數VOID LOS_IntRestore(UINT32 intSave)可以用來恢復UINT32 LOS_IntLock(VOID)函數關閉的中斷,UINT32 LOS_IntLock(VOID)的返回值作為VOID LOS_IntRestore(UINT32 intSave)的參數進行恢復中斷。
⑴ STATIC INLINE UINT32 LOS_IntLock(VOID) { return ArchIntLock(); } ⑵ STATIC INLINE UINT32 LOS_IntUnLock(VOID) { return ArchIntUnlock(); } ⑶ STATIC INLINE VOID LOS_IntRestore(UINT32 intSave) { ArchIntRestore(intSave); }
小結
本文帶領大家一起剖析了LiteOS中斷模塊的源代碼,結合講解,參考官方示例程序代碼,自己寫寫程序,實際編譯運行一下,加深理解。
感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/LiteOS/LiteOS/issues 。為了更容易找到LiteOS代碼倉,建議訪問 https://gitee.com/LiteOS/LiteOS ,關注Watch、Star、并Fork到自己賬戶下,如下圖,謝謝。
嵌入式 輕量級操作系統 LiteOS
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。