在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

鴻蒙內核源碼分析之線程環境下的任務切換

鴻蒙系統HarmonyOS ? 來源:my.oschina ? 作者: 鴻蒙內核源碼分析 ? 2021-04-25 15:22 ? 次閱讀

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

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

在線程環境下,從當前線程切換到目標線程,這種方式也稱為軟切換,能由軟件控制的自主式切換.哪些情況下會出現軟切換呢?

運行的線程申請某種資源(比如各種鎖,讀/寫消息隊列)失敗時,需要主動釋放CPU的控制權,將自己掛入等待隊列,調度算法重新調度新任務運行.

每隔10ms就執行一次的OsTickHandler節拍處理函數,檢測到任務的時間片用完了,就發起任務的重新調度,切換到新任務運行.

不管是內核態的任務還是用戶態的任務,于切換而言是統一處理,一視同仁的,因為切換是需要換棧運行,寄存器有限,需要頻繁的復用,這就需要將當前寄存器值先保存到任務自己的棧中,以便別人用完了輪到自己再用時恢復寄存器當時的值,確保老任務還能繼續跑下去. 而保存寄存器順序的結構體叫:任務上下文(TaskContext).

在中斷環境下,從當前線程切換到目標線程,這種方式也稱為硬切換.不由軟件控制的被動式切換.哪些情況下會出現硬切換呢?

由硬件產生的中斷,比如 鼠標,鍵盤外部設備每次點擊和敲打,屏幕的觸摸,USB的插拔等等這些都是硬中斷.同樣的需要切換棧運行,需要復用寄存器,但與軟切換不一樣的是,硬切換會切換工作模式(中斷模式).所以會更復雜點,但道理還是一樣要保存和恢復切換現場寄存器的值, 而保存寄存器順序的結構體叫:任務中斷上下文(TaskIrqContext).

本篇說清楚在線程環境下切換(軟切換)的實現過程.中斷切換(硬切換)實現過程將在鴻蒙內核源碼分析(總目錄)中斷切換篇中詳細說明.

本篇具體說清楚以下幾個問題:

任務上下文(TaskContext)怎么保存的?

代碼的實現細節是怎樣的?

如何保證切換不會發生錯誤,指令不會丟失?

在鴻蒙內核源碼分析(總目錄)系列篇中已經說清楚了調度機制,線程概念,寄存器,CPU,工作模式,這些是讀懂本篇的基礎,建議先前往翻看,不然理解本篇會費勁.本篇代碼量較多,涉及C和匯編代碼,代碼都添加了注釋,試圖把任務的整個切換過程逐行逐行說清楚.

前置條件

一個任務要跑起來,需要兩個必不可少的硬性條件:

1.從代碼段哪個位置取指令? 也就是入口地址,main函數是應用程序的入口地址, run()是new一個線程執行的入口地址.高級語言是這么叫,但到了匯編層的叫法就是PC寄存器.給PC寄存器喂妹值,指令就從哪里取.

2.運行的場地(棧空間)在哪里? ARM有7種工作模式,到了進程層面只需要考慮內核模式和用戶模式兩種,對應到任務會有內核態棧空間和用戶態棧空間.內核模式的任務只有內核態的棧空間,用戶模式任務二者都有.棧空間是在初始化一個任務時就分配指定好的.以下是兩種棧空間的初始化過程.為了精練省去了部分代碼,留下了核心部分.

