• RT-Thread内核学习记录


    内核

    内核基础(RTT v3.0.3)

    RT-Thread启动流程(基于STM32F103VET6)

    RT-Thread启动流程

    1. 启动文件startup_stm32f10x_hd.s

    2. $Sub$$main
      调用main()函数之前调用此函数,使用到了MDK编译器的特性,具体可见ARM® Compiler v5.06 for µVision®armlink User Guide。意思是调用main()之前,先调用$Sub

      main$Sub
      main里面再通过函数调用main()。

       //components.c
       int $Sub$$main(void)
       {
           rt_hw_interrupt_disable();
           rtthread_startup();
           return 0;
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    3. rtthread_startup() 函数

      //components.c
      int rtthread_startup(void)
       {
           rt_hw_interrupt_disable();
      
           /* board level initalization
           * NOTE: please initialize heap inside board initialization.
           */
           rt_hw_board_init();
      
           /* show RT-Thread version */
           rt_show_version();
      
           /* timer system initialization */
           rt_system_timer_init();
      
           /* scheduler system initialization */
           rt_system_scheduler_init();
      
       #ifdef RT_USING_SIGNALS
           /* signal system initialization */
           rt_system_signal_init();
       #endif
      
           /* create init_thread */
           rt_application_init();
      
           /* timer thread initialization */
           rt_system_timer_thread_init();
      
           /* idle thread initialization */
           rt_thread_idle_init();
      
           /* start scheduler */
           rt_system_scheduler_start();
      
           /* never reach here */
           return 0;
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      1. rt_hw_interrupt_disable()在context_rvds.s文件里面,作用是屏蔽中断。

        rt_hw_interrupt_disable    PROC
        EXPORT  rt_hw_interrupt_disable
        MRS     r0, PRIMASK ;读PRIMASK值到r0
        CPSID   I ;关中断
        BX      LR
        ENDP            
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6

        PRIMASK是Cortex-M3的一个特殊功能寄存器,属于中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)。关闭所有可以屏蔽的异常,只有NMI和硬件fault可以响应。

        特色功能寄存器

        特殊功能寄存器只能被专用的 MSR/MRS 指令访问,而且它们也没有与之相关联的访问地址。如:

        • MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器
        • MSR <special_reg>, <gp_reg> ;写通用寄存器的值到特殊功能寄存器
          Cortex-M3快速开关中断的CPS指令
           CPSID I ;PRIMASK=1,  ;关中断
           CPSIE I ;PRIMASK=0,  ;开中断
           CPSID F ;FAULTMASK=1, ;关异常	'
           Y]\,
           CPSIE F ;FAULTMASK=0  ;开异常
          
          • 1
          • 2
          • 3
          • 4
          • 5

        最后跳转LR寄存器(即R14)指向的地址,LR寄存器存储了子程序调用的返回地址。

      2. rt_hw_board_init():板级初始化,board.c文件中定义

        void rt_hw_board_init()
        {   
            //配置System Tick
            SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND ); 
            //外设硬件配置(用户)
            LED_GPIO_Config();
            /* Call components board initial (use INIT_BOARD_EXPORT()) */
            #ifdef RT_USING_COMPONENTS_INIT
                rt_components_board_init();
            #endif
                
            #if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
                rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
            #endif
                
            #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
                rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
            #endif
        }  
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        1. SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND )初始化并使能SysTick定时器,为RT-Thread提供时基,配置成1S有RT_TICK_PER_SECOND个时基,当Systick定时器进入计数到0,则通知内核tick加1。其中断处理函数如下:
          //SysTick中断时间处理函数
          void SysTick_Handler(void)
          {
              /* enter interrupt */
              rt_interrupt_enter();
          
              rt_tick_increase();//通知内核tick加1
          
              /* leave interrupt */
              rt_interrupt_leave();
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
        2. rt_components_board_init();
        3. rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
        4. rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());//初始化系统堆空间
          • 系统使用的堆空间由一个数组分配,再由系统使用
            //在board.c中
            #if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
            #define RT_HEAP_SIZE 1024
            static uint32_t rt_heap[RT_HEAP_SIZE];	// heap default size: 4K(1024 * 4)
            RT_WEAK void *rt_heap_begin_get(void)
            {
                return rt_heap;
            }
            
            RT_WEAK void *rt_heap_end_get(void)
            {
                return rt_heap + RT_HEAP_SIZE;
            }
            #endif
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
            • 8
            • 9
            • 10
            • 11
            • 12
            • 13
            • 14
      3. rt_application_init();//此处创建主线程

        void rt_application_init(void)
        {
            rt_thread_t tid;
        
        #ifdef RT_USING_HEAP //动态线程,线程栈由堆分配
            tid = rt_thread_create("main", main_thread_entry, RT_NULL,
                                RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20);
            RT_ASSERT(tid != RT_NULL);
        #else //创建静态线程,线程栈由用户分配
            rt_err_t result;
        
            tid = &main_thread;
            result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
                                    main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20);
            RT_ASSERT(result == RT_EOK);
            
            /* if not define RT_USING_HEAP, using to eliminate the warning */
            (void)result;
        #endif
        
            rt_thread_startup(tid);//启动主线程
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22

        Q:为什么主线程的优先级设置为(RT_THREAD_PRIORITY_MAX / 3)?

        A:

        main_thread_entry()为主线程函数,其中在main_thread_entry()函数中调用main()函数。

        void main_thread_entry(void *parameter)
        {
            extern int main(void);
            //在MDK中,使用$Super$$main()区分main()和$Sub$$main(),调用$Super$$main()从而进入main()
            extern int $Super$$main(void);
        
            /* RT-Thread components initialization */
            rt_components_init();//主要是一些组件的初始化及应用初始化
        
            /* invoke system main function */
        #if defined (__CC_ARM)
            $Super$$main(); /* for ARMCC. */
        #elif defined(__ICCARM__) || defined(__GNUC__)
            main();
        #endif
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16

        rt_components_init()的目的和实现参考RT-Thread 自动初始化详解

        /* board init routines will be called in board_init() function */
        #define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")
        
        /* pre/device/component/env/app init routines will be called in init_thread */
        /* components pre-initialization (pure software initilization) */
        #define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
        /* device initialization */
        #define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
        /* components initialization (dfs, lwip, ...) */
        #define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
        /* environment initialization (mount disk, ...) */
        #define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
        /* appliation initialization (rtgui application etc ...) */
        #define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14

        补充:section含义?

      4. rt_system_scheduler_start();//调度具体实现之后再学习

    线程管理

    1. 线程重要属性

      1. 线程控制块
        struct rt_thread
        {
            /* rt object */
            char        name[RT_NAME_MAX];                      /**< the name of thread */
            rt_uint8_t  type;                                   /**< type of object */
            rt_uint8_t  flags;                                  /**< thread's flags */
        
        #ifdef RT_USING_MODULE
            void       *module_id;                              /**< id of application module */
        #endif
        
            rt_list_t   list;                                   /**< the object list */
            rt_list_t   tlist;                                  /**< the thread list */
        
            /* stack point and entry */
            void       *sp;                                     /**< stack point */
            void       *entry;                                  /**< entry */
            void       *parameter;                              /**< parameter */
            void       *stack_addr;                             /**< stack address */
            rt_uint32_t stack_size;                             /**< stack size */
        
            /* error code */
            rt_err_t    error;                                  /**< error code */
        
            rt_uint8_t  stat;                                   /**< thread status */
        
            /* priority */
            rt_uint8_t  current_priority;                       /**< current priority */
            rt_uint8_t  init_priority;                          /**< initialized priority */
        #if RT_THREAD_PRIORITY_MAX > 32
            rt_uint8_t  number;
            rt_uint8_t  high_mask;
        #endif
            rt_uint32_t number_mask;
        
        #if defined(RT_USING_EVENT)
            /* thread event */
            rt_uint32_t event_set;
            rt_uint8_t  event_info;
        #endif
        
        #if defined(RT_USING_SIGNALS)
            rt_sigset_t     sig_pending;                        /**< the pending signals */
            rt_sigset_t     sig_mask;                           /**< the mask bits of signal */
        
            void            *sig_ret;                           /**< the return stack pointer from signal */
            rt_sighandler_t *sig_vectors;                       /**< vectors of signal handler */
            void            *si_list;                           /**< the signal infor list */
        #endif
        
            rt_ubase_t  init_tick;                              /**< thread's initialized tick */
            rt_ubase_t  remaining_tick;                         /**< remaining tick */
        
            struct rt_timer thread_timer;                       /**< built-in thread timer */
        
            void (*cleanup)(struct rt_thread *tid);             /**< cleanup function when thread exit */
        
            rt_uint32_t user_data;                              /**< private user data beyond this thread */
        };
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
      2. 线程栈
        • 存储线程上下文信息

        RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。

        • 存放线程函数的局部变量

        线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。

        • 为线程分配的线程栈如果不满足线程的需求,运行的时候可能会引起异常。
      3. 入口函数
        线程控制块中,void *entry 存储入口函数,实现线程预期功能。入口函数一般有两种模式:
        • 无限循环模式:一般里面包含while(1),实现无限循环,但是循环里面一般有能让线程阻塞的函数如rt_thread_delay(),以让出处理器的使用权,否则低于线程优先级的线程就没有机会执行。
        • 顺序执行或有限次循环模式

        如简单的顺序语句、do while() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。

      4. 线程优先级

        RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。

      5. 时间片

        每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。

      6. 内置定时器thread_timer
        用来定时唤醒挂起的线程,在延迟rt_thread_delay()、睡眠函数rt_thread_sleep()中使用。
    2. 常用线程操作

      线程操作函数

      线程状态切换

      1. 线程创建(删除)/初始化(脱离)

        • rt_thread_create()创建一个动态线程,rt_thread_delete()删除动态线程;
        • rt_thread_init()初始化一个静态线程,rt_thread_detach() 脱离一个静态线程。静态线程需要用户提前分配栈空间,一般是一个一维数组。
        • 动态线程与静态线程的区别:区别是在哪里分配栈空间,而不是说动态线程的栈空间大小是动态的。动态线程的空间大小也是由用户设定,这一点和静态线程一样。

          动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。

      2. 启动线程 rt_thread_startup();

      3. 挂起/恢复线程

        1. 挂起:线程挂起有三个原因:

          • 线程自己阻塞自己,使其进入睡眠(挂起态)。以下三个函数作用一样。

            rt_err_t rt_thread_sleep(rt_tick_t tick);
            rt_err_t rt_thread_delay(rt_tick_t tick);
            rt_err_t rt_thread_mdelay(rt_int32_t ms);
            
            • 1
            • 2
            • 3
          • 因为等待资源挂起

            rt_sem_take()
            rt_mb_recv()
            
            • 1
            • 2
          • 被别的线程挂起。调用rt_thread_suspend()可以挂起别的函数,也可以自己挂起自己。如果线程A尝试调用rt_thread_suspend()挂起线程B的时候,如果线程B不处于就绪态,rt_thread_suspend()就会返回错误码。

            注:一个线程尝试挂起另一个线程是一个非常危险的行为,因此RT-Thread对此函数有严格的使用限制:该函数只能使用来挂起当前线程(即自己挂起自己),不可以在线程A中尝试挂起线程B。而且在挂起线程自己后,需要立刻调用 rt_schedule() 函数进行手动的线程上下文切换。这是因为A线程在尝试挂起B线程时,A线程并不清楚B线程正在运行什么程序,一旦B线程正在使用例如互斥量、信号量等影响、阻塞其他线程(如C线程)的内核对象,如果此时其他线程也在等待这个内核对象,那么A线程尝试挂起B线程的操作将会引发其他线程(如C线程)的饥饿,严重危及系统的实时性。

            if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
            {
                RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend: thread disorder, 0x%2x\n",
                                            thread->stat));
            
                return -RT_ERROR;
            }
            
            • 1
            • 2
            • 3
            • 4
            • 5
            • 6
            • 7
        2. 恢复 rt_err_t rt_thread_resume (rt_thread_t thread);

    3. 钩子函数
      钩子函数感觉和回调函数差不多,在触发了某个事件或者进入某种状态,钩子函数就会被执行。

      • 空闲钩子

        空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置 / 删除空闲钩子的接口如下:

        rt_err_t rt_thread_idle_sethook(void (*hook)(void));
        rt_err_t rt_thread_idle_delhook(void (*hook)(void));
        
        • 1
        • 2

        注:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。并且,由于 malloc、free 等内存相关的函数内部使用了信号量作为临界区保护,因此在钩子函数内部也不允许调用此类函数!

      • 调度器钩子
        当系统发生线程切换,调度器钩子函数就会被调用。hook是用户编写的钩子函数,里面存放了发生线程调度时需要执行的代码。
        void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));
        
        • 1

        注:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。

    线程间同步

    • 线程同步概念

      同步是指按预定的先后次序进行运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间将是无序的。
      线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入 / 退出临界区的方式有很多种:

      1. 调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。(注:屏蔽中断后,因为时基中断也被屏蔽,所以相当于系统事件停止,线程不可能发生调度)
      2. 调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。
    • 信号量(semaphore)

      • 计数型信号量:信号量的值可以表示信号量对象的实例数目、资源数目。

        • 数据结构

          //rtdef.h
          struct rt_semaphore
          {
              struct rt_ipc_object parent;  /* 继承自 rt_ipc_object 类 */
              rt_uint16_t value;            /* 信号量的值 */
          };
          /* rt_sem_t 是指向 semaphore 结构体的指针类型 */
          typedef struct rt_semaphore* rt_sem_t;
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8

          rt_ipc_object数据结构

          struct rt_ipc_object
          {
              struct rt_object parent;/* 继承自 rt_object */
          
              rt_list_t  suspend_thread;/* 在此IPC对象阻塞的线程 */   
          };
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6

          rt_object数据结构

          struct rt_object
          {
              char       name[RT_NAME_MAX]; /* 内核对象的名字 */                      
              rt_uint8_t type;              /* 内核对象类型 */                                 
              rt_uint8_t flag;              /* 内核对象标志 */                    
          
          #ifdef RT_USING_MODULE     
              void      *module_id;                               
          #endif
              rt_list_t  list;             /* 内核对象链表节点,记录双向链表的前后节点指针 */                         
          };
          typedef struct rt_object *rt_object_t;                  
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12

          内核对象类由一个枚举结构给出

          enum rt_object_class_type
          {
              RT_Object_Class_Thread = 0,                         /**< The object is a thread. */
              RT_Object_Class_Semaphore,                          /**< The object is a semaphore. */
              RT_Object_Class_Mutex,                              /**< The object is a mutex. */
              RT_Object_Class_Event,                              /**< The object is a event. */
              RT_Object_Class_MailBox,                            /**< The object is a mail box. */
              RT_Object_Class_MessageQueue,                       /**< The object is a message queue. */
              RT_Object_Class_MemHeap,                            /**< The object is a memory heap */
              RT_Object_Class_MemPool,                            /**< The object is a memory pool. */
              RT_Object_Class_Device,                             /**< The object is a device */
              RT_Object_Class_Timer,                              /**< The object is a timer. */
              RT_Object_Class_Module,                             /**< The object is a module. */
              RT_Object_Class_Unknown,                            /**< 此类型之前的内核对象类型是已经定义的 */
              RT_Object_Class_Static = 0x80                       /**< 说明这个内核对象是静态的,即其内存不是由系统在堆上分配的,删除内核对象时,不用回收内存。可以和其它类型一起使用 */
          };
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16

          IPC(Inter-Process Communication,进程间同步与通信)对象的flag,flag的作用是决定在此IPC对象挂起的线程以什么方式排队获取此IPC对象。RT_IPC_FLAG_FIFO表示使用先来后到的方式,RT_IPC_FLAG_PRIO表示按照线程的优先级排队。

          #define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
          #define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */
          
          • 1
          • 2

          内核设置了容器来管理容器内核对象,对象容器在RTT以静态结构体数组的形式存在。结构体数组包含所定义的内核对象,数组初始化记录了内核对象的类型以及对象链表指针。当创建一个内核对象时,内核对象需要插入链表管理。

          //object.c
          static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] =
          {
              /* initialize object container - thread */
              {RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)},
          #ifdef RT_USING_SEMAPHORE
              /* initialize object container - semaphore */
              {RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)},
          #endif
          #ifdef RT_USING_MUTEX
              /* initialize object container - mutex */
              {RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex), sizeof(struct rt_mutex)},
          #endif
          #ifdef RT_USING_EVENT
              /* initialize object container - event */
              {RT_Object_Class_Event, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event), sizeof(struct rt_event)},
          #endif
          #ifdef RT_USING_MAILBOX
              /* initialize object container - mailbox */
              {RT_Object_Class_MailBox, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox), sizeof(struct rt_mailbox)},
          #endif
          #ifdef RT_USING_MESSAGEQUEUE
              /* initialize object container - message queue */
              {RT_Object_Class_MessageQueue, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue), sizeof(struct rt_messagequeue)},
          #endif
          #ifdef RT_USING_MEMHEAP
              /* initialize object container - memory heap */
              {RT_Object_Class_MemHeap, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap), sizeof(struct rt_memheap)},
          #endif
          #ifdef RT_USING_MEMPOOL
              /* initialize object container - memory pool */
              {RT_Object_Class_MemPool, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool), sizeof(struct rt_mempool)},
          #endif
          #ifdef RT_USING_DEVICE
              /* initialize object container - device */
              {RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), sizeof(struct rt_device)},
          #endif
              /* initialize object container - timer */
              {RT_Object_Class_Timer, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer), sizeof(struct rt_timer)},
          #ifdef RT_USING_MODULE
              /* initialize object container - module */
              {RT_Object_Class_Module, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module), sizeof(struct rt_module)},
          #endif
          };
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44

          Q: RTT设置内核对象链表的意义?

          A:可以通过宏rt_list_entry获取内核对象的地址,从而管理内核对象。

        • 用户函数

          信号量操作

      • 二值信号量:二值信号量和计数型信号量在RTT中并没有本质区别,二值信号量初始值为1,通常用于临界区的互斥访问。

        锁,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问。信号量在作为锁来使用时,通常应将信号量资源实例初始化成 1,代表系统默认有一个资源可用,因为信号量的值始终在 1 和 0 之间变动,所以这类锁也叫做二值信号量。如下图所示,当线程需要访问共享资源时,它需要先获得这个资源锁。当这个线程成功获得资源锁时,其他打算访问共享资源的线程会由于获取不到资源而挂起,这是因为其他线程在试图获取这个锁时,这个锁已经被锁上(信号量值是 0)。当获得信号量的线程处理完毕,退出临界区时,它将会释放信号量并把锁解开,而挂起在锁上的第一个等待线程将被唤醒从而获得临界区的访问权。

        二值信号量作为锁

    • 互斥量(mutex)

      • 互斥量和二值信号量的功能相类似,但是使用二值信号量可能导致几个问题:

        • 线程死锁
          线程如果递归的持有信号量,最终会导致线程被挂起。而由于线程持有了信号量(所有的值),不能释放信号量,所以线程一直被挂起,形成死锁。

        • 线程优先级翻转
          优先级翻转的意思是:高优先级的线程得不到及时的运行,但是比其低优先级的线程却得以充分运行。

          所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。如下图所示:有优先级为 A、B 和 C 的三个线程,优先级 A> B > C。线程 A,B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,且线程B没有用到共享资源 M ,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。

          优先级反转
          )

      • 互斥量

        • 支持递归访问,不会引起死锁

        • 不会引起线程优先级翻转问题:RTT使用优先级继承协议解决优先级翻转问题。

          优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

        • 数据结构

          struct rt_mutex
          {
              struct rt_ipc_object parent;                        /**< inherit from ipc_object */
          
              rt_uint16_t          value;                         /**< value of mutex */
          
              rt_uint8_t           original_priority;             /**< priority of last thread hold the mutex */
              rt_uint8_t           hold;                          /**< numbers of thread hold the mutex */
          
              struct rt_thread    *owner;                         /**< current owner of mutex */
          };
          typedef struct rt_mutex *rt_mutex_t;
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12

          original_priority是持有互斥量线程的原优先级,设置此变量是因为RTT使用了优先级继承协议,当线程不再持有此互斥量时,线程的优先级需要保持原来的优先级。owner表示持有此互斥量的线程。hold用来支持互斥量的递归持有:如果持有此互斥量的线程再次尝试持有此互斥量,hold加1,value值不改变;如果持有互斥量的线程释放此互斥量,hold减1,当hold为0,此互斥量不在被此线程持有,互斥量被释放。

        • 操作函数

          互斥量操作

          • rt_sem_release()释放信号量时,如果有在该信号量上阻塞的线程,会先将阻塞的线程唤醒,而不是执行sem->value ++。只有当在该信号量没有阻塞的线程时,执行sem->value ++。
    • 事件集(event)

      事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。

      • 数据结构
      struct rt_event
      {
          struct rt_ipc_object parent;    /* 继承自 ipc_object 类 */
      
          /* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */
          rt_uint32_t set;
      };
      /* rt_event_t 是指向事件结构体的指针类型  */
      typedef struct rt_event* rt_event_t;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 操作函数

      事件集操作

    线程间通信

    • 邮箱

    • 消息队列

    • 信号

    时钟管理

    • RTT时钟

      • RTT中的基本时间单位:RTT中以时钟节拍 (OS Tick)为基本时间单位,线程的时间片、软件定时器的时间,都是以时钟节拍计数。用户可以通过RT_TICK_PER_SECOND宏定义时钟节拍的长短。如果定义RT_TICK_PER_SECOND为1000,则1秒内打1000个时钟节拍,即一个时钟节拍的长度是1ms。
      • 时钟节拍的产生:时钟节拍是通过配置好的硬件定时器产生的,当硬件定时器到达设定的一个时钟节拍的长度后,就会进入定时中断处理函数,通知系统内核时钟节拍加1。STM32 Cortex-M3内核的MCU中,RTT一般以Systick定时器做系统时钟节拍的硬件定时器。
    • 定时器:定时器的作用是定时执行任务。

      • 硬件定时器:芯片本身提供的硬件功能,当向定时器对应的寄存器设置相应的值(设定定时时间),到达定时时间后,就会触发定时中断。使用定时器可以进行精确定时。
      • 软件定时器;由操作系统提供的软件定时接口,其定时的最小单位是一个时钟节拍。
    • RTT定时器:RTT提供的定时器有两种模式:HARD_TIMER以及SOFT_TIMER模式。两种模式下,定时器的最小时间单位都是一个tick,其区别在于定时器超时函数的执行环境。HARD_TIMER模式下,定时器超时函数在系统中断的上下文环境执行;SOFT_TIMER模式下,超时函数在timer线程的上下文环境执行。

      • HARD_TIMER:在int rtthread_startup(void) 中,调用rt_system_timer_init()初始化定时器链表,设置为HARD_TIMER模式的定时器启动后其节点将插入此链表。硬件定时器链表表头是一个静态数组:static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL]。定时器链表是一个跳表,用来加速链表的查询。如下图,head就是rt_timer_list,跳表的层数由RT_TIMER_SKIP_LIST_LEVEL设置,RT_TIMER_SKIP_LIST_LEVEL-1是最底层。检索时,从0层开始检索。

        定时器跳表

      • SOFT_TIMER:通过定义RT_USING_TIMER_SOFT宏来启用SOFT_TIMER模式,当启用此模式之后,系统在初始化阶段(rtthread_startup())通过rt_system_timer_thread_init()创建timer线程。SOFT_TIMER模式的定时器超时函数都是在timer线程中执行。

    • RTT定时器数据结构

      struct rt_timer
      {
          struct rt_object parent;                            //继承内核对象
      
          rt_list_t        row[RT_TIMER_SKIP_LIST_LEVEL];     //链表节点,启动定时器时插入定时器链表
      
          void (*timeout_func)(void *parameter);              //超时函数
          void            *parameter;                         //超时函数参数
      
          rt_tick_t        init_tick;                         //设定超时时间
          rt_tick_t        timeout_tick;                      //加上目前的时间后的超时时间
      };
      typedef struct rt_timer *rt_timer_t;
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      init_tick和timeout_tick区分:假设目前的时钟节拍current_tick = 10,10后定时器函数执行,则init_tick = 10,timeout_tick = 20。

    • RTT定时器操作

      定时器操作

      • rt_timer_init/create():区别主要是在哪里给定时器控制块分配内存空间,其余实现逻辑差不多。
      • rt_timer_start()
        rt_err_t rt_timer_start(rt_timer_t timer)
        {
            unsigned int row_lvl;
            rt_list_t *timer_list;
            register rt_base_t level;
            rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
            unsigned int tst_nr;
            static unsigned int random_nr;
        
            /* timer check */
            RT_ASSERT(timer != RT_NULL);
        
            //将定时器从定时器链表移除(如果定时器已经启动)
            level = rt_hw_interrupt_disable();
            /* remove timer from list */
            _rt_timer_remove(timer);
            /* change status of timer */
            timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
            rt_hw_interrupt_enable(level);
        
            RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));
        
            //定时时间须小于RT_TICK_MAX / 2
            RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
            timer->timeout_tick = rt_tick_get() + timer->init_tick;
        
            /* disable interrupt */
            level = rt_hw_interrupt_disable();
        
        #ifdef RT_USING_TIMER_SOFT
            if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
            {
                /* insert timer to soft timer list */
                timer_list = rt_soft_timer_list;
            }
            else
        #endif
            {
                /* insert timer to system timer list */
                timer_list = rt_timer_list;
            }
            
            //此段代码的作用是找出跳表各层中,定时器应该插入的节点
            //从跳表的顶层开始检索
            row_head[0]  = &timer_list[0];
            for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
            {
                for (; row_head[row_lvl] != timer_list[row_lvl].prev;
                    row_head[row_lvl]  = row_head[row_lvl]->next)
                {
                    struct rt_timer *t;
                    rt_list_t *p = row_head[row_lvl]->next;
        
                    //rt_list_entry的作用是取得节点p对应的结构体的地址
                    //这里获取节点p所对应定时器控制块的地址
                    //思想是利用p地址减去(rt_timer *)(0)->row[row_lvl]的地址
                    //得到p所在结构体地址
                    t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
        
                    //如果遇到一个相同的init_tick的定时器,则按照先来后到的原则
                    //将定时器插入此定时器之后
                    if ((t->timeout_tick - timer->timeout_tick) == 0)
                    {
                        continue;
                    }
                    else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
                    {
                        break;
                    }
                }
                //调表下一层起始节点
                if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
                    row_head[row_lvl + 1] = row_head[row_lvl] + 1;
            }
        
            /* Interestingly, this super simple timer insert counter works very very
            * well on distributing the list height uniformly. By means of "very very
            * well", I mean it beats the randomness of timer->timeout_tick very easily
            * (actually, the timeout_tick is not random and easy to be attacked). */
            random_nr++;
            tst_nr = random_nr;
            
            //此段代码作用是将定时器插入跳表各层对应的位置
            //将定时器插入最底层节点
            rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
                                &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
            for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
            {
                //这里意思大概是random_nr决定每层应不应该插入定时器
                //RT_TIMER_SKIP_LIST_MASK若为0x03,则下一层每隔3个节点,即每新增4个节点,此层就插入一个节点
                if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
                    rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
                                        &(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));
                else
                    break;
                /* Shift over the bits we have tested. Works well with 1 bit and 2
                * bits. */
                //以random_nr的1/2个bit表示一层的节点增加数,利用进位来决定一层是否应该增加节点
                tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
            }
        
            timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
        
            /* enable interrupt */
            rt_hw_interrupt_enable(level);
        
        #ifdef RT_USING_TIMER_SOFT
            if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
            {
                /* check whether timer thread is ready */
                if ((timer_thread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
                {
                    /* resume timer thread to check soft timer */
                    rt_thread_resume(&timer_thread);
                    rt_schedule();
                }
            }
        #endif
        
            return -RT_EOK;
        }
        RTM_EXPORT(rt_timer_start);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
        • 50
        • 51
        • 52
        • 53
        • 54
        • 55
        • 56
        • 57
        • 58
        • 59
        • 60
        • 61
        • 62
        • 63
        • 64
        • 65
        • 66
        • 67
        • 68
        • 69
        • 70
        • 71
        • 72
        • 73
        • 74
        • 75
        • 76
        • 77
        • 78
        • 79
        • 80
        • 81
        • 82
        • 83
        • 84
        • 85
        • 86
        • 87
        • 88
        • 89
        • 90
        • 91
        • 92
        • 93
        • 94
        • 95
        • 96
        • 97
        • 98
        • 99
        • 100
        • 101
        • 102
        • 103
        • 104
        • 105
        • 106
        • 107
        • 108
        • 109
        • 110
        • 111
        • 112
        • 113
        • 114
        • 115
        • 116
        • 117
        • 118
        • 119
        • 120
        • 121
        • 122
        启动定时器步骤:
        • 从原来的链表移除定时器
        • 计算timeout_tick
        • 搜索此定时器应该在跳表中应该插入的位置
        • 插入定时器到跳表
        • 如果是SOFT_TIMER,则启动timer线程
      • rt_timer_stop()
        rt_err_t rt_timer_stop(rt_timer_t timer)
        {
            register rt_base_t level;
        
            /* timer check */
            RT_ASSERT(timer != RT_NULL);
            if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
                return -RT_ERROR;
        
            RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));
        
            /* disable interrupt */
            level = rt_hw_interrupt_disable();
        
            _rt_timer_remove(timer);
        
            /* enable interrupt */
            rt_hw_interrupt_enable(level);
        
            /* change stat */
            timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
        
            return RT_EOK;
        }
        RTM_EXPORT(rt_timer_stop);
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        停止定时器相比启动定时器简单:主要是将定时器节点从跳表中移除。
      • rt_timer_detach/delete()和rt_timer_init/create()对应使用

    内存管理

    中断管理

    • Cortex-M3中断机制

      • 寄存器

        • 通用寄存器:R0R3、R4R12、R13(MSP、PSP)、R14(LR)、R15(PC)。R0R3、R4R12可以用来传递参数,R0开始传递第一个参数,R0也可以返回函数返回值。MSP主堆栈指针寄存器,PSP进程堆栈指针寄存器。R14(LR)存放函数调用完毕的返回地址。
        • 特殊功能寄存器

        特殊功能寄存器

      • 操作模式、特权级别

        • 操作模式:thread模式和handler模式。handler模式执行异常处理函数,thread模式运行其它代码(用户代码/…)。操作模式区分的是异常和正常流程代码。
        • 特权级别:特权级别和非特权(用户)级别。特权级别区分的是对寄存器的操作权限。特权模式下,对所有的寄存器都有权限,非特权模式下,有些寄存器是不可以操作(写)的。
        • handler模式下一定是特权级别,而thread模式下可以有特权级别和非特权级别。当处于thread模式非特权级别时,不能直接修改CONTROL寄存器进入特权级别,因为非特权级别下并没有对CONTROL寄存器的操作权限。而应该通过触发异常进入handler模式,修改CONTROL寄存器。
      • NVIC:嵌套向量中断控制器。

      • PendSV系统调用:一个系统异常,通常用来辅助操作系统进行上下文切换。

    • RTT中断接口:install\mask\unmask在 Cortex-M0/M3/M4 的移植分支中没有。

      中断接口

    • Cortex-M3中运行RTT,中断实现和裸机上差不多。当中断触发,进入中断函数,中断函数内会通知相关的线程执行相关任务(上半部分),当中断函数返回后,相关线程就会启动(底半部分)。

    线程调度实现

    • rt_schedule()实现
      void rt_schedule(void)
      {
          rt_base_t level;
          struct rt_thread *to_thread;
          struct rt_thread *from_thread;
      
          /* disable interrupt */
          level = rt_hw_interrupt_disable();
      
          /* check the scheduler is enabled or not */
          if (rt_scheduler_lock_nest == 0)
          {
              register rt_ubase_t highest_ready_priority;
      /* 从就绪优先级列表找出最高优先级 */
      #if RT_THREAD_PRIORITY_MAX <= 32
              highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
      #else
              register rt_ubase_t number;
      
              number = __rt_ffs(rt_thread_ready_priority_group) - 1;
              highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
      #endif
      
              /* get switch to thread */
              to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
                                      struct rt_thread,
                                      tlist);
      
              /* if the destination thread is not the same as current thread */
              if (to_thread != rt_current_thread)
              {
                  rt_current_priority = (rt_uint8_t)highest_ready_priority;
                  from_thread         = rt_current_thread;
                  rt_current_thread   = to_thread;
      
                  RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
      
                  /* switch to new thread */
                  RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
                              ("[%d]switch to priority#%d "
                              "thread:%.*s(sp:0x%p), "
                              "from thread:%.*s(sp: 0x%p)\n",
                              rt_interrupt_nest, highest_ready_priority,
                              RT_NAME_MAX, to_thread->name, to_thread->sp,
                              RT_NAME_MAX, from_thread->name, from_thread->sp));
      
      #ifdef RT_USING_OVERFLOW_CHECK
                  _rt_scheduler_stack_check(to_thread);
      #endif
      
                  if (rt_interrupt_nest == 0)
                  {
                      extern void rt_thread_handle_sig(rt_bool_t clean_state);
      
                      rt_hw_context_switch((rt_uint32_t)&from_thread->sp,
                                          (rt_uint32_t)&to_thread->sp);
      
                      /* enable interrupt */
                      rt_hw_interrupt_enable(level);
      
      #ifdef RT_USING_SIGNALS
                      /* check signal status */
                      rt_thread_handle_sig(RT_TRUE);
      #endif
                  }
                  else
                  {
                      RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
      
                      rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp,
                                                  (rt_uint32_t)&to_thread->sp);
                      /* enable interrupt */
                      rt_hw_interrupt_enable(level);
                  }
              }
              else
              {
                  /* enable interrupt */
                  rt_hw_interrupt_enable(level);
              }
          }
          else
          {
              /* enable interrupt */
              rt_hw_interrupt_enable(level);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 调度流程:找出就绪的最高优先级 —> 从就绪链表中,找出最高优先级的线程 —> 上下文切换。
      • 查找就绪最高优先级(RT_THREAD_PRIORITY_MAX>32)
        • 数据结构:rt_thread_ready_priority_group、rt_thread_ready_table[]。rt_thread_ready_priority_group存储thread->current_priority高5位信息,rt_thread_ready_table[]存储thread->current_priority对应的低3位信息。两个结构都是按位的方式存储信息。
        • 查找方法:按照位图的方式查找。__rt_ffs()查找rt_thread_ready_priority_group对应的最高优先级。
          number = __rt_ffs(rt_thread_ready_priority_group) - 1;
          highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
          
          • 1
          • 2

    内核移植

    • 移植相关接口:libcpu的抽象层

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbuMYNIh-1657031522733)(img/RT-thread_2022-07-04-18-56-41.png)]

    • 开关全局中断:两个函数需要配对使用

      ;context-rvds.S
      ;/*
      ; * rt_base_t rt_hw_interrupt_disable();
      ; */
      rt_hw_interrupt_disable    PROC
          EXPORT  rt_hw_interrupt_disable
          MRS     r0, PRIMASK ;返回PRIMASK寄存器的原始值
          CPSID   I           ;关中断
          BX      LR          
          ENDP
      
      ;/*
      ; * void rt_hw_interrupt_enable(rt_base_t level);
      ; */
      rt_hw_interrupt_enable    PROC
          EXPORT  rt_hw_interrupt_enable
          MSR     PRIMASK, r0   ;恢复PRIMASK寄存器的原始值,即开中断
          BX      LR
          ENDP
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
    • 初始化线程的栈空间

      //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);
          //地址向下圆整,须是8的倍数
          stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
          //满递减堆栈
          stk -= sizeof(struct stack_frame);
      
          stack_frame = (struct stack_frame *)stk;
      
          /* init all register */
          for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
          {
              ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
          }
      
          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  = (unsigned long)texit;     /* lr */
          stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */
          stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */
      
          /* return task's current stack address */
          return stk;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
    • 切换上下文

      ;context-rvds.S
      ;/*
      ; * 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
      
          ; set rt_thread_switch_interrupt_flag to 1
          LDR     r2, =rt_thread_switch_interrupt_flag
          LDR     r3, [r2]
          CMP     r3, #1
          BEQ     _reswitch
          MOV     r3, #1
          STR     r3, [r2]
      
          LDR     r2, =rt_interrupt_from_thread   ; set rt_interrupt_from_thread
          STR     r0, [r2]
      
      _reswitch
          LDR     r2, =rt_interrupt_to_thread     ; set rt_interrupt_to_thread
          STR     r1, [r2]
      
          LDR     r0, =NVIC_INT_CTRL              ; trigger the PendSV exception (causes context switch)
          LDR     r1, =NVIC_PENDSVSET
          STR     r1, [r0]
          BX      LR
          ENDP
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • rt_thread_switch_interrupt_flag:上下文切换中断标志,每次触发 PendSV之前需要置1
      • rt_interrupt_from_thread和rt_interrupt_to_thread中保存的是线程的栈指针
      • 疑惑:为什么rt_thread_switch_interrupt_flag为1,直接保存to_thread的栈指针而不保存from_thread的栈指针?
    • PendSV中切换上下文

      ;context-rvds.S
      ; 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
      
          ; disable interrupt to protect context switch
          MRS     r2, PRIMASK
          CPSID   I
      
          ; get rt_thread_switch_interrupt_flag
          LDR     r0, =rt_thread_switch_interrupt_flag
          LDR     r1, [r0]
          CBZ     r1, pendsv_exit         ; pendsv already handled
      
          ; clear rt_thread_switch_interrupt_flag to 0
          MOV     r1, #0x00
          STR     r1, [r0]
      
          LDR     r0, =rt_interrupt_from_thread
          LDR     r1, [r0]
          CBZ     r1, switch_to_thread    ; skip register save at the first time
      
          MRS     r1, psp                 ; get from thread stack pointer
          STMFD   r1!, {r4 - r11}         ; push r4 - r11 register
          LDR     r0, [r0]
          STR     r1, [r0]                ; update from thread stack pointer
      
      switch_to_thread
          LDR     r1, =rt_interrupt_to_thread
          LDR     r1, [r1]
          LDR     r1, [r1]                ; load thread stack pointer
      
          LDMFD   r1!, {r4 - r11}         ; pop r4 - r11 register
          MSR     psp, r1                 ; update stack pointer
      
      pendsv_exit
          ; restore interrupt
          MSR     PRIMASK, r2
      
          ORR     lr, lr, #0x04
          BX      lr
          ENDP
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • rt_interrupt_to_thread为1,则直接进行上下文切换;为0则已经切换过上下文,此时进入PendSV中断不是由上下文切换触发的。
      • 上下文切换需要保存from_thread的psr, pc, lr, r12, r3, r2, r1, r0,r4 - r11寄存器;psr, pc, lr, r12, r3, r2, r1, r0 8个寄存器在进入中断之前是由硬件自动保存,PendSV中需要做的是将r4 - r11压栈;而恢复to_thread的环境则相反,PendSV中r4 - r11出栈,中断返回时由硬件自动恢复psr, pc, lr, r12, r3, r2, r1, r0。

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9xn5XpD-1657031522734)(img/RT-thread_2022-07-05-10-34-39.png)]
        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sFWJt6QH-1657031522736)(img/RT-thread_2022-07-05-10-36-07.png)]

    设备和驱动

    参考

    1. RT-Thread文档中心 —— 标准版本
    2. Cortex-M3 权威指南,Joseph Yiu 著,宋岩 译
  • 相关阅读:
    Linux项目自动化构建工具-make/Makefile
    前端面试题(四)
    Linux下交叉编译工具链的安转和配置总结
    AI技术在基于风险测试模式转型中的应用
    PX4天大bug,上电反复重启,连不上QGC!
    MapReduce分区机制(Hadoop)
    【MATLAB】CEEMDAN_ MFE_SVM_LSTM 神经网络时序预测算法
    linux常用命令
    【unity小技巧】实现无限滚动视图和类似CSGO的开箱抽奖功能及Content Size Fitter组件的使用介绍
    【洛谷】P1828 [USACO3.2] 香甜的黄油 Sweet Butter (最短路)
  • 原文地址:https://blog.csdn.net/qq_36439722/article/details/125629548