精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

3天內不再提示

stm32裸機RT—thread開始創建線程詳解

嵌入式應用開發 ? 來源:嵌入式應用開發 ? 作者:嵌入式應用開發 ? 2022-05-19 15:02 ? 次閱讀

在裸機系統 中,他們統統放在一個叫棧的地方,棧是單片機RAM里面一段連續的內存空間,棧的大小一般在啟動文件或者鏈接腳 本里面指定,最后由C庫函數_main進行初始化。

在多線程系統中,每個線程都是獨立的,互不干擾的,所以要為每個線程都分配獨立的棧空間,這個棧空間通常是一個預先定義好的全局數組,也可以是動態分配的一段內存空間,但它們都存在于RAM中。

創建線程棧,需要幾個線程創建幾個線程棧,這里線程棧實際上就是全局變量,大小為512,這里創建兩個工作線程,如下:

ALIGN(RT_ALIGN_SIZE)//
/* 定義線程棧*/
rt_uint8_t rt_flag1_thread_stack[512];
rt_uint8_t rt_flag2_thread_stack[512];

ALIGN是一個 帶參宏,在rtdef.h中定義,設置變量需要多少個字節對齊,對在它下面的變量起作用。RT_ALIGN_SIZE是一個在rtconfig.h(rtconfig.h第一次使用需要在User文件夾下面新建然后添加到工程user這個組文件)中定義 的宏,默認為4,表示4個字節對齊。

#ifndef __RTTHREAD_CFG_H__
#define __RTTHREAD_CFG_H__

#define RT_THREAD_PRIORITY_MAX  32     /* 最大優先級 */
#define RT_ALIGN_SIZE           4      /* 多少個字節對齊 */

#endif /* __RTTHREAD_CFG_H__ */

下一步,定義線程函數:

這里我們建立線程函數,并給函數命名flag1_thread_entry,flag2_thread_entry兩個函數 ,分別代表著兩個無限循環不返回的線程

/* 軟件延時 */
void delay (uint32_t count)
{
    for(; count!====0; count--);
}
/* 線程1 */
void flag1_thread_entry( void *p_arg )//   (1)
{
    for( ;; )
    {
        flag1 ==== 1;
        delay( 100 );
        flag1 ==== 0;
        delay( 100 );

        /* 線程切換,這里是手動切換 */
        rt_schedule();
    }
}
/* 線程2 */
void flag2_thread_entry( void *p_arg )//   (2)
{
    for( ;; )
    {
        flag2 ==== 1;
        delay( 100 );
        flag2 ==== 0;
        delay( 100 );

        /* 線程切換,這里是手動切換 */
        rt_schedule();
    }
}

下一步定義線程控制塊

在裸機系統中,程序的主體是CPU按照順序執行的。而在多線程系統中,線程的執行是由系統調度的。系統為了順利的調度線程,為每個線程都額外定義了一個線程控制塊,這個線程控制塊就相當于線程的身份證,里面存有線程的所有信息,比如線程的棧指針,線程名稱,線程的形參等。有了這個線程控制塊之后,以后系統對線程的全部操作都可以通過這個線程控制塊來實現。定義一個線程控制塊需要一個新的數據類型,該數據類型在rtdef.h這個頭 文件中聲明,使用它可以為每個線程都定義一個線程控制塊實體。下面來看一下,此結構體定義函數及內容:

struct rt_thread
{
    void        *sp;                    /* 線程棧指針 */
    void        *entry;              /* 線程入口地址 */
    void        *parameter;       /* 線程形參 */
    void        *stack_addr;      /* 線程起始地址 */
    rt_uint32_t stack_size;       /* 線程棧大小,單位為字節 */

    rt_list_t   tlist;            /* 線程鏈表節點 */
};
typedef struct rt_thread *rt_thread_t;// 

我們在main.c文件中為兩個線程定義的線程控制塊。

/* 定義線程控制塊 */
struct rt_thread rt_flag1_thread;
struct rt_thread rt_flag2_thread;

下一步,創建線程實現函數

線程的棧,線程的函數實體,線程的控制塊最終需要聯系起來才能由系統進行統一調度。那么這個聯系的工作就由線 程初始化函數rt_thread_init()來實現,該函數在thread.c(thread.c第一次使用需要自行在文件夾rtthread/3.0.3/src中新建并添加到工程的rtt/source組)中定義,在rtthread.h中聲明,所有跟線程相關的函數都在這個文件定義。rt_thread_init()函數的實現如下 :

