RT-Thread--內核移植


內核移植

  • 內核移植就是指將 RT-Thread 內核在不同的芯片架構、不同的板卡上運行起來,能夠具備線程管理和調度,內存管理,線程間同步和通信、定時器管理等功能。移植可分為 CPU 架構移植和 BSP(Board support package,板級支持包)移植兩部分。

CPU移植

  • 為了使 RT-Thread 能夠在不同 CPU 架構的芯片上運行,RT-Thread 提供了一個 libcpu 抽象層來適配不同的 CPU 架構。libcpu 層向上對內核提供統一的接口,包括全局中斷的開關,線程棧的初始化,上下文切換等。
  • RT-Thread 的 libcpu 抽象層向下提供了一套統一的 CPU 架構移植接口,這部分接口包含了全局中斷開關函數、線程上下文切換函數、時鍾節拍的配置和中斷函數、Cache 等等內容。下表是 CPU 架構移植需要實現的接口和變量。
  • 函數和變量 描述
    rt_base_t rt_hw_interrupt_disable(void); 關閉全局中斷
    void rt_hw_interrupt_enable(rt_base_t level); 打開全局中斷
    rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit); 線程棧的初始化,內核在線程創建和線程初始化里面會調用這個函數
    void rt_hw_context_switch_to(rt_uint32 to); 沒有來源線程的上下文切換,在調度器啟動第一個線程的時候調用,以及在 signal 里面會調用
    void rt_hw_context_switch(rt_uint32 from, rt_uint32 to); 從 from 線程切換到 to 線程,用於線程和線程之間的切換
    void rt_hw_context_switch_interrupt(rt_uint32 from, rt_uint32 to); 從 from 線程切換到 to 線程,用於中斷里面進行切換的時候使用
    rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要在中斷里進行切換的標志
    rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread; 在線程進行上下文切換時候,用來保存 from 和 to 線程

實現全局中斷開關

  •  無論內核代碼還是用戶的代碼,都可能存在一些變量,需要在多個線程或者中斷里面使用,如果沒有相應的保護機制,那就可能導致臨界區問題。RT-Thread 里為了解決這個問題,提供了一系列的線程間同步和通信機制來解決。但是這些機制都需要用到 libcpu 里提供的全局中斷開關函數。分別是:
  • /* 關閉全局中斷 */
    rt_base_t rt_hw_interrupt_disable(void);
    
    /* 打開全局中斷 */
    void rt_hw_interrupt_enable(rt_base_t level);

     

  •  Cortex-M 架構上如何實現這兩個函數,前文中曾提到過,Cortex-M 為了快速開關中斷,實現了 CPS 指令,可以用在此處

  • CPSID I ;PRIMASK=1, ; 關中斷
    CPSIE I ;PRIMASK=0, ; 開中斷

 

實現全局中斷開關

  • 在 rt_hw_interrupt_disable() 函數里面需要依序完成的功能是:

  1. 保存當前的全局中斷狀態,並把狀態作為函數的返回值。

  2. 關閉全局中斷。

  • 在 context_rvds.S 中實現
  • ;/*
    ; * rt_base_t rt_hw_interrupt_disable(void);
    ; */
    rt_hw_interrupt_disable    PROC      ;PROC 偽指令定義函數
        EXPORT  rt_hw_interrupt_disable  ;EXPORT 輸出定義的函數,類似於 C 語言 extern
        MRS     r0, PRIMASK              ; 讀取 PRIMASK 寄存器的值到 r0 寄存器
        CPSID   I                        ; 關閉全局中斷
        BX      LR                       ; 函數返回
        ENDP                             ;ENDP 函數結束

 

打開全局中斷

  • 在 rt_hw_interrupt_enable(rt_base_t level) 里,將變量 level 作為需要恢復的狀態,覆蓋芯片的全局中斷狀態。在 context_rvds.S 中實現;

  • ;/*
    ; * void rt_hw_interrupt_enable(rt_base_t level);
    ; */
    rt_hw_interrupt_enable    PROC      ; PROC 偽指令定義函數
        EXPORT  rt_hw_interrupt_enable  ; EXPORT 輸出定義的函數,類似於 C 語言 extern
        MSR     PRIMASK, r0             ; 將 r0 寄存器的值寫入到 PRIMASK 寄存器
        BX      LR                      ; 函數返回
        ENDP                            ; ENDP 函數結束

