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

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

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

3天內不再提示

RTOS內功修煉記(一)— 任務到底應該怎么寫?

冬至子 ? 來源:Mculover666 ? 作者:Mculover666 ? 2023-12-01 16:36 ? 次閱讀

本篇文章講述了任務的三大元素:任務控制塊、任務棧、任務入口函數,并講述了編寫RTOS任務入口函數時三個重要的注意點。

1. 知識點回顧

在正式開始講解內容之前,我會先回顧一下基礎知識點,請確保你已經了解并掌握。

1.1. 任務的創建方法

在用戶層調用API創建一個任務,通常的流程如下:

① 創建一個數組作為任務棧:

#define TASK1_STACK_SIZE    512  
k_stack_t task1_stack[TASK1_STACK_SIZE];

② 創建一個任務控制塊:

k_task_t    task1;

③ 編寫任務入口函數:

void task1_entry(void *arg)  
{  
    while(1)  
    {  
        printf("task1 is runningrn");  
        tos_task_delay(1000);  
    }  
}

④ 調用系統API創建任務:

ret = tos_task_create(&task1,  
                      "task1",  
                      task1_entry,  
                      NULL,  
                      TASK1_PRO,  
                      task1_stack,  
                      TASK1_STACK_SIZE,  
                      10);

創建之后任務為就緒態(處于系統就緒隊列中),等待系統調度器調度執行。

1.2. STM32內存分布

閱讀之后,你應該要知道,STM32(Cortex-M3)中Flash和SRAM的內存空間如下:

image.png

其中Flash存儲空間中又分為文本段、只讀數據段、復制數據段:

image.png

其中SRAM存儲空間中又分為data數據段、bss數據段、堆空間、棧空間:

image.png

并且還要知道不同的變量類型,它對應的存儲位置在哪里,如果沒有,一定要閱讀上文之后再回來看,這是理解之后內容的基礎。

1.3. Cortex-M3/4系列內核

CrortexM3/4系列內核中的寄存器組都有16個寄存器,如圖所示,寄存器組通常都是CPU用于數據處理和運行控制的,希望你可以大概知道每個寄存器的作用:

image.png

① R0-R12:通用寄存器,用于數據操作;

② R13:棧頂指針,有兩個互斥的指針MSP和PSP,在任一時刻只能使用其中一個;

③ R14:連接寄存器,調用子程序時存放返回地址;

④ R15:程序計數器,PC指針指向哪里,CPU就執行哪里的代碼;

在RTOS內核中,這16個寄存器組的值稱之為 「上下文環境」 ,即當前任務運行時這16個寄存器中的值稱為上文環境,下一個任務運行時這16個寄存器的值稱為下文環境, 「上下文切換」 就是指將這16寄存器組的值修改為下一個任務的值。

1.4. 棧

棧是一種 「只能在一端插入或者刪除元素」 的數據結構,規則為: 「先入后出」 (FILO)。

image.png

C語言程序運行的時候,棧是非常非常非常重要的,在裸機程序中,棧頂指針由寄存器R13給出。

棧的作用,一方面是局部變量的存儲,局部變量的定義會被匯編為PUSH 指令,將局部變量中的內容壓入棧中,在函數執行完畢之后出棧,該局部變量被銷毀;另一方面是函數調用時的參數傳遞,也會被壓入棧中,在函數執行完畢后出棧。

2. 任務控制塊長啥樣

任務控制塊是一個任務的核心,廣義的講: 「內核所有對任務的操作,其實都是在操作任務控制塊」

任務控制塊類型k_task_t是一個結構體類型:

typedef struct k_task_st    k_task_t;

當定義了一個任務控制塊時,該結構體變量沒有初始值,所以 「存儲位置在STM32內部SRAM中的bss段內」

任務控制塊的結構體類型定義如下:

/**  
 * task control block  
 */  
struct k_task_st {  
    k_stack_t          *sp;                     /**< task stack pointer. This lady always comes first, we count on her in port_s.S for context switch. */  
  
    knl_obj_t           knl_obj;                /**< just for verification, test whether current object is really a task. */  
  
    char                name[K_TASK_NAME_MAX];  /**< task name */  
    k_task_entry_t      entry;                  /**< task entry */  
    void               *arg;                    /**< argument for task entry */  
    k_task_state_t      state;                  /**< just state */  
    k_prio_t            prio;                   /**< just priority */  
  
    k_stack_t          *stk_base;               /**< task stack base address */  
    size_t              stk_size;               /**< stack size of the task */  
  
  
  
    k_list_t            stat_list;              /**< list for hooking us to the k_stat_list */  
  