//任務控制塊中對兩個棧空間的描述
typedef struct {
    VOID            *stackPointer;      /**< Task stack pointer */  //內核態棧指針,SP位置,切換任務時先保存上下文并指向TaskContext位置.
    UINT32          stackSize;          /**< Task stack size */     //內核態棧大小
    UINTPTR         topOfStack;         /**< Task stack top */      //內核態棧頂 bottom = top + size
    // ....
    UINTPTR         userArea;       //使用區域,由運行時劃定,根據運行態不同而不同
    UINTPTR         userMapBase;    //用戶態下的棧底位置
    UINT32          userMapSize;    /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */
} LosTaskCB;    
//內核態運行棧初始化
LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag)
{
    UINT32 index = 1;
    TaskContext *taskContext = NULL;
    taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));//上下文存放在棧的底部
    /* initialize the task context */ //初始化任務上下文
    taskContext->PC = (UINTPTR)OsTaskEntry;//程序計數器,CPU首次執行task時跑的第一條指令位置
    taskContext->LR = (UINTPTR)OsTaskExit;  /* LR should be kept, to distinguish it's THUMB or ARM instruction */
    taskContext->resved = 0x0;
    taskContext->R[0] = taskID;             /* R0 */
    taskContext->R[index++] = 0x01010101;   /* R1, 0x01010101 : reg initialed magic word */ //0x55
    for (; index < GEN_REGS_NUM; index++) {//R2 - R12的初始化很有意思
        taskContext->R[index] = taskContext->R[index - 1] + taskContext->R[1]; /* R2 - R12 */
    }
    taskContext->regPSR = PSR_MODE_SVC_ARM;   /* CPSR (Enable IRQ and FIQ interrupts, ARM-mode) */
    return (VOID *)taskContext;
}
//用戶態運行棧初始化
LITE_OS_SEC_TEXT_INIT VOID OsUserTaskStackInit(TaskContext *context, TSK_ENTRY_FUNC taskEntry, UINTPTR stack)
{
    context->regPSR = PSR_MODE_USR_ARM;//工作模式:用戶模式 + 工作狀態:arm
    context->R[0] = stack;//棧指針給r0寄存器
    context->SP = TRUNCATE(stack, LOSCFG_STACK_POINT_ALIGN_SIZE);//給SP寄存器值使用
    context->LR = 0;//保存子程序返回地址 例如 a call b ,在b中保存 a地址
    context->PC = (UINTPTR)taskEntry;//入口函數
}

您一定注意到了TaskContext,說的全是它,這就是任務上下文結構體,理解它是理解任務切換的鑰匙.它不僅在C語言層面出現,而且還在匯編層出現,TaskContext是連接或者說打通 C->匯編->C 實現任務切換的最關鍵概念.本篇全是圍繞著它來展開.先看看它張啥樣,LOOK!

TaskContext 任務上下文

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;          
    UINT32 R[GEN_REGS_NUM]; /* R0-R12 */
    UINT32 SP;              /* R13 */
    UINT32 LR;              /* R14 */
    UINT32 PC;              /* R15 */
} TaskContext;

結構很簡單,目的更簡單,就是用來保存寄存器現場的值的.鴻蒙內核源碼分析(總目錄)系列寄存器篇中已經說過了,到了匯編層就是寄存器在玩,當CPU工作在用戶和系統模式下時寄存器是復用的,玩的是17個寄存器和內存地址,訪問內存地址也是通過寄存器來玩.

哪17個? R0~R15和CPSR. 當調度(主動式)或者中斷(被動式)發生時.將這17個寄存器壓入任務的內核棧的過程叫保護案發現場.從任務棧中彈出依次填入寄存器的過程叫恢復案發現場.

從棧空間的具體哪個位置開始恢復呢? 答案是:stackPointer,任務控制塊(LosTaskCB)的首個變量.對應到匯編層的就是SP寄存器.

而TaskContext(任務上下文)就是一定的順序來保存和恢復這17個寄存器的.任務上下文在任務還沒有開始執行的時候就已經保存在內核棧中了,只不過是一些默認的值,OsTaskStackInit干的就是這個默認的事. 而OsUserTaskStackInit是對用戶棧的初始化,改變的是(CPSR)工作模式和SP寄存器.

新任務的運行棧指針(stackPointer)給SP寄存器意味著切換了運行棧,這是本篇最重要的一句話.

以下通過匯編代碼逐行分析如何保存和恢復TaskContext(任務上下文)

OsSchedResched 調度算法

//調度算法的實現
VOID OsSchedResched(VOID)
{
    // ...此處省去 ...
    /* do the task context switch */
    OsTaskSchedule(newTask, runTask);//切換任務上下文,注意OsTaskSchedule是一個匯編函數 見于 los_dispatch.s
}

在鴻蒙內核源碼分析(總目錄)之調度機制篇中,留了一個問題,OsTaskSchedule不是一個C函數,而是個匯編函數,就沒有往下分析了,本篇要完成整個分析過程.OsTaskSchedule實現了任務的上下文切換,匯編代碼見于los_dispatch.S中

OsTaskSchedule的參數指向的是新老兩個任務,這兩個參數分別保存在R0,R1寄存器中.

OsTaskSchedule 匯編實現

讀這段匯編代碼一定要對照上面的TaskContext,不然很難看懂,容易懵圈,但對照著看就秒懂.

/*
 * R0: new task 
 * R1: run task
 */