rt_err_t rt_thread_init(struct rt_thread *thread,//          (1)
                        void (*entry)(void *parameter),//    (2)
                        void             *parameter,//       (3)
                        void             *stack_start,//     (4)
                        rt_uint32_t       stack_size)//      (5)
{
    rt_list_init(&(thread->tlist));//                         (6)

    thread->entry ==== (void *)entry;//                       (7)
    thread->parameter ==== parameter;//                       (8)

    thread->stack_addr ==== stack_start;//                    (9)
    thread->stack_size ==== stack_size;//                     (10)

    /* 初始化線程棧,并返回線程棧指針 */ //                      (11)
    thread->sp ==== (void *)rt_hw_stack_init( thread->entry,
                                        thread->parameter,
                                    (void *)((char *)thread->stack_addr + thread->stack_size - 4) );

    return RT_EOK;//                                          (12)
}

(1) :thread是線程控制塊指針。

(2) :entry 是線程函數名, 表示線程的入口。

(3) :parameter是線程形參,用于傳遞線程參數。

(4) :stack_start 用于指向線程棧的起始地址。

(5) :stack_size表示線程棧的大小,單位為字節。

(7) :將線程入口保存到線程控制塊的entry成員中。

(8) :將線程入口形參保存到線程控制塊的parameter成員中。

(9) :將線程棧起始地址保存到線程控制塊的stack_start成員中。

(10) :將線程棧起大小保存到線程控制塊的stack_size成員中。

(11) :初始化線程棧,并返回線程棧頂指針。

rt_hw_stack_init()用來初始化線程棧, 當線程第一次運行的時候,加載到CPU寄存器參數就放在線程棧里面,該函數在cpuport.c中實現。cpuport.c第一次使用需要自行 在rtthread/3.0.3/ libcpu/arm/cortex-m3(cortex-m4或cortex-m7)文件夾下新建,然后添加到工程 的rtt/ports組中。

下一步,定義鏈表節點數據類型:

struct rt_list_node
{
   struct rt_list_node *next;              /* 指向后一個節點 */
   struct rt_list_node *prev;              /* 指向前一個節點 */
};
typedef struct rt_list_node rt_list_t;

rt_list_t 類型的節點里面有兩個rt_list_t類型的節點指針next和prev,分別用來指向鏈表中的下一個節點和上一個節點。

pYYBAGKF8peAQwaOAAB3seF5MG4606.png雙向鏈表輪詢示意圖

雙向鏈表的相關操作,這些函數均在rtservice.h中實現,rtservice.h第一次使用需要自行在rtthread/3.0.3/include文件夾下新建,然后添加到工程的rtt/source組中。下面從鏈表操作逐步實現。

鏈表節點初始化:

rt_inline void rt_list_init(rt_list_t *l)
{
    l->next ==== l->prev ==== l;
}

實際上進行了如下操作:

poYBAGKF9FaAZa-8AAA6i6E1JnY592.png鏈表初始化示意圖

下面在鏈表頭部和尾部分別插入一個鏈表節點:

表頭后面插入一個節點

/* 在雙向鏈表頭部插入一個節點*/
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev ==== n; /* 第 1 步*/
    n->next ==== l->next; /* 第 2 步*/
    l->next ==== n; /* 第 3 步*/
    n->prev ==== l; /* 第 4 步*/
}

表頭前邊插入一個節點

rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next ==== n; /* 第 1 步*/
    n->prev ==== l->prev; /* 第 2 步*/
    l->prev ==== n; /* 第 3 步*/
    n->next ==== l; /* 第 4 步*/
}

從雙向鏈表刪除一個節點

rt_inline void rt_list_remove(rt_list_t *n)
{
    n->next->prev ==== n->prev; /* 第 1 步*/
    n->prev->next ==== n->next; /* 第 2 步*/
    n->next ==== n->prev ==== n; /* 第 3 步*/
}

創建線程初始化函數