    k_tick_t            tick_expires;           /**< if we are in k_tick_list, how much time will we wait for? */  
  
    k_list_t            tick_list;              /**< list for hooking us to the k_tick_list */  
    k_list_t            pend_list;              /**< when we are ready, our pend_list is in readyqueue; when pend, in a certain pend object's list. */  
      
  
    pend_obj_t         *pending_obj;            /**< if we are pending, which pend object's list we are in? */  
    pend_state_t        pend_state;             /**< why we wakeup from a pend */  
};

此處引用的源碼 「不完整」 ,方便閱讀起見,所有使用宏開關配置的定義全部省略。

任務控制塊中的內容主要分為三部分:

① 任務棧棧頂指針sp:接下來會重點講解;

② 任務的全部信息:任務名稱、任務狀態、任務優先級、任務入口函數及參數、任務棧地址和大小;

③ 任務的鏈表:后續文章中重點講解。

3. 任務棧

3.1. 任務棧是什么

任務棧類型 k_stack_t 是一個 uint8_t 類型:

typedef uint8_t             k_stack_t;

當定義了一個任務棧數組時:

#define TASK1_STACK_SIZE    512  
k_stack_t task1_stack[TASK1_STACK_SIZE];

本質上還是一個uint8_t類型的全局變量數組,該全局變量數組沒有初始值,所以 「存儲位置仍在STM32內部SRAM中的bss段內」

在使用該數組的時候,只通過指針sp訪問,假裝它是一個棧,在使用上和棧的使用方式一模一樣,所以稱之為任務棧。

3.2. 任務棧中有什么(作用)

在創建任務的API中,有這樣一句代碼來初始化任務棧,并且返回任務棧的棧頂指針sp:

task- >sp = cpu_task_stk_init((void *)entry, arg, (void *)task_exit, stk_base, stk_size);

查看cpu_task_stk_init函數的定義,會發現 「不同的CPU結構,該函數的實現不同」

image.png

為什么不同的CPU結構,會導致任務棧的初始化代碼實現不同呢?

不急,讓我們先來看看如何來初始化任務棧, 「Cortex-M系列芯片的內核對應的都是ARM v7m架構」 ,選取此架構中的 cpu_task_stk_init 函數實現來探索問題的答案。

① 獲取任務棧棧頂指針的地址并對齊:

cpu_data_t *sp;  
  
sp = (cpu_data_t *)&stk_base[stk_size];  
sp = (cpu_data_t *)((cpu_addr_t)sp & 0xFFFFFFF8);

② PendSV異常發生時自動保存的寄存器:

/* auto-saved on exception(pendSV) by hardware */  
*--sp = (cpu_data_t)0x01000000u;    /* xPSR     */  
*--sp = (cpu_data_t)entry;          /* entry    */  
*--sp = (cpu_data_t)exit;           /* R14 (LR) */  
*--sp = (cpu_data_t)0x12121212u;    /* R12      */  
*--sp = (cpu_data_t)0x03030303u;    /* R3       */  
*--sp = (cpu_data_t)0x02020202u;    /* R2       */  
*--sp = (cpu_data_t)0x01010101u;    /* R1       */  
*--sp = (cpu_data_t)arg;            /* R0: arg  */

③ 手動保存/加載的寄存器:

*--sp = (cpu_data_t)0x11111111u;    /* R11      */  
*--sp = (cpu_data_t)0x10101010u;    /* R10      */  
*--sp = (cpu_data_t)0x09090909u;    /* R9       */  
*--sp = (cpu_data_t)0x08080808u;    /* R8       */  
*--sp = (cpu_data_t)0x07070707u;    /* R7       */  
*--sp = (cpu_data_t)0x06060606u;    /* R6       */  
*--sp = (cpu_data_t)0x05050505u;    /* R5       */  
*--sp = (cpu_data_t)0x04040404u;    /* R4       */

④ 返回當前棧頂指針:

return (k_stack_t *)sp;

初始化后任務棧中的內容如下:

image.png

任務切換的大致流程是觸發PendSV異常,在異常處理函數中使用匯編語言實現任務切換,也就是 「上下文切換」 ,在接下來的文章中會專門講述任務切換。

當該任務被調度執行時,CPU會自動將任務棧中最前面的8個寄存器值加載到CPU寄存器中,完成 「下文環境切換」 ,此時:

  • 棧頂指針寄存器R13中的值是該任務的任務棧的sp指針;
  • 程序計數器指針PC指向的是該任務的入口函數entry;

接下來CPU中的環境就是該任務的環境,該任務開始運行。

因為棧頂指針指向的是該任務的任務棧,所以此時若在任務的入口函數中傳遞參數,調用函數,創建局部變量, 「所有數據都被壓入到該任務的任務棧中」 ,與STM32內部的棧空間毫無關系。

同理,當任務執行完畢時(不一定是程序結束,而是調度器需要去調度執行別的任務了),因為棧具有 「后入先出」 的規則,CPU再將當前寄存器組的值壓入到棧中,完成 「上文環境保存」 ,下次再需要被加載時,這些寄存器組的值將首先出棧。

最后揭曉問題答案,因為 「不同的CPU架構,CPU寄存器組的數量、功能都不同」 ,所以需要針對每種CPU架構都要有一個實現。

4. 任務到底應該怎么寫

在學習RTOS的時候,我們的關注點都是“如何創建任務”,將重點放在了創建任務的API上,而忽略了一些最重要的問題。

重點①: 「任務入口函數,并不是一個普通的函數」

任務入口函數,通常它都偽裝成了一個普通函數,不像main函數那樣鶴立雞群,所以很多時候我們覺得它就是一個普通函數調用,實則不然。

「每一個任務的entry,首先應該是一個獨立的裸機程序。」

為什么這么說?因為多任務操作系統的機制是搶占式調度和時間片輪轉,無論再怎么牛逼,也無法改變CPU中只有一個CPU的事實,所以無論在任何一個時刻,系統中都只有唯一一個任務在運行。

重點②: 「每寫一行代碼,都要思考任務棧是否足夠」

在任務入口函數中創建的局部變量,函數調用,函數傳參,都使用的是該任務的任務棧,和STM32內部棧空間沒有任何關系,所以在編寫的時候一定要時刻思考自己指定的任務棧大小是否足夠,特別是在開辟局部變量數組的時候,調用一些庫的API的時候。

而在任務入口函數中,如果定義的是static變量,則不會存放到任務棧中,存放位置在STM32內部SRAM中的bss區域內。

除此之外,其余代碼都屬于可執行代碼,存放在Flash中Text區域中的Executable Code段,大可不必太在意。

重點③: 「盡量盡量要主動釋放CPU,切忌浪費CPU」

在裸機程序中,如果你動不動喜歡寫個死循環延時,尚可原諒,但是在RTOS系統中,如果一個任務在死循環做無用功,而導致其它任務得不到調度執行,將是不可饒恕的。

在編寫任務入口函數的時候,一定要遵循“不使用,就讓出”的原則,做一個高素質的任務,最普遍的做法是使用系統提供的delay函數來延時。

這樣做有非常多的優點,一方面是防止系統發生堵塞,導致其它任務得不到運行;另一方面是使系統中的空閑任務可以在空閑的時候回收系統內存資源,進入低功耗模式等騷操作。

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

    關注

    31

    文章

    5359

    瀏覽量

    120812
  • STM32
    +關注

    關注

    2270

    文章

    10915

    瀏覽量

    356774
  • RTOS
    +關注

    關注

    22

    文章

    817

    瀏覽量

    119769
  • Cortex-M3
    +關注

    關注

    9

    文章

    270

    瀏覽量

    59524
  • SRAM控制器
    +關注

    關注

    0

    文章

    11

    瀏覽量

    5909
收藏 人收藏

    評論

    相關推薦

    CTO到底應不應該代碼?聽聽硅谷大神們怎么說

    醫療社區丁香園的 CTO 馮大輝離職了,炸出了科技行業里的個大問題:CTO到底應不應該代碼?
    發表于 08-29 10:07 ?1574次閱讀

    電子工程師應該修煉的九大內功

    ,平均年齡在29歲,美國電子工程師的年齡平均在40歲左右。在這個朝氣蓬勃的年齡,電子工程師如何實現快速個人成長呢?本文作者結合網上的資源,整理了下就大內功修養路徑,和電子工程師們分享。
    發表于 11-03 10:31 ?3403次閱讀

    RTOS信號量、隊列通信原理

    有深入理解RTOS原理,或閱讀過RTOS源碼的同學應該知道:RTOS實現任務間通信通常是由系列
    發表于 08-16 10:07 ?1697次閱讀

    嵌入式軟件工程師的內功修煉

    嵌入式軟件的內功,21ic家也曾經多次強調。相信大家都看過武俠小說或電視,金老前輩的甚是出名,里面有“天下武功出少林”說,為什么呢?就是因為少林有本《易筋經》,“掃地僧”也就是當時江湖的大神,學了
    發表于 11-03 15:33

    RTOS中的多任務切換的相關資料分享

    淺談RTOS中的多任務切換(基于UC/OS iii)文章目錄淺談RTOS中的多任務切換(基于UC/OS iii). 簡介二.主要變量1.全
    發表于 12-06 07:08

    RTOS任務性能分析實現經驗分享

    1、如何利用公式評估RTOS任務的系統資源占用呢在實踐中,我們應該如何利用上述公式評估 RTOS
    發表于 04-15 18:16

    到底該不該用RTOS?看完你就有答案了

    到底該不該用RTOS,看完你就有答案了
    的頭像 發表于 02-25 16:17 ?3402次閱讀

    LEDs狀態燈任務(線程)設計 (基于RTOS

    LEDs狀態燈任務(線程)設計(基于RTOS
    的頭像 發表于 03-12 11:30 ?2381次閱讀

    如何讓RTOS任務訪問同個UART?

    RTOS任務編程的時候,同個硬件(比如UART、I2C等)被多個任務訪問的情況比較多,如果不合理處理,就會導致“混亂”的局面。 處理“混亂”局面的方法比較多,下面基于FreeRT
    的頭像 發表于 03-12 17:18 ?2112次閱讀

    淺析OS中的線程、進程和協程與RTOS任務屬于那種

    今天為大家講解講解OS中的線程、進程和協程的這幾個概念,同時起看看RTOS中的任務到底屬于哪種。
    的頭像 發表于 04-19 10:06 ?3239次閱讀
    淺析OS中的線程、進程和協程與<b class='flag-5'>RTOS</b><b class='flag-5'>任務</b>屬于那種

    RTOS任務的堆棧大小與代碼量有啥關系嗎?

    最近有小伙伴問了這樣個問題:我有個任務中的代碼量很多,是不是這個任務的堆棧需要分配很大才行? 下面就圍繞任務代碼量,以及堆棧進行描述相關內容。 1
    的頭像 發表于 05-26 09:34 ?2171次閱讀

    分析RTOS實現多任務調度的基本原理

    概念,相信大部分初學者都是懵的。 不太懂這些概念很正常,下子懂了,我倒反而覺得不正常。 1什么是多任務 這里大部分人應該都是從裸機階段過來的,裸機系統般也稱之為單
    的頭像 發表于 07-27 15:59 ?5464次閱讀
    分析<b class='flag-5'>RTOS</b>實現多<b class='flag-5'>任務</b>調度的基本原理

    電池制造內功修煉法打開產線智識之窗

    制造能力是制造業永恒修煉的“內功”。電池制造的要求正從ppm向ppb演進,對良率的追求近乎極限,數字化技術成為伸向極限的觸手,新技術的應用正助力電池制造成本、效率、體系邁向新階段,推動極限制造變革。
    的頭像 發表于 04-03 11:05 ?854次閱讀

    RTOS中的任務是線程?進程?還是協程?

    今天為大家講解講解OS中的線程、進程和協程的這幾個概念,同時起看看RTOS中的任務到底屬于哪種。
    的頭像 發表于 06-04 17:19 ?1738次閱讀
    <b class='flag-5'>RTOS</b>中的<b class='flag-5'>任務</b>是線程?進程?還是協程?

    使用任務通知提高RTOS應用的效率

    在實時嵌入式系統中,性能和資源效率是決定設計成敗的關鍵因素。傳統的實時操作系統(RTOS)提供了如隊列、信號量和事件組機制,實現任務之間的同步和通信。FreeRTOS/SAFERTOS還提供種方法可以使這些過程更快、更輕量化,
    的頭像 發表于 12-27 14:54 ?269次閱讀
    主站蜘蛛池模板: 欧美一级视频免费看| 日韩a免费| 久久久国产在线| 男男小说高h| 美女隐私黄www视频| 高清不卡免费一区二区三区| 91麻豆麻豆| 午夜精品福利在线| 天堂种子| 欧美一区二区三区在线观看免费| 欧美成人三级伦在线观看| 精品色视频| bt天堂在线观看| 国产日日操| h在线免费视频| 免费视频性| 中国成人在线视频| 天堂在线视频观看| 欧美综合久久| 美女网站在线观看视频18| 国产亚洲综合色就色| 51影院在线观看成人免费| 久久sese| jiuse视频| 手机福利在线观看| 天天爽夜夜爽天天做夜夜做| 欧美黄色免费看| 西西人体www303sw大胆高清| 亚洲欧美精品| 夜夜春色| aⅴ一区二区三区| 四虎在线视频观看| 你懂的亚洲| a毛片免费观看完整| 久久久久88色偷偷免费| 黄色网页在线播放| 女69女人poren25| 免费观看做网站爱| 四虎影院观看视频| 狠狠色丁香婷婷久久| 天天天操|