OsTaskSchedule:	/*任務調度,OsTaskSchedule的目的是將寄存器值按TaskContext的格式保存起來*/
    MRS      R2, CPSR 	/*MRS 指令用于將特殊寄存器(如 CPSR 和 SPSR)中的數據傳遞給通用寄存器,要讀取特殊寄存器的數據只能使用 MRS 指令*/
    STMFD    SP!, {LR}	/*返回地址入棧,LR = PC-4 ,對應TaskContext->PC(R15寄存器)*/
    STMFD    SP!, {LR}	/*再次入棧對應,對應TaskContext->LR(R14寄存器)*/
    /* jump sp */
    SUB      SP, SP, #4 /* 跳的目的是為了,對應TaskContext->SP(R13寄存器)*/
    /* push r0-r12*/
    STMFD    SP!, {R0-R12} 	 @對應TaskContext->R[GEN_REGS_NUM](R0~R12寄存器)。
    STMFD    SP!, {R2}		/*R2 入棧 對應TaskContext->regPSR*/
    /* 8 bytes stack align */
    SUB      SP, SP, #4		@棧對齊,對應TaskContext->resved
    /* save fpu registers */
    PUSH_FPU_REGS   R2	/*保存fpu寄存器*/
    /* store sp on running task */
    STR     SP, [R1] @在運行的任務棧中保存SP,即runTask->stackPointer = sp

OsTaskContextLoad: @加載上下文
    /* clear the flag of ldrex */ @LDREX 可從內存加載數據,如果物理地址有共享TLB屬性,則LDREX會將該物理地址標記為由當前處理器獨占訪問,并且會清除該處理器對其他任何物理地址的任何獨占訪問標記。
    CLREX @清除ldrex指令的標記
    /* switch to new task's sp */
    LDR     SP, [R0] @ 即:sp =  task->stackPointer
    /* restore fpu registers */
    POP_FPU_REGS    R2 @恢復fpu寄存器,這里用了匯編宏R2是宏的參數
    /* 8 bytes stack align */
    ADD     SP, SP, #4 @棧對齊
    LDMFD   SP!, {R0}  @此時SP!位置保存的是CPSR的內容,彈出到R0
    MOV     R4, R0	@R4=R0,將CPSR保存在r4, 將在OsKernelTaskLoad中保存到SPSR 
    AND     R0, R0, #CPSR_MASK_MODE @R0 =R0&CPSR_MASK_MODE ,目的是清除高16位
    CMP     R0, #CPSR_USER_MODE @R0 和 用戶模式比較
    BNE     OsKernelTaskLoad @非用戶模式則跳轉到OsKernelTaskLoad執行,跳出
    /*此處省去 LOSCFG_KERNEL_SMP 部分*/
    MVN     R3, #CPSR_INT_DISABLE @按位取反 R3 = 0x3F
    AND     R4, R4, R3		@使能中斷
    MSR     SPSR_cxsf, R4	@修改spsr值
    /* restore r0-r12, lr */
    LDMFD   SP!, {R0-R12}	@恢復寄存器值
    LDMFD   SP, {R13, R14}^	@恢復SP和LR的值,注意此時SP值已經變了,CPU換地方上班了.
    ADD     SP, SP, #(2 * 4)@sp = sp + 8
    LDMFD   SP!, {PC}^		@恢復PC寄存器值,如此一來 SP和PC都有了新值,完成了上下文切換.完美!
OsKernelTaskLoad: 			@內核任務的加載
    MSR     SPSR_cxsf, R4 	@將R4整個寫入到程序狀態保存寄存器
    /* restore r0-r12, lr */
    LDMFD   SP!, {R0-R12} 	@出棧,依次保存到 R0-R12,其實就是恢復現場
    ADD     SP, SP, #4 		@sp=SP+4
    LDMFD   SP!, {LR, PC}^ 	@返回地址賦給pc指針,直接跳出.

解讀

匯編分成了三段OsTaskSchedule,OsTaskContextLoad,OsKernelTaskLoad.

第一段OsTaskSchedule其實就是在保存現場.代碼都有注釋,對照著TaskContext來的,它就干了一件事把17個寄存器的值按TaskContext的格式入棧,因為鴻蒙用棧方式采用的是滿棧遞減的方式,所以存放順序是從最后一個往前依次入棧.

連著來兩句STMFD SP!, {LR}之前讓筆者懵圈了很久, 看了TaskContext才恍然大悟,因為三級流水線的原因,LR和PC寄存器之間是差了一條指令的,LR指向了處于譯碼階段指令,而PC指向了取指階段的指令,所以此處做了兩次LR入棧,其實是保存了未執行的譯碼指令地址,確保執行不會丟失一條指令.