/* 線程棧初始化 */
rt_uint8_t *rt_hw_stack_init(void       *tentry,//                  (1)
                            void       *parameter,//                 (2)
                            rt_uint8_t *stack_addr)// 線程棧頂地址-4,在該函數調用的時候傳進來的是線程棧的棧頂地址-4
{

    struct stack_frame *stack_frame;//                               (4)
    rt_uint8_t         *stk;
    unsigned long       i;

    /* 獲取棧頂指針
    rt_hw_stack_init 在調用的時候,傳給stack_addr的是(棧頂指針)*/
    stk  ==== stack_addr + sizeof(rt_uint32_t);//                       (5)

    /* 讓stk指針向下8字節對齊 */
    stk  ==== (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);//       (6)

    /* stk指針繼續向下移動sizeof(struct stack_frame)個偏移 */
    stk -==== sizeof(struct stack_frame);//                             (7)

    /* 將stk指針強制轉化為stack_frame類型后存到stack_frame */
    stack_frame ==== (struct stack_frame *)stk;//                       (8)

    /* 以stack_frame為起始地址,將棧空間里面的sizeof(struct stack_frame)
    個內存初始化為0xdeadbeef */
    for (i ==== 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)//   (9)
    {
            ((rt_uint32_t *)stack_frame)[i] ==== 0xdeadbeef;
    }

    /* 初始化異常發生時自動保存的寄存器 *///                            (10)
    stack_frame->exception_stack_frame.r0  ==== (unsigned long)parameter; /* r0 : argument */
    stack_frame->exception_stack_frame.r1  ==== 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  ==== 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  ==== 0;                        /* r3 */
    stack_frame->exception_stack_frame.r12 ==== 0;                        /* r12 */
    stack_frame->exception_stack_frame.lr  ==== 0;                        /* lr */
    stack_frame->exception_stack_frame.pc  ==== (unsigned long)tentry;    /* entry point, pc */
    stack_frame->exception_stack_frame.psr ==== 0x01000000L;              /* PSR */

    /* 返回線程棧指針 */
    return stk;//                                                    (11)
}

(5) :獲取棧頂指針,將棧頂指針傳給指針stk。rt_hw_stack_init()函數 在rt_thread_init ()函數中調用的時候傳給形參stack_addr的值是棧頂指針減去4,所以現在 加上sizeof(rt_uint32_t)剛好與減掉的4相互抵消,即傳遞給stk的是棧頂指針。

(6) :讓stk這個指針向下8個字節對齊,確保stk是8字節對齊的地址。 在Cortex-M3(Cortex-M4或Cortex-M7)內核的單片機中,因為總線寬度是32位的,通常只要棧保持4字節對齊就 行,可這樣為啥要8字節?難道有哪些操作是64位的?確實有,那就是浮 點運算,所以要8字節對齊(但是目前我們都還沒有涉及到浮點運算,只是為了后續兼容浮點運行的考慮)。 如果棧頂指針是8字節對齊的,在進行向下8字節對齊的時候,指針不會移動,如果不是8字節對齊的, 在做向下8字節對齊的時候,就會空出幾個字節,不會使用,比如當stk是33,明顯不能整除8, 進行向下8字節對齊就是32,那么就會空出一個字節不使用。

(7) :stk指針繼續向下移動sizeof(struct stack_frame) 個偏移,即16個字的大小。

ok!!!!!!!

這些了解了之后,我們在主函數內加入線程初始化即可

/* 初始化線程 */
rt_thread_init(&rt_flag1_thread,                 /* 線程控制塊 */
                flag1_thread_entry,               /* 線程入口地址 */
                RT_NULL,                          /* 線程形參 */
                &rt_flag1_thread_stack[0],        /* 線程棧起始地址 */
                sizeof(rt_flag1_thread_stack) );  /* 線程棧大小,單位為字節 */
/* 將線程插入到就緒列表 */

/* 初始化線程 */
rt_thread_init(&rt_flag2_thread,                 /* 線程控制塊 */
                flag2_thread_entry,               /* 線程入口地址 */
                RT_NULL,                          /* 線程形參 */
                &rt_flag2_thread_stack[0],        /* 線程棧起始地址 */
                sizeof(rt_flag2_thread_stack) );  /* 線程棧大小,單位為字節 */


審核編輯:符乾江

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

    關注

    5068

    文章

    19021

    瀏覽量

    303329
  • 線程機制
    +關注

    關注

    0

    文章

    2

    瀏覽量

    5589
  • RT Thread操作系統

    關注

    0

    文章

    4

    瀏覽量

    692
收藏 人收藏

    評論

    相關推薦

    線程創建成功了,為啥ai_thread_entry()函數不運行呢?

    我這個線程創建成功了,為啥ai_thread_entry()函數不運行呢? void airun_thread() { /* 創建 se
    發表于 09-27 09:35

    如何在RT-thread studio的裸機例程上移植freertos?

    如何在RT-thread studio的裸機例程上移植freertos
    發表于 09-13 06:32

    玩轉RT-Thread之消息隊列的應用

    在嵌入式系統開發中,實時處理串口和ADC數據是一項重要的任務。本文將介紹如何在RT-Thread實時操作系統中,利用消息隊列來同時處理來自串口和ADC的數據。通過這種方法,我們能夠高效地管理和處理
    的頭像 發表于 07-23 08:11 ?551次閱讀
    玩轉<b class='flag-5'>RT-Thread</b>之消息隊列的應用

    使用rt_thread_mdelay函數后出現hardfault的原因?

    問題:在一個線程中共有前后2部分使用rt_thread_mdelay()進行延時等待,前面部分延時是正常的,后面部分進入rt_thread_mdelay()函數后立馬打印hardfault錯誤,定位
    發表于 07-16 07:07

    rt-thread是如何在線程運行結束后識別到的呢?

    _mdelay(1000); } return RT_EOK; } 總所周知,main本身就是rt-thread創建線程,我在這個線程
    發表于 03-22 08:20

    RT-thread中運行了好多個線程,有的線程很長時間不執行是什么原因?

    RT-thread中運行了好多個線程,有的線程很長時間不執行,通過什么方式知道線程因為什么原因阻塞
    發表于 03-22 06:48

    【從0開始創建AWTK應用程序】編譯應用到RTOS平臺

    AWTK是基于C語言開發的跨平臺GUI框架。本系列文章介紹如何從0開始創建AWTK應用程序,包括搭建開發調試環境、使用AWTK創建Hello工程并在模擬器上運行、將AWTK應用程序移植到其它平臺。在
    的頭像 發表于 03-21 08:23 ?572次閱讀
    【從0<b class='flag-5'>開始創建</b>AWTK應用程序】編譯應用到RTOS平臺

    RT-Thread創建SQLite數據庫失敗是什么原因呢?

    STM32F103ZET6基于RT-Thread V4.1.1,文件系統littlefs,SQLite是從github下載的;在線程中調用示例代碼create_student_tbl()創建
    發表于 03-05 06:35

    線程中調用rt_thread_mdelay()函數程序卡死了怎么解決?

    線程中調用rt_thread_mdelay()函數程序卡死。搞了兩天也不知道問題出在哪,怎么解決。 int main(void) { interrupt_config
    發表于 02-26 08:39

    RTT Nano線程創建成功,沒有進入線程創建的函數運行怎么解決?

    RTT Nano線程創建成功,沒有進入線程創建的函數運行int main(void) { interrupt_config(); // gd_eval_led_init(LED1
    發表于 02-26 06:27

    使用rt_thread nano有辦法查看線程的資源占用情況嗎?

    使用rt_thread nano ,有辦法查看線程的資源占用情況麼?
    發表于 02-26 06:05

    RT-Thread Nano在調用rt_thread_control時,修改線程優先級一直不執行的原因?如何解決?

    RT-Thread Nano在調用rt_thread_control時,修改線程優先級一直不執行
    發表于 02-23 07:32

    【先楫HPM5361EVK開發板試用體驗】RT-Thread GPIO使用

    key_init(void) { /* 創建線程,名稱是 key_thread,入口是 key_thread*/ key_tid = rt_t
    發表于 12-24 19:00

    線程池的創建方式有幾種

    線程池是一種用于管理和調度線程的技術,能夠有效地提高系統的性能和資源利用率。它通過預先創建一組線程并維護一個工作隊列,將任務提交給線程池來處
    的頭像 發表于 12-04 16:52 ?827次閱讀

    【從0開始創建AWTK應用程序】創建應用程序并在模擬器運行

    AWTK是基于C語言開發的跨平臺GUI框架。本系列文章介紹如何從0開始創建AWTK應用程序,包括搭建開發調試環境、使用AWTK創建Hello工程并在模擬器上運行、將AWTK應用程序移植到其它平臺
    的頭像 發表于 12-01 08:24 ?469次閱讀
    【從0<b class='flag-5'>開始創建</b>AWTK應用程序】<b class='flag-5'>創建</b>應用程序并在模擬器運行