鴻蒙內核源碼分析(中斷切換篇) | 系統因中斷活力四射

關于中斷部分系列篇將用三篇詳細說明整個過程.

  • 中斷概念篇 中斷概念很多,比如中斷控制器,中斷源,中斷向量,中斷共享,中斷處理程序等等.本篇做一次整理.先了解透概念才好理解中斷過程.用海公公打比方說明白中斷各個概念.

  • 中斷管理篇 從中斷初始化HalIrqInit開始,到注冊中斷的LOS_HwiCreate函數,到消費中斷函數的 HalIrqHandler,剖析鴻蒙內核實現中斷的過程,很像設計模式中的觀察者模式.

  • 中斷切換篇(本篇) 用自下而上的方式,從中斷源頭純匯編代碼往上跟蹤代碼細節.說清楚保存和恢復中斷現場TaskIrqContext過程.

中斷環境下的任務切換

在鴻蒙的內核線程就是任務,系列篇中說的任務和線程當一個東西去理解.

一般二種場景下需要切換任務上下文:

  • 在中斷環境下,從當前線程切換到目標線程,這種方式也稱為硬切換.它們通常由硬件產生或是軟件發生異常時的被動式切換.哪些情況下會出現硬切換呢?

    • 中斷源可分外部和內部中斷源兩大類,比如 鼠標,鍵盤外部設備每次點擊和敲打,屏幕的觸摸,USB的插拔等等這些都是外部中斷源.存儲器越限、缺頁,核間中斷,斷點中斷等等屬于內部中斷源.由此產生的硬切換都需要換棧運行,硬切換硬在需切換工作模式(中斷模式).所以會比線程環境下的切換更復雜點,但道理還是一樣要保存和恢復切換現場寄存器的值, 而保存寄存器順序格式結構體叫:任務中斷上下文(TaskIrqContext).
  • 在線程環境下,從當前線程切換到目標線程,這種方式也稱為軟切換,能由軟件控制的自主式切換.哪些情況下會出現軟切換呢?

    • 運行的線程申請某種資源(比如各種鎖,讀/寫消息隊列)失敗時,需要主動釋放CPU的控制權,將自己掛入等待隊列,調度算法重新調度新任務運行.
    • 每隔10ms就執行一次的OsTickHandler節拍處理函數,檢測到任務的時間片用完了,就發起任務的重新調度,切換到新任務運行.
    • 不管是內核態的任務還是用戶態的任務,于切換而言是統一處理,一視同仁的,因為切換是需要換棧運行,寄存器有限,需要頻繁的復用,這就需要將當前寄存器值先保存到任務自己的棧中,以便別人用完了輪到自己再用時恢復寄存器當時的值,確保老任務還能繼續跑下去. 而保存寄存器順序格式結構體叫:任務上下文(TaskContext).

本篇說清楚在中斷環境下切換(硬切換)的實現過程.

ARM的七種工作模式中,有兩個是和中斷相關.

  • 普通中斷模式(irq):一般中斷模式也叫普通中斷模式,用于處理一般的中斷請求,通常在硬件產生中斷信號之后自動進入該模式,該模式可以自由訪問系統硬件資源。
  • 快速中斷模式(fiq):快速中斷模式是相對一般中斷模式而言的,用來處理高優先級中斷的模式,處理對時間要求比較緊急的中斷請求,主要用于高速數據傳輸及通道處理中。

此處分析普通中斷模式下的任務切換過程.

普通中斷模式相關寄存器

這張圖一定要刻在腦海里,系列篇會多次拿出來,目的是為了能牢記它.

  • 普通中斷模式(圖中IRQ列)是一種異常模式,有自己獨立運行的棧空間.一個(IRQ)中斷發生后,硬件會將CPSR寄存器工作模式置為IRQ模式.并跳轉到入口地址OsIrqHandler執行.
#define OS_EXC_IRQ_STACK_SIZE    64 //中斷模式棧大小 64個字節
__irq_stack:
    .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM
__irq_stack_top:
  • OsIrqHandler匯編代碼實現過程,就干了三件事:
    • 1.保存任務中斷上下文TaskIrqContext
    • 2.執行中斷處理程序HalIrqHandler,這是個C函數,由匯編調用
    • 3.恢復任務中斷上下文TaskIrqContext,返回被中斷的任務繼續執行

TaskIrqContext 和 TaskContext

先看本篇結構體 TaskIrqContext

#define TASK_IRQ_CONTEXT \
        unsigned int R0;     \
        unsigned int R1;     \
        unsigned int R2;     \
        unsigned int R3;     \
        unsigned int R12;    \
        unsigned int USP;    \
        unsigned int ULR;    \
        unsigned int CPSR;   \
        unsigned int PC;

typedef struct {//任務中斷上下文
#if !defined(LOSCFG_ARCH_FPU_DISABLE)
    UINT64 D[FP_REGS_NUM]; /* D0-D31 */
    UINT32 regFPSCR;       /* FPSCR */
    UINT32 regFPEXC;       /* FPEXC */
#endif
    UINT32 resved;
    TASK_IRQ_CONTEXT
} TaskIrqContext;

typedef struct {//任務上下文,已在任務切換篇中詳細說明,放在此處是為了對比  
#if !defined(LOSCFG_ARCH_FPU_DISABLE)
    UINT64 D[FP_REGS_NUM]; /* D0-D31 */
    UINT32 regFPSCR;       /* FPSCR */
    UINT32 regFPEXC;       /* FPEXC */
#endif
    UINT32 resved;          /* It's stack 8 aligned */
    UINT32 regPSR;          //保存CPSR寄存器
    UINT32 R[GEN_REGS_NUM]; /* R0-R12 */
    UINT32 SP;              /* R13 */
    UINT32 LR;              /* R14 */
    UINT32 PC;              /* R15 */
} TaskContext;
  • 兩個結構體很簡單,目的更簡單,就是用來保存寄存器現場的值的. TaskContext把17個寄存器全部保存了,TaskIrqContext保存的少些,在棧中并沒有保存R4-R11寄存器的值,這說明在整個中斷處理過程中,都不會用到R4-R11寄存器.不會用到就不會改變,當然就沒必要保存了.這也說明內核開發者的嚴謹程度,不造成時間和空間上的一丁點浪費.效率的提升是從細節處入手的,每個小地方優化那么一丟丟,整體性能就上來了.
  • TaskIrqContext中有兩個變量有點奇怪 unsigned int USP; unsigned int ULR; 指的是用戶模式下的SP和LR值, 這個要怎么理解? 因為對一個正運行的任務而言,中斷的到來是顆不定時炸彈,無法預知,也無法提前準備,中斷一來它立即被打斷,壓根沒有時間去保存現場到自己的棧中,那保存工作只能是放在IRQ棧或者SVC棧中.而IRQ棧非常的小,只有64個字節,16個棧空間,指望不上了,就保存在SVC棧中,SVC模式棧可是有 8K空間的.
  • 從接下來的 OsIrqHandler代碼中可以看出,鴻蒙內核整個中斷的工作其實都是在SVC模式下完成的,而irq的棧只是個過渡棧.具體看匯編代碼逐行注解分析.

普通中斷處理程序

OsIrqHandler:   @硬中斷處理,此時已切換到硬中斷棧
    SUB     LR, LR, #4  @記錄譯碼指令地址,以防切換過程丟失指令

    /* push r0-r3 to irq stack */ @irq棧只是個過渡棧
    STMFD   SP, {R0-R3}     @r0-r3寄存器入 irq 棧
    SUB     R0, SP, #(4 * 4)@r0 = sp - 16,目的是記錄{R0-R3}4個寄存器保存的開始位置,屆時從R3開始出棧
    MRS     R1, SPSR        @獲取程序狀態控制寄存器
    MOV     R2, LR          @r2=lr

    /* disable irq, switch to svc mode */@超級用戶模式(SVC 模式),主要用于 SWI(軟件中斷)和 OS(操作系統)。
    CPSID   i, #0x13                @切換到SVC模式,此處一切換,后續指令將在SVC棧運行
                                    @CPSID i為關中斷指令,對應的是CPSIE
    @TaskIrqContext 開始保存中斷現場 ......                         
    /* push spsr and pc in svc stack */
    STMFD   SP!, {R1, R2} @實際是將 SPSR,和PC入棧對應TaskIrqContext.PC,TaskIrqContext.CPSR,
    STMFD   SP, {LR}      @LR再入棧,SP不自增,如果是用戶模式,LR值將被 282行:STMFD   SP, {R13, R14}^覆蓋  
                          @如果非用戶模式,將被 286行:SUB     SP, SP, #(2 * 4) 跳過.
    AND     R3, R1, #CPSR_MASK_MODE @獲取CPU的運行模式
    CMP     R3, #CPSR_USER_MODE     @中斷是否發生在用戶模式
    BNE     OsIrqFromKernel         @非用戶模式不用將USP,ULR保存在TaskIrqContext

    /* push user sp, lr in svc stack */
    STMFD   SP, {R13, R14}^         @將用戶模式的sp和LR入svc棧

OsIrqFromKernel:    @從內核發起中斷
    /* from svc not need save sp and lr */@svc模式下發生的中斷不需要保存sp和lr寄存器值
    SUB     SP, SP, #(2 * 4)    @目的是為了留白給 TaskIrqContext.USP,TaskIrqContext.ULR
                                @TaskIrqContext.ULR已經在 276行保存了,276行用的是SP而不是SP!,所以此處要跳2個空間
    /* pop r0-r3 from irq stack*/
    LDMFD   R0, {R0-R3}         @從R0位置依次出棧 

    /* push caller saved regs as trashed regs in svc stack */
    STMFD   SP!, {R0-R3, R12}   @寄存器入棧,對應 TaskIrqContext.R0~R3,R12

    /* 8 bytes stack align */
    SUB     SP, SP, #4          @棧對齊 對應TaskIrqContext.resved

    /*
     * save fpu regs in case in case those been
     * altered in interrupt handlers.
     */
    PUSH_FPU_REGS   R0 @保存fpu regs,以防中斷處理程序中的fpu regs被修改。
    @TaskIrqContext 結束保存中斷現場......  
    @開始執行真正的中斷處理函數了.
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨立的IRQ棧
    PUSH    {R4}    @R4先入棧保存,接下來要切換棧,需保存現場
    MOV     R4, SP  @R4=SP
    EXC_SP_SET __svc_stack_top, OS_EXC_SVC_STACK_SIZE, R1, R2 @切換到svc棧
#endif
    /*BLX 帶鏈接和狀態切換的跳轉*/
    BLX     HalIrqHandler /* 調用硬中斷處理程序,無參 ,說明HalIrqHandler在svc棧中執行 */

#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨立的IRQ棧
    MOV     SP, R4  @恢復現場,sp = R4 
    POP     {R4}    @彈出R4
#endif

    /* process pending signals */   @處理掛起信號
    BL      OsTaskProcSignal        @跳轉至C代碼 

    /* check if needs to schedule */@檢查是否需要調度
    CMP     R0, #0  @是否需要調度,R0為參數保存值
    BLNE    OsSchedPreempt @不相等,即R0非0,一般是 1

    MOV     R0,SP   @參數
    MOV     R1,R7   @參數
    BL      OsSaveSignalContextIrq @跳轉至C代碼 

    /* restore fpu regs */
    POP_FPU_REGS    R0 @恢復fpu寄存器值

    ADD     SP, SP, #4 @sp = sp + 4 

OsIrqContextRestore:    @恢復硬中斷環境
    LDR     R0, [SP, #(4 * 7)]  @R0 = sp + 7,目的是跳到恢復中斷現場TaskIrqContext.CPSR位置,剛好是TaskIrqContext倒數第7的位置.
    MSR     SPSR_cxsf, R0       @恢復spsr 即:spsr = TaskIrqContext.CPSR
    AND     R0, R0, #CPSR_MASK_MODE @掩碼找出當前工作模式
    CMP     R0, #CPSR_USER_MODE @是否為用戶模式?
    @TaskIrqContext 開始恢復中斷現場 ...... 
    LDMFD   SP!, {R0-R3, R12}   @從SP位置依次出棧 對應 TaskIrqContext.R0~R3,R12
                                @此時已經恢復了5個寄存器,接來下是TaskIrqContext.USP,TaskIrqContext.ULR
    BNE     OsIrqContextRestoreToKernel @看非用戶模式,怎么恢復中斷現場.

    /* load user sp and lr, and jump cpsr */
    LDMFD   SP, {R13, R14}^ @出棧,恢復用戶模式sp和lr值 即:TaskIrqContext.USP,TaskIrqContext.ULR
    ADD     SP, SP, #(3 * 4) @跳3個位置,跳過 CPSR ,因為上一句不是 SP!,所以跳3個位置,剛好到了保存TaskIrqContext.PC的位置

    /* return to user mode */
    LDMFD   SP!, {PC}^ @回到用戶模式,整個中斷過程完成
    @TaskIrqContext 結束恢復中斷現場(用戶模式下) ......  

OsIrqContextRestoreToKernel:@從內核恢復中斷
    /* svc mode not load sp */
    ADD     SP, SP, #4 @其實是跳過TaskIrqContext.USP,因為在內核模式下并沒有保存這個值,翻看 287行
    LDMFD   SP!, {LR} @彈出LR
    /* jump cpsr and return to svc mode */
    ADD     SP, SP, #4 @跳過cpsr
    LDMFD   SP!, {PC}^ @回到svc模式,整個中斷過程完成
    @TaskIrqContext 結束恢復中斷現場(內核模式下) ......

逐句解讀

  • 跳轉到 OsIrqFromKernel硬件會自動切換到__irq_stack執行
  • 1句:SUB LR, LR, #4 在arm執行過程中一般分為取指,譯碼,執行階段,而PC是指向取指,正在執行的指令為 PC-8 ,譯碼指令為PC-4.當中斷發生時硬件自動執行 mov lr pc, 中間的PC-4譯碼指令因為沒有寄存器去記錄它,就會被丟失掉.所以SUB LR, LR, #4 的結果是lr = PC -4 ,定位到了被中斷時譯碼指令,將在棧中保存這個位置,確保回來后能繼續執行.
  • 2句:STMFD SP, {R0-R3} 當前4個寄存器入__irq_stack保存
  • 3句:SUB R0, SP, #(4 * 4) 因為SP沒有自增,R0跳到保存R0內容地址
  • 4,5句:讀取SPSR,LR寄存器內容,目的是為了后面在SVC棧中保存TaskIrqContext
  • 6句:CPSID i, #0x13禁止中斷和切換SVC模式,執行完這條指令后工作模式將切到 SVC模式
  • @TaskIrqContext 開始保存中斷現場 ......
  • 中間代碼需配合TaskIrqContext來看,不然100%懵逼.結合看就秒懂,代碼都已經注釋,不再做解釋,注解中提到的 翻看276行 是指源碼的第276行,請對照注解源碼看理解會更透徹.
  • @TaskIrqContext 結束保存中斷現場 ......
  • TaskIrqContext保存完現場后就真正的開始處理中斷了.
    /*BLX 帶鏈接和狀態切換的跳轉*/
    BLX     HalIrqHandler /* 調用硬中斷處理程序,無參 ,說明HalIrqHandler在svc棧中執行 */
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了獨立的IRQ棧
    MOV     SP, R4  @恢復現場,sp = R4 
    POP     {R4}    @彈出R4
#endif
    /* process pending signals */   @處理掛起信號
    BL      OsTaskProcSignal        @跳轉至C代碼 
    /* check if needs to schedule */@檢查是否需要調度
    CMP     R0, #0  @是否需要調度,R0為參數保存值
    BLNE    OsSchedPreempt @不相等,即R0非0,一般是 1
    MOV     R0,SP   @參數
    MOV     R1,R7   @參數
    BL      OsSaveSignalContextIrq @跳轉至C代碼 
    /* restore fpu regs */
    POP_FPU_REGS    R0 @恢復fpu寄存器值
    ADD     SP, SP, #4 @sp = sp + 4 
  • 這段代碼都是跳轉到C語言去執行,分別是 HalIrqHandler OsTaskProcSignal OsSchedPreempt OsSaveSignalContextIrq C語言部分內容很多,將在中斷管理篇中說明.

  • @TaskIrqContext 開始恢復中斷現場 ......

  • 同樣的中間代碼需配合TaskIrqContext來看,不然100%懵逼.結合看就秒懂,代碼都已經注釋,不再做解釋,注解中提到的 翻看287行 是指源碼的第287行,請對照注解源碼看理解會更透徹.進入源碼注解地址查看

  • @TaskIrqContext 結束恢復中斷現場 ......

寫在最后

  • 如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
  • 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。
  • 關注小編,同時可以期待后續文章ing??,不定期分享原創知識。
  • 想要獲取更多完整鴻蒙最新學習知識點,請移步前往小編:https://gitee.com/MNxiaona/733GH/blob/master/jianshu
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容