R1是正在運行的任務棧,OsTaskSchedule總的理解是在任務R1的運行棧中插入一個TaskContext結構塊.而STR SP, [R1],是改變了LosTaskCB->stackPointer的值,這個值只能在匯編層進行精準的改變,而在整個鴻蒙內核C代碼層面都沒有看到對它有任何修改的地方.這個改變意義極為重要.因為新的任務被調度后的第一件事情就是恢復現場!!!

在OsTaskSchedule執行完成后,因為PC寄存器并沒有發生跳轉,所以緊接著往下執行OsTaskContextLoad

OsTaskContextLoad的任務就是恢復現場,誰的現場?當然是R0: new task的,所以第一條指令就是CLREX,清除干凈后立馬執行LDR SP, [R0],所指向的就是LosTaskCB->stackPointer,這個位置存的是新任務的TaskContext結構塊,是上一次R0任務被打斷時保存下來當時這17個寄存器的值啊,依次出棧就是恢復這17個寄存器的值.

OsTaskContextLoad在開始之前會判斷下工作模式,即判斷下是內核棧還是用戶棧,兩種處理方式稍有不同.但都是在恢復現場.

BNE OsKernelTaskLoad是查詢CPSR后判斷此時為內核棧的現場恢復過程,代碼很簡單就是恢復17個寄存器. 如此一來,任務執行的兩個條件,第一個SP的在LDR SP, [R0]時就有了.第二個條件:PC寄存器的值也在最后一條匯編LDMFD SP!, {LR, PC}^也已經有了.改變了PC和LR有了新值,下一條指令位置一樣是上次任務被中斷時還沒被執行的處于譯碼階段的指令地址.

如果是用戶態區別是需要恢復中斷.因為用戶模式的優先級是最低的,必須允許響應中斷,也是依次恢復各寄存器的值,最后一句LDMFD SP!, {PC}^結束本次旅行,下一條指令位置一樣是上次任務被中斷時還沒被執行的處于譯碼階段的指令地址.

如此,說清楚了任務上下文切換的整個過程,初看可能不太容易理解,建議多看幾篇,用筆畫下棧的運行過程,腦海中會很清晰的浮現出整個切換過程的運行圖.

編輯:hfy

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 線程
    +關注

    關注

    0

    文章

    505

    瀏覽量

    19690
  • 鴻蒙系統
    +關注

    關注

    183

    文章

    2634

    瀏覽量

    66362