實現線程棧初始化

  • 在動態創建線程和初始化線程的時候,會使用到內部的線程初始化函數_rt_thread_init(),_rt_thread_init() 函數會調用棧初始化函數 rt_hw_stack_init(),在棧初始化函數里會手動構造一個上下文內容,這個上下文內容將被作為每個線程第一次執行的初始值。上下文在棧里的排布如下圖所示:

  • 下代碼是棧初始化的代碼:在棧里構建上下文;在 cpuport.c中實現;

  • rt_uint8_t *rt_hw_stack_init(void       *tentry,
                                 void       *parameter,
                                 rt_uint8_t *stack_addr,
                                 void       *texit)
    {
        struct stack_frame *stack_frame;
        rt_uint8_t         *stk;
        unsigned long       i;
    
        /* 對傳入的棧指針做對齊處理 */
        stk  = stack_addr + sizeof(rt_uint32_t);
        stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
        stk -= sizeof(struct stack_frame);
    
        /* 得到上下文的棧幀的指針 */
        stack_frame = (struct stack_frame *)stk;
    
        /* 把所有寄存器的默認值設置為 0xdeadbeef */
        for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
        {
            ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
        }
    
        /* 根據 ARM  APCS 調用標准,將第一個參數保存在 r0 寄存器 */
        stack_frame->exception_stack_frame.r0  = (unsigned long)parameter;
        /* 將剩下的參數寄存器都設置為 0 */
        stack_frame->exception_stack_frame.r1  = 0;                 /* r1 寄存器 */
        stack_frame->exception_stack_frame.r2  = 0;                 /* r2 寄存器 */
        stack_frame->exception_stack_frame.r3  = 0;                 /* r3 寄存器 */
        /* 將 IP(Intra-Procedure-call scratch register.) 設置為 0 */
        stack_frame->exception_stack_frame.r12 = 0;                 /* r12 寄存器 */
        /* 將線程退出函數的地址保存在 lr 寄存器 */
        stack_frame->exception_stack_frame.lr  = (unsigned long)texit;
        /* 將線程入口函數的地址保存在 pc 寄存器 */
        stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;
        /* 設置 psr 的值為 0x01000000L,表示默認切換過去是 Thumb 模式 */
        stack_frame->exception_stack_frame.psr = 0x01000000L;
    
        /* 返回當前線程的棧地址       */
        return stk;
    }

實現上下文切換

  • 在不同的 CPU 架構里,線程之間的上下文切換和中斷到線程的上下文切換,上下文的寄存器部分可能是有差異的,也可能是一樣的。在 Cortex-M 里面上下文切換都是統一使用 PendSV 異常來完成,切換部分並沒有差異。但是為了能適應不同的 CPU 架構,RT-Thread 的 libcpu 抽象層還是需要實現三個線程切換相關的函數:
    1.  rt_hw_context_switch_to():沒有來源線程,切換到目標線程,在調度器啟動第一個線程的時候被調用。

    2. rt_hw_context_switch():在線程環境下,從當前線程切換到目標線程。

    3. rt_hw_context_switch_interrupt ():在中斷環境下,從當前線程切換到目標線程。

  • 線程環境下,如果調用 rt_hw_context_switch() 函數,那么可以馬上進行上下文切換;而在中斷環境下,需要等待中斷處理函數完成之后才能進行切換;
  • 在 ARM9 等平台,rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 的實現並不一樣。在中斷處理程序里如果觸發了線程的調度,調度函數里會調用 rt_hw_context_switch_interrupt() 觸發上下文切換。中斷處理程序里處理完中斷事務之后,中斷退出之前,檢查 rt_thread_switch_interrupt_flag 變量,如果該變量的值為 1,就根據 rt_interrupt_from_thread 變量和 rt_interrupt_to_thread 變量,完成線程的上下文切換。

 

  • 在 Cortex-M 處理器架構里,基於自動部分壓棧和 PendSV 的特性,上下文切換可以實現地更加簡潔。
  • 線程之間的上下文切換,如下圖表示:

  • 硬件在進入 PendSV 中斷之前自動保存了 from 線程的 PSR、PC、LR、R12、R3-R0 寄存器,然后 PendSV 里保存 from 線程的 R11\~R4 寄存器,以及恢復 to 線程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中斷之后,自動恢復 to 線程的 R0\~R3、R12、LR、PC、PSR 寄存器。
  • 中斷到線程的上下文切換可以用下圖表示:

  • 硬件在進入中斷之前自動保存了 from 線程的 PSR、PC、LR、R12、R3-R0 寄存器,然后觸發了 PendSV 異常。在 PendSV 異常處理函數里保存 from 線程的 R11\~R4 寄存器,以及恢復 to 線程的 R4\~R11 寄存器,最后硬件在退出 PendSV 中斷之后,自動恢復 to 線程的 R0\~R3、R12、PSR、PC、LR 寄存器。
  • 顯然,在 Cortex-M 內核里 rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在 PendSV 里完成剩余上下文的保存和回復。所以我們僅僅需要實現一份代碼,簡化移植的工作。

