日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

详解基于 Cortex-M3 的任务调度(上)

發布時間:2025/3/15 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 详解基于 Cortex-M3 的任务调度(上) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 什么是任務
    • 任務及其內存結構
    • 上下文切換
    • CM3 的寄存器組
    • CM3 的 CONTROL 寄存器
    • 雙棧
    • CM3 的中斷
      • 入棧
      • 取向量
      • 更新寄存器
      • 異常返回
    • 切換的時機
    • 切換
    • 非基級線程模式(補充材料)
    • 代碼

什么是任務

對于嵌入式 RTOS,我覺得任務(task) 其實是線程。為什么這樣說呢?首先,有幾個知識點要明確:

  • 進程是資源分配的最小單位,線程是 CPU 調度的最小單位
  • 一個線程只能屬于一個進程,而一個進程可以有多個線程,但至少有一個線程。
  • 資源分配給進程,同一進程的所有線程共享該進程的所有資源。
  • 處理器分給線程,即真正在處理機上運行的是線程。
  • 其次,每個進程都有其自己的存儲空間,它們是互相隔離的。比如你在瀏覽網頁,瀏覽器崩潰了,但并不會影響到音樂播放器。但是對于線程來說,資源是共享的,所以對于共享資源的訪問就會存在競爭問題,于是就產生臨界區,互斥、信號量等概念。如果一個線程崩潰了,極大可能會影響到該進程中的其他線程。

    對于 MCU 上的資源,每個任務都是共享的,可以認為是單進程多線程模型。MCU一般沒有內存管理模塊,這樣無法很好地保證進程的安全,這也是當某個任務跑飛會導致整個程序崩潰的原因。

    通常認為,嵌入式系統在運行時只有一個進程,而把這個進程進行分解之后的那些程序模塊,由于沒有獨立的內存空間,實質上就是線程。在 μC/OS-II 中,把這樣的線程叫做任務。

    直觀上來講,

    任務及其內存結構

    既然是任務調度,那就需要系統把任務管理起來,系統管理任務的數據結構叫做任務控制塊(TCB)

    以 μC/OS-II 為例,任務控制塊記錄一個任務的各個屬性,相當于是任務的身份證。TCB 中有兩個指針特別重要:

    • 指向任務的指針:在任務初始化的時候,這個指針指向任務的代碼入口
    • 指向任務堆棧的指針:指向任務的棧。每個任務都有自己的棧,用來保存局部變量,還有寄存器的快照。在這些寄存器中,最重要的就是 PC,指向任務當前運行的代碼

    系統中一般會有多個任務,這些任務的 TCB 用鏈表串起來,也可以用數組。

    上下文切換

    要進行任務調度,就要進行上下文切換。

    先看看線程是如何對付中斷的。當線程在執行時,所做的事情就是從存儲器中取指令、譯碼、執行。在整個過程中,CPU 里寄存器的值會不斷更新。此時如果一個中斷來了,那么 CPU 就要把核心寄存器的值先保存到內存的某個地方(比如這個線程的棧),然后響應中斷。等中斷執行完了,再把剛才保存的值加載到對應的寄存器,從剛才中斷的地方繼續執行(由程序計數器 PC 記錄)。

    任務切換也是這個道理,如果要把當前任務 A 換出,就要先找到 A 任務的棧,把當前的寄存器信息保存到棧上,然后找出要換入的任務 B,再找到 B 任務的棧,把棧上保存的寄存器值恢復到寄存器里,最后讓 B 開始運行。

    前面做了很多鋪墊,接下來我們就結合具體的一款 CPU 來講任務調度。

    CM3 的寄存器組

    注意,在 CM3 處理器內核中共有兩個堆棧指針,于是也就支持兩個堆棧。當引用 R13(或寫作 SP)時,引用到的是當前正在使用的那一個,另一個必須用特殊的指令來訪問(MRS,MSR指令)。這兩個堆棧指針分別是:

    • 主堆棧指針(MSP), 或寫作 SP_main。這是缺省的堆棧指針,它由 OS 內核、異常服務例程以及所有需要特權訪問的應用程序代碼來使用。
    • 進程堆棧指針(PSP), 或寫作 SP_process。用于常規的應用程序代碼(不處于異常服用例程中時)。

    要注意的是,并不是每個程序都要用齊兩個堆棧指針才算圓滿。簡單的應用程序只使用 MSP 就夠了。

    在本文的示例代碼中,采用了雙棧。

    CM3 的 CONTROL 寄存器

    復位后,CONTROL[0]=0 ,也就是說線程模式處于特權級。

    Cortex-M3 處理器支持兩種處理器的操作模式,還支持兩級特權操作。
    兩種操作模式分別為:handler 模式和線程模式(thread mode)。引入兩個模式的本意,是用于區別普通應用程序的代碼和異常服務例程的代碼。

    兩級特權分別是:特權級和用戶級。這可以提供一種存儲器訪問的保護機制,使得普通的用戶程序代碼不能意外地、甚至是惡意地執行涉及到要害的操作。

    示例代碼中有一句:

    __set_CONTROL(0x3); // Switch to use Process Stack, unprivileged state

    意思是強行切換到用戶級,且用 PSP(后面馬上就說)

    雙棧

    我們已經知道了 CM3 的堆棧有兩個:主棧和進程棧,CONTROL[1] 決定如何選擇。

    當 CONTROL[1]=0 時,只使用 MSP,此時用戶程序和異常 handler 共享同一個棧,這也是復位后的缺省使用方式

    我們的示例代碼采用了雙棧。

    當 CONTROL[1]=1 時,線程模式將不再使用 MSP,而改用 PSP(注意:handler 模式永遠使用 MSP)。這樣做的好處在哪里?原來,在使用 OS 的環境下,我們想讓 OS 內核僅在 handler 模式下執行,用戶程序僅在用戶模式下執行,這種雙棧機制的好處是:萬一用戶棧崩潰了,并不會累及 OS 的棧。

    在雙棧模式下,進入異常時的自動壓棧使用的是進程棧,進入異常后會自動改為 MSP,退出異常時切換回 PSP,并且從進程棧上彈出數據。 如下圖所示:

    CM3 的中斷

    任務切換一般是在中斷中進行的,所以了解 CPU 的中斷過程非常必要。

    當 CM3 開始響應一個中斷時,會在它小小的體內奔涌起三股暗流:

    • 入棧: 把 8 個寄存器的值壓入棧
    • 取向量:從向量表中找出對應服務程序的入口地址
    • 更新寄存器:選擇堆棧指針 MSP/PSP,更新堆棧指針 SP,更新連接寄存器 LR,更新程序計數器 PC

    好,我們一個一個來說。

    入棧

    自動入棧的寄存器有 8 個,見表 9.1:

    取向量

    當數據總線(系統總線)正在為入棧操作而忙得風風火火時,指令總線(I-Code)可不是袖手旁觀——它正在為響應中斷緊張有序地執行另一項重要的任務:從向量表中找出正確的異常向量,然后在服務程序的入口處預取指。由此可以看到各自都有專用總線的好處:入棧與取指這兩個工作能同時進行。

    更新寄存器

    在入棧和取向量操作完成之后,執行服務例程之前,還要更新一系列的寄存器:

    • SP:在入棧后會把堆棧指針(PSP 或 MSP)更新到新的位置。在執行服務例程時,將由 MSP 負責對堆棧的訪問。
    • PSR:更新 IPSR 位段的值為新響應的異常編號。
    • PC:在取向量完成后,PC 將指向服務例程的入口地址。
    • LR:在出入 ISR 的時候,LR 的值將有新意義,這種特殊的值稱為“EXC_RETURN”,在異常進入時由系統計算并賦給 LR,并在異常返回時使用它。EXC_RETURN 的值除了最低 4 位外全為 1,而其最低4位則有另外的含義(見表9.3和表9.4)。

    以上是在響應異常時通用寄存器的變化。另一方面,在 NVIC 中,也會更新若干個相關寄存器。例如,新響應異常的懸起位將被清除,同時其活動位將被置位。

    異常返回

    當異常服務例程執行完畢后,需要很正式地做一個“異常返回”的動作序列,從而恢復先前的系統狀態,才能使被中斷的程序得以繼續執行。從形式上看,有 3 種途徑可以觸發異常返回序列,如表 9.2 所示。而不管使用哪一種,都需要用到先前儲到 LR 的 EXC_RETURN。

    在示例代碼中,使用的是第一個方法:

    BX LR // Return

    在啟動了中斷返回序列后,下述的處理就將進行:

  • 出棧:先前壓入棧中的寄存器值恢復到對應的寄存器,出棧順序與入棧時相對應,堆棧指針也改回先前的值。
  • 更新 NVIC 寄存器:伴隨著異常的返回,它的活動位也被硬件清除。對于外部中斷,倘若中斷輸入再次被置為有效,懸起位也將再次置位,新一次的中斷響應也可再次開始。
  • 切換的時機

    已經說了,任務切換在中斷中進行,但是在哪個中斷呢?

    例如,一個系統中有兩個任務,上下文切換被觸發的場合可以是:

    • 執行一個系統調用(SVC 異常)
    • 系統滴答定時器(SYSTICK)中斷,(輪轉調度中需要)

    讓我們舉個簡單的例子。假設有這么一個系統,里面有兩個就緒的任務,并且通過 SysTick 異常啟動上下文切換。如圖 7.15 所示。

    上圖是兩個任務輪轉調度的示意圖。但若在產生 SysTick 異常時正在響應一個中斷,則 SysTick 異常會搶占其 ISR。在這種情況下,OS 是不能執行上下文切換的,否則將使中斷請求被延遲,而且在真實系統中延遲時間還往往不可預知——任何有一丁點實時要求的系統都決不能容忍這種事。因此,在 CM3 中也是嚴禁沒商量——如果 OS 在某中斷活躍時嘗試切入線程模式,將觸發用法 fault 異常(但是有例外情況,感興趣的讀者可以看本文末尾的“非基級線程模式”)。

    為解決此問題,早期的 OS 大多會檢測當前是否有中斷在活躍中,只有在無任何中斷需要響應時,才執行上下文切換(切換期間無法響應中斷)。然而,這種方法的弊端在于,它可以把任務切換動作拖延很久(因為如果搶占了 IRQ,則本次 SysTick 在執行后不得作上下文切換,只能等待下一次 SysTick 異常),尤其是當某中斷源的頻率和 SysTick 異常的頻率比較接近時,會發生“共振”,使上下文切換遲遲不能進行。
    現在好了,有 PendSV 來完美解決這個問題。PendSV 異常會自動延遲上下文切換的請求。為實現這個機制,需要把 PendSV 編程為最低優先級的異常。若 OS 需要執行上下文切換,它將懸起一個 PendSV 異常,并在 PendSV 異常內執行上下文切換。如圖 7.17 所示

    解釋:

  • 任務 A 呼叫 SVC 來請求任務切換(例如,等待某些工作完成)
  • OS 接收到請求,做好上下文切換的準備,并且懸起一個 PendSV 異常。
  • 當 CPU 退出 SVC 后,它立即進入 PendSV,從而執行上下文切換。
  • 當 PendSV 執行完畢后,將返回到任務 B,同時進入線程模式。
  • 發生了一個中斷,并且中斷服務程序開始執行
  • 在 ISR 執行過程中,發生 SysTick 異常,并且搶占了該 ISR。
  • OS 執行必要的操作,然后懸起 PendSV 異常以作好上下文切換的準備。
  • 當 SysTick 退出后,回到先前被搶占的 ISR 中,ISR 繼續執行
  • ISR 執行完畢并退出后,PendSV 服務例程開始執行,并且在里面執行上下文切換
  • 當 PendSV 執行完畢后,回到任務 A,同時系統再次進入線程模式。
  • 遺留問題:在調試的時候,我認為 SysTick 異常返回后會立刻進入 PendSV 服務例程,應該看到 Tail chaining 現象,但測試結果是 SysTick 中斷處理后返回到了任務 B,執行了一點點,馬上進入 PendSV

    切換

    具體的切換如下圖所示。

    我給出的解釋:

  • Task A 正在執行的時候,發生了 PendSV 異常
  • xPSR, PC, LR, R12以及R3-R0由硬件自動入棧(注意:發生異常的時候,當前代碼使用哪個棧,就壓入哪個棧,圖中顯示使用的是 PSP。一旦進入服務例程,就會使用 MSP)
  • 手動保存 R4-R11 到 A 的棧
  • 更新 A 的棧指針到 PSP array[]
  • 從 PSP array[] 中找到 Task B 的棧指針
  • 根據 Task B 的棧指針,找到 Task B 的棧,手動出棧 R4-R11 的值到寄存器
  • 從 PendSV 異常返回,xPSR, PC, LR, R12以及R3-R0由硬件自動出棧
  • Task B 開始執行
  • 非基級線程模式(補充材料)

    在 CM3 中,原則上異常服務程序要在 handler 模式下執行,但是也允許在服務例程中切換到線程模式。通過設置 NVIC 配置與控制寄存器的“非基級線程模式允許”位(NONBASETHRDENA,位偏移:0),可以在服務例程中把處理器切換入線程模式。為什么要這么做?如果中斷服務例程是用戶程序的一部分,可能需要讓它在線程模式下執行,以限制它訪問特權級下的資源,此時可以讓此功能派上用場。

    如果使用此功能,則需要手工調整堆棧指針,還要重建堆棧中的數據。這種乾坤大挪移可是高度危險的作業,一不小心就很容易把整個系統弄垮。所以必須格外嚴肅地對待。另外,在使用時,系統設計者還必須保證服務例程能正確地返回。因為在線程模式下是不允許作中斷返回的,所以必須用一點手腕才行。如果放任不管,則中斷無法退出,這會永遠阻塞其它同級和更低優先級中斷。通常,由系統軟件負責完成這種工作。

    此節內容和本文主旨無關,所以僅放一個圖片在這里,提示讀者“居然可以如此操作”!

    代碼

    囿于篇幅,代碼下一篇博文再講。

    歡迎讀者批評指正。


    參考資料

    【0】RTOS中的任務是線程、進程、還是協程?-面包板社區

    【1】任務、進程和線程的區別(轉自博客園) - 雷明 - 博客園

    【2】《Cortex-M3 權威指南 》

    【3】《The De?nitive Guide to ARM Cortex-M3 and Cortex-M4 Processors(Third Edition)》

    總結

    以上是生活随笔為你收集整理的详解基于 Cortex-M3 的任务调度(上)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。