收藏 人收藏

    評論

    相關推薦

    鴻蒙內核源碼Task/線程技術分析

    前言 在鴻蒙內核中,廣義上可理解為一個Task就是一個線程 一、怎么理解Task 1. 官方文檔是怎么描述線程 基本概念 從系統的角度看,線程
    的頭像 發表于 10-18 10:42 ?2219次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b>Task/<b class='flag-5'>線程</b>技術<b class='flag-5'>分析</b>

    鴻蒙內核源碼的中斷環境任務切換

    中斷環境任務切換鴻蒙內核線程就是
    的頭像 發表于 04-30 16:41 ?2308次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b>的中斷<b class='flag-5'>環境</b><b class='flag-5'>下</b>的<b class='flag-5'>任務</b><b class='flag-5'>切換</b>

    【HarmonyOS】鴻蒙內核源碼分析(調度機制篇)

    意義上所理解的線程呢。狹義上的后續有 鴻蒙內核源碼分析(啟動過程篇) 來說明。不知道大家有沒有這種體會,學一個東西的過程中要接觸很多新概念,
    發表于 10-14 14:00

    鴻蒙源碼分析系列(總目錄) | 給HarmonyOS源碼逐行加上中文注釋

    |-鴻蒙內核源碼分析(調度機制篇) | 任務是如何被調度執行的|-鴻蒙
    發表于 11-20 11:24

    鴻蒙內核源碼分析(調度機制篇):Task是如何被調度執行的

    (); 就是設置啟動任務,但此時啥都還沒開始呢,Kprocess 進程都沒創建,怎么會有大家一般意義上所理解的線程呢。狹義上的后續有 鴻蒙內核源碼
    發表于 11-23 10:53

    鴻蒙內核源碼分析(Task管理篇):task是內核調度的單元

    遷移示意圖注意官方文檔說的是線程,沒有提到task(任務),但內核源碼中卻有大量 task代碼,很少有線程(thread)代碼 ,這是怎么回
    發表于 11-23 14:01

    鴻蒙內核源碼分析(Task管理篇):task是內核調度的單元

    還原回去就能保證task的連續執行,讓用戶毫無感知。鴻蒙內核給一個任務執行的時間是 20ms ,也就是說有多任務競爭的情況,一秒鐘內最多要
    發表于 11-24 10:24

    線程管理線程切換

    線程管理線程切換前言基本信息前言說明PendSV_Handler函數前言基本信息名稱描述說明RT-Thread Studio 軟件版本版本: 1.1.3RT-Thread 系統版本
    發表于 08-24 08:19

    鴻蒙內核源碼線程環境任務切換

    在中斷環境,從當前線程切換到目標線程,這種方式也稱為硬切換.不由軟件控制的被動式
    的頭像 發表于 04-25 16:48 ?1468次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b><b class='flag-5'>之</b><b class='flag-5'>線程</b><b class='flag-5'>環境</b><b class='flag-5'>下</b>的<b class='flag-5'>任務</b><b class='flag-5'>切換</b>

    鴻蒙內核源碼分析任務環境的事件控制塊

    一對多同步模型:一個任務等待多個事件的觸發。可以是任意一個事件發生時喚醒任務處理事件,也可以是幾個事件都發生后才喚醒任務處理事件。
    的頭像 發表于 04-26 14:29 ?1353次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b><b class='flag-5'>分析</b>多<b class='flag-5'>任務</b><b class='flag-5'>環境</b><b class='flag-5'>下</b>的事件控制塊

    鴻蒙內核分析線程中斷環境任務切換

    ?OsSaveSignalContextIrqC函數為止. 中斷環境任務切換鴻蒙內核
    的頭像 發表于 03-19 14:34 ?2597次閱讀
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>分析</b>:<b class='flag-5'>線程</b>中斷<b class='flag-5'>環境</b><b class='flag-5'>下</b>的<b class='flag-5'>任務</b><b class='flag-5'>切換</b>

    鴻蒙內核源碼分析:task是內核調度的單元

    從系統的角度看,線程是競爭系統資源的最小運行單元。線程可以使用或等待CPU、使用內存空間等系統資源,并獨立于其它線程運行。 鴻蒙內核每個進
    發表于 11-23 15:51 ?22次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b><b class='flag-5'>分析</b>:task是<b class='flag-5'>內核</b>調度的單元

    鴻蒙內核源碼分析:進程和Task的就緒隊列對調度的作用

    鴻蒙內核代碼中有兩個源文件是關于隊列的,一個是用于調度的隊列,另一個是用于線程間通訊的IPC隊列。 鴻蒙內核進程和
    發表于 11-23 15:48 ?31次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b><b class='flag-5'>分析</b>:進程和Task的就緒隊列對調度的作用

    鴻蒙內核源碼分析內核最重要結構體

    為何鴻蒙內核源碼分析系列開篇就說 LOS_DL_LIST ? 因為它在鴻蒙 LOS 內核中無處
    發表于 11-24 17:54 ?35次下載
    <b class='flag-5'>鴻蒙</b><b class='flag-5'>內核</b><b class='flag-5'>源碼</b><b class='flag-5'>分析</b> :<b class='flag-5'>內核</b>最重要結構體

    華為鴻蒙系統內核源碼分析上冊

    鴻蒙內核源碼注釋中文版【 Gitee倉】給 Harmoηy○S源碼逐行加上中文注解,詳細闡述設計細節,助你快速精讀 Harmonyos內核源碼
    發表于 04-09 14:40 ?17次下載
    主站蜘蛛池模板: 手机看片久久| 国产老肥熟xxxx| 午夜黄色一级片| 制服丝袜国产精品| xxxx人妖| 国产成人精品一区二区三区| 欧美黑人换爱交换乱理伦片| 69pao强力打造免费高速| 久青草国产免费观看| 美日韩一区二区| 色多多在线| 日韩综合图区| 免费看一级毛片| 精品你懂的| videsgratis欧美另类| 久操福利视频| 丁香婷婷在线| 国产你懂的视频| 永久在线观看| 日日干夜夜操视频| 69国产成人精品午夜福中文| 亚洲一成人毛片| 亚洲一区在线免费观看| 久久国产精品无码网站| 婷婷国产| 亚洲成电影| 欧美婷婷| 免费人成网站| www.天天色| 狠狠干激情| 日本免费在线一区| 美女露出扒开尿口让男人桶| 亚洲人成一区| 亚洲欧美一区二区久久香蕉| 永久黄网站色视频免费观看| 色老头永久免费网站| 四虎国产精品成人永久免费影视| 免费在线成人| 夜夜夜久久久| 97天天摸天天碰天天爽| 国产精品性|