實現 rt_hw_context_switch_to()

  •  rt_hw_context_switch_to() 只有目標線程,沒有來源線程。這個函數里實現切換到指定線程的功能,下圖是流程圖:

 

  • rt_hw_context_switch_to() 實現
  • ;/*
    ; * void rt_hw_context_switch_to(rt_uint32 to);
    ; * r0 --> to
    ; * this fucntion is used to perform the first thread switch
    ; */
    rt_hw_context_switch_to    PROC
        EXPORT rt_hw_context_switch_to
        ; r0 的值是一個指針,該指針指向 to 線程的線程控制塊的 SP 成員
        ; 將 r0 寄存器的值保存到 rt_interrupt_to_thread 變量里
        LDR     r1, =rt_interrupt_to_thread
        STR     r0, [r1]
    
        ; 設置 from 線程為空,表示不需要從保存 from 的上下文
        LDR     r1, =rt_interrupt_from_thread
        MOV     r0, #0x0
        STR     r0, [r1]
    
        ; 設置標志為 1,表示需要切換,這個變量將在 PendSV 異常處理函數里切換的時被清零
        LDR     r1, =rt_thread_switch_interrupt_flag
        MOV     r0, #1
        STR     r0, [r1]
    
        ; 設置 PendSV 異常優先級為最低優先級
        LDR     r0, =NVIC_SYSPRI2
        LDR     r1, =NVIC_PENDSV_PRI
        LDR.W   r2, [r0,#0x00]       ; read
        ORR     r1,r1,r2             ; modify
        STR     r1, [r0]             ; write-back
    
        ; 觸發 PendSV 異常 (將執行 PendSV 異常處理程序)
        LDR     r0, =NVIC_INT_CTRL
        LDR     r1, =NVIC_PENDSVSET
        STR     r1, [r0]
    
        ; 放棄芯片啟動到第一次上下文切換之前的棧內容,將 MSP 設置啟動時的值
        LDR     r0, =SCB_VTOR
        LDR     r0, [r0]
        LDR     r0, [r0]
        MSR     msp, r0
    
        ; 使能全局中斷和全局異常,使能之后將進入 PendSV 異常處理函數
        CPSIE   F
        CPSIE   I
    
        ; 不會執行到這里
        ENDP
  • rt_hw_context_switch()/ rt_hw_context_switch_interrupt()實現:
  • 函數 rt_hw_context_switch() 和函數 rt_hw_context_switch_interrupt() 都有兩個參數,分別是 from 線程和 to 線程。它們實現從 from 線程切換到 to 線程的功能。下圖是具體的流程圖:

  • rt_hw_context_switch()/rt_hw_context_switch_interrupt() 實現:、
  • ;/*
    ; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
    ; * r0 --> from
    ; * r1 --> to
    ; */
    rt_hw_context_switch_interrupt
        EXPORT rt_hw_context_switch_interrupt
    rt_hw_context_switch    PROC
        EXPORT rt_hw_context_switch
    
        ; 檢查 rt_thread_switch_interrupt_flag 變量是否為 1
        ; 如果變量為 1 就跳過更新 from 線程的內容
        LDR     r2, =rt_thread_switch_interrupt_flag
        LDR     r3, [r2]
        CMP     r3, #1
        BEQ     _reswitch
        ; 設置 rt_thread_switch_interrupt_flag 變量為 1
        MOV     r3, #1
        STR     r3, [r2]
    
        ; 從參數 r0 里更新 rt_interrupt_from_thread 變量
        LDR     r2, =rt_interrupt_from_thread
        STR     r0, [r2]
    
    _reswitch
        ; 從參數 r1 里更新 rt_interrupt_to_thread 變量
        LDR     r2, =rt_interrupt_to_thread
        STR     r1, [r2]
    
        ; 觸發 PendSV 異常,將進入 PendSV 異常處理函數里完成上下文切換
        LDR     r0, =NVIC_INT_CTRL
        LDR     r1, =NVIC_PENDSVSET
        STR     r1, [r0]
        BX      LR

實現 PendSV 中斷

  •  在 Cortex-M3 里,PendSV 中斷處理函數是 PendSV_Handler()。在 PendSV_Handler() 里完成線程切換的實際工作,下圖是具體的流程圖:

  •  PendSV_Handler 實現:
  • ; r0 --> switch from thread stack
    ; r1 --> switch to thread stack
    ; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
    PendSV_Handler   PROC
        EXPORT PendSV_Handler
    
        ; 關閉全局中斷
        MRS     r2, PRIMASK
        CPSID   I
    
        ; 檢查 rt_thread_switch_interrupt_flag 變量是否為 0
        ; 如果為零就跳轉到 pendsv_exit
        LDR     r0, =rt_thread_switch_interrupt_flag
        LDR     r1, [r0]
        CBZ     r1, pendsv_exit         ; pendsv already handled
    
        ; 清零 rt_thread_switch_interrupt_flag 變量
        MOV     r1, #0x00
        STR     r1, [r0]
    
        ; 檢查 rt_thread_switch_interrupt_flag 變量
        ; 如果為 0,就不進行 from 線程的上下文保存
        LDR     r0, =rt_interrupt_from_thread
        LDR     r1, [r0]
        CBZ     r1, switch_to_thread
    
        ; 保存 from 線程的上下文
        MRS     r1, psp                 ; 獲取 from 線程的棧指針
        STMFD   r1!, {r4 - r11}       ; 將 r4~r11 保存到線程的棧里
        LDR     r0, [r0]
        STR     r1, [r0]                ; 更新線程的控制塊的 SP 指針
    
    switch_to_thread
        LDR     r1, =rt_interrupt_to_thread
        LDR     r1, [r1]
        LDR     r1, [r1]                ; 獲取 to 線程的棧指針
    
        LDMFD   r1!, {r4 - r11}       ; 從 to 線程的棧里恢復 to 線程的寄存器值
        MSR     psp, r1                 ; 更新 r1 的值到 psp
    
    pendsv_exit
        ; 恢復全局中斷狀態
        MSR     PRIMASK, r2
    
        ; 修改 lr 寄存器的 bit2,確保進程使用 PSP 堆棧指針
        ORR     lr, lr, #0x04
        ; 退出中斷函數
        BX      lr
        ENDP

實現時鍾節拍

  • 有了開關全局中斷和上下文切換功能的基礎,RTOS 就可以進行線程的創建、運行、調度等功能了。有了時鍾節拍支持,RT-Thread 可以實現對相同優先級的線程采用時間片輪轉的方式來調度,實現定時器功能,實現 rt_thread_delay() 延時函數等等。
  • libcpu 的移植需要完成的工作,就是確保 rt_tick_increase() 函數會在時鍾節拍的中斷里被周期性的調用,調用周期取決於 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。
  • 在 Cortex M 中,實現 SysTick 的中斷處理函數即可實現時鍾節拍功能。
  • void SysTick_Handler(void)
    {
        /* enter interrupt */
        rt_interrupt_enter();
    
        rt_tick_increase();
    
        /* leave interrupt */
        rt_interrupt_leave();
    }

BSP移植

  • RT-Thread 提供了 BSP 抽象層來適配常見的板卡。如果希望在一個板卡上使用 RT-Thread 內核,除了需要有相應的芯片架構的移植,還需要有針對板卡的移植,也就是實現一個基本的 BSP。主要任務是建立讓操作系統運行的基本環境,需要完成的主要工作是:
    1. 初始化 CPU 內部寄存器,設定 RAM 工作時序。

       

    2. 實現時鍾驅動及中斷控制器驅動,完善中斷管理。

    3. 實現串口和 GPIO 驅動。

    4. 初始化動態內存堆,實現動態堆內存管理。

參考

  • 《RT-Thread 編程指南》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM