• RT-Thread--线程管理


    线程管理的功能特点

    • RT-Thread系统中线程是调度的最小单位;
    • 线程分为:系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除

    • RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。
    • 当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。

    线程的工作机制

    线程控制块

    • 线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等
    • /**
       * Thread structure
       */
      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 */
      
          /* light weight process if present */
      #ifdef RT_USING_LWP
          void        *lwp;
      #endif
      
          rt_uint32_t user_data;                             /**< private user data beyond this thread */
      };
      typedef struct rt_thread *rt_thread_t;
    • nit_priority 是线程创建时指定的线程优先级,在线程运行过程当中是不会被改变的(除非用户执行线程控制函数进行手动调整线程优先级);
    • cleanup 会在线程退出时,被空闲线程回调一次以执行用户设置的清理现场等工作;
    • user_data 可由用户挂接一些数据信息到线程控制块中,以提供类似线程私有数据的实现。线程重要属性;

    线程重要属性

    线程栈

    • RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
    • 线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。
    • 于线程第一次运行,可以以手工的方式构造这个上下文来设置一些初始的环境:入口函数(PC 寄存器)、入口参数(R0 寄存器)、返回位置(LR 寄存器)、当前机器运行状态(CPSR 寄存器)。
    • 线程栈的增长方向是芯片构架密切相关的,RT-Thread 3.1.0 以前的版本,均只支持栈由高地址向低地址增长的方式,对于 ARM Cortex-M 架构,线程栈可构造如下图所示

    • 线程栈大小可以这样设定:对于资源相对较大的 MCU,可以适当设计较大的线程栈;也可以在初始时设置较大的栈,例如指定大小为 1K 或 2K 字节,然后在 FinSH 中用 list_thread 命令查看线程运行的过程中线程所使用的栈的大小,通过此命令,能够看到从线程启动运行时,到当前时刻点,线程使用的最大栈深度,而后加上适当的余量形成最终的线程栈大小,最后对栈空间大小加以修改。

    线程状态

    • 线程运行的过程中,同一时间内只允许一个线程在处理器中运行,从运行的过程上划分,线程有多种不同的运行状态,如初始状态、挂起状态、就绪状态等。在 RT-Thread 中,线程包含五种状态,操作系统会自动根据它运行的情况来动态调整它的状态;
    • 初始状态:当线程刚开始创建还没开始运行时就处于初始状态;在初始状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT;
    • 就绪状态:在就绪状态下,线程按照优先级排队,等待被执行;一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY;
    • 运行状态:线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态;在多核系统中,可能就不止这一个线程处于运行状态。此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING;
    • 挂起状态:也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。在挂起状态下,线程不参与调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND;
    • 关闭状态:当线程运行结束时将处于关闭状态。关闭状态的线程不参与线程的调度。此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE;
    • /*
       * thread state definitions
       */
      #define RT_THREAD_INIT                  0x00                //*< Initialized status */
      #define RT_THREAD_READY                 0x01                //*< Ready status */
      #define RT_THREAD_SUSPEND               0x02                //*< Suspend status */
      #define RT_THREAD_RUNNING               0x03                //*< Running status */
      #define RT_THREAD_BLOCK                 RT_THREAD_SUSPEND   //*< Blocked status */
      #define RT_THREAD_CLOSE                 0x04                //*< Closed status */

    线程优先级

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

    时间片

    • 个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效;
    • 系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用;
    • 假设有 2 个优先级相同的就绪态线程 A 与 B,A 线程的时间片设置为 10,B 线程的时间片设置为 5,那么当系统中不存在比 A 优先级高的就绪态线程时,系统会在 A、B 线程间来回切换执行,并且每次对 A 线程执行 10 个节拍的时长,对 B 线程执行 5 个节拍的时长,如下图示:

    线程的入口函数

    • 线程控制块中的entry是线程的入口函数。线程的入口函数由用户设计实现,分为:无循环模式和顺序执行(有限循环模式);
    • 实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU 使用权的动作,如循环中调用延时函数或者主动挂起。
    • 无限循环模式:无线循环的线程的目的,就是为了让这个线程一直被系统循环调度运行,永不删除
    • void thread_entry(void* paramenter)
      {
          while (1)
          {
          /* 等待事件的发生 */
      
          /* 对事件进行服务、进行处理 */
          }
      }
    • 顺序执行模式:如简单的顺序语句、do whlie() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。
    • static void thread_entry(void* parameter)
      {
          /* 处理事务 #1 *//* 处理事务 #2 *//* 处理事务 #3 */
      }

    线程的错误码

    •  一个线程就是一个执行场景,错误码是与执行环境密切相关的,所以每个线程配备了一个变量用于保存错误码,线程的错误码有以下几种:
    • #define RT_EOK           0 // 无错误     
      #define RT_ERROR         1 //  普通错误     
      #define RT_ETIMEOUT      2 //  超时错误    
      #define RT_EFULL         3 //  资源已满     
      #define RT_EEMPTY        4 //  无资源     
      #define RT_ENOMEM        5 //  无内存   
      #define RT_ENOSYS        6 //  系统不支持   
      #define RT_EBUSY         7 //  系统忙    
      #define RT_EIO           8 //  IO 错误      
      #define RT_EINTR         9 //  中断系统调用  
      #define RT_EINVAL       10 //  非法参数     

    线程状态切换

    •  RT-Thread 提供一系列的操作系统调用接口,使得线程的状态在这五个状态之间来回切换。几种状态间的转换关系如下图所示:

    • 线程通过调用函数 rt_thread_create/init() 进入到初始状态(RT_THREAD_INIT);
    • 初始状态的线程通过调用函数 rt_thread_startup() 进入到就绪状态(RT_THREAD_READY);
    • 就绪状态的线程被调度器调度后进入运行状态(RT_THREAD_RUNNING);
    • 当处于运行状态的线程调用 rt_thread_delay(),rt_sem_take(),rt_mutex_take(),rt_mb_recv() 等函数或者获取不到资源时,将进入到挂起状态(RT_THREAD_SUSPEND);
    • 处于挂起状态的线程,如果等待超时依然未能获得资源或由于其他线程释放了资源,那么它将返回到就绪状态。
    • 挂起状态的线程,如果调用 rt_thread_delete/detach() 函数,将更改为关闭状态(RT_THREAD_CLOSE);
    • 运行状态的线程,如果运行结束,就会在线程的最后部分执行 rt_thread_exit() 函数,将状态更改为关闭状态。
    • 注意:RT-Thread 中,实际上线程并不存在运行状态,就绪状态和运行状态是等同的。

    系统线程

    • 系统线程是指由系统创建的线程,用户线程是由用户程序调用线程管理接口创建的线程,在 RT-Thread 内核中的系统线程有空闲线程和主线程。

    空闲线程

    • 空闲线程是系统创建的最低优先级的线程,线程状态永远为就绪态。当系统中无其他就绪线程存在时,调度器将调度到空闲线程,它通常是一个死循环,且永远不能被挂起。
    • 空闲线程在 RT-Thread 也有着它的特殊用途:
    • 若某线程运行完毕,系统将自动删除线程:自动执行 rt_thread_exit() 函数,先将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入 rt_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后空闲线程会回收被删除线程的资源。
    • 空闲线程也提供了接口来运行用户设置的钩子函数,在空闲线程运行时会调用该钩子函数,适合钩入功耗管理、看门狗喂狗等工作。
    • /**
       * @ingroup Hook
       * This function sets a hook function to idle thread loop. When the system performs
       * idle loop, this hook function should be invoked.
       *
       * @param hook the specified hook function
       *
       * @return RT_EOK: set OK
       *         -RT_EFULL: hook list is full
       *
       * @note the hook function must be simple and never be blocked or suspend.
       */
      rt_err_t rt_thread_idle_sethook(void (*hook)(void))
      
      /**
       * delete the idle hook on hook list
       *
       * @param hook the specified hook function
       *
       * @return RT_EOK: delete OK
       *         -RT_ENOSYS: hook was not found
       */
      rt_err_t rt_thread_idle_delhook(void (*hook)(void))

    主线程

    • 在系统启动时,系统会创建 main 线程,它的入口函数为 main_thread_entry(),用户的应用入口函数 main() 就是从这里真正开始的,系统调度器启动后,main 线程就开始运行,过程如下图,就可以在 main() 函数里添加自己的应用程序初始化代码。

    线程的管理方式

    • 线程的相关操作,包含:创建 / 初始化线程、启动线程、运行线程、删除 / 脱离线程。
    • 可以使用 rt_thread_create() 创建一个动态线程;
    • 使用 rt_thread_init() 初始化一个静态线程;
    • 动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。

    创建和删除线程

    • 一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。可以通过如下的接口创建一个动态线程:

    • /**
       * This function will create a thread object and allocate thread object memory
       * and stack.
       *
       * @param name the name of thread, which shall be unique
       * @param entry the entry function of thread
       * @param parameter the parameter of thread enter function
       * @param stack_size the size of thread stack
       * @param priority the priority of thread
       * @param tick the time slice if there are same priority thread
       *
       * @return the created thread object,RT_EOK on RT_ERROR
       */
      rt_thread_t rt_thread_create(const char *name,
                                   void (*entry)(void *parameter),
                                   void       *parameter,
                                   rt_uint32_t stack_size,
                                   rt_uint8_t  priority,
                                   rt_uint32_t tick)
    • 对于一些使用 rt_thread_create() 创建出来的线程,当不需要使用,或者运行出错时,我们可以使用 rt_thread_delete 函数来从系统中把线程完全删除掉:

    • /**
       * This function will delete a thread. The thread object will be removed from
       * thread queue and deleted from system object management in the idle thread.
       *
       * @param thread the thread to be deleted
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       */
      rt_err_t rt_thread_delete(rt_thread_t thread)

    初始化和脱离线程

    • 线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象:
    • /**
       * This function will initialize a thread, normally it's used to initialize a
       * static thread object.
       *
       * @param thread the static thread object
       * @param name the name of thread, which shall be unique
       * @param entry the entry function of thread
       * @param parameter the parameter of thread enter function
       * @param stack_start the start address of thread stack
       * @param stack_size the size of thread stack
       * @param priority the priority of thread
       * @param tick the time slice if there are same priority thread
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       */
      rt_err_t rt_thread_init(struct rt_thread *thread,
                              const char       *name,
                              void (*entry)(void *parameter),
                              void             *parameter,
                              void             *stack_start,
                              rt_uint32_t       stack_size,
                              rt_uint8_t        priority,
                              rt_uint32_t       tick)
    • 静态线程的线程句柄(或者说线程控制块指针)、线程栈由用户提供。静态线程是指线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统对齐(例如 ARM 上需要做 4 字节对齐)。
    • 对于用 rt_thread_init() 初始化的线程,使用 rt_thread_detach() 将使线程对象在线程队列和内核对象管理器中被脱离。线程脱离函数如下:
    • /**
       * This function will detach a thread. The thread object will be removed from
       * thread queue and detached/deleted from system object management.
       *
       * @param thread the thread to be deleted
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       */
      rt_err_t rt_thread_detach(rt_thread_t thread)
    • 这个函数接口是和 rt_thread_delete() 函数相对应的, rt_thread_delete() 函数操作的对象是 rt_thread_create() 创建的句柄,而 rt_thread_detach() 函数操作的对象是使用 rt_thread_init() 函数初始化的线程控制块。

    启动线程

    • 创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化 / 创建成功后调用 rt_rtthread_startup() 函数接口让该线程进入就绪态:

    • /**
       * This function will start a thread and put it to system ready queue
       *
       * @param thread the thread to be started
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       */
      rt_err_t rt_thread_startup(rt_thread_t thread)

    获得当前线程

    • 程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过 rt_thread_self() 函数接口获得当前执行的线程句柄:

    • /**
       * This function will return self thread object
       *
       * @return the self thread object
       */
      rt_thread_t rt_thread_self(void)

    使线程让出处理器资源

    • 当前线程的时间片用完或者该线程主动要求让出处理器资源时,它将不再占有处理器,调度器会选择相同优先级的下一个线程执行。线程让出处理器可以使用 rt_err_t rt_thread_yield() 函数

    • /**
       * This function will let current thread yield processor, and scheduler will
       * choose a highest thread to run. After yield processor, the current thread
       * is still in READY state.
       *
       * @return RT_EOK
       */
      rt_err_t rt_thread_yield(void)
    • 调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
    • rt_thread_yield() 函数和 rt_schedule() 函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。执行 rt_thread_yield() 函数后,当前线程被换出,相同优先级的下一个就绪线程将被执行。而执行 rt_schedule() 函数后,当前线程并不一定被换出,即使被换出,也不会被放到就绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完 rt_schedule() 函数后,系统将继续执行当前线程)。

    使线程睡眠

    • 在实际应用中,我们有时需要让运行的当前线程延迟一段时间,在指定的时间到达后重新运行,这就叫做 “线程睡眠”。线程睡眠可使用以下三个函数接口,这三个函数接口的作用相同,调用它们可以使当前线程挂起一段指定的时间,当这个时间过后,线程会被唤醒并再次进入就绪状态。

    • /**
       * This function will let current thread sleep for some ticks.
       *
       * @param tick the sleep ticks
       *
       * @return RT_EOK
       */
      rt_err_t rt_thread_sleep(rt_tick_t tick)
      
      /**
       * This function will let current thread delay for some ticks.
       *
       * @param tick the delay ticks
       *
       * @return RT_EOK
       */
      rt_err_t rt_thread_delay(rt_tick_t tick)
      
      /**
       * This function will let current thread delay for some milliseconds.
       *
       * @param tick the delay time
       *
       * @return RT_EOK
       */
      rt_err_t rt_thread_mdelay(rt_int32_t ms)

    挂起和恢复线程

    • 当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
    • /**
       * This function will suspend the specified thread.
       *
       * @param thread the thread to be suspended
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       *
       * @note if suspend self thread, after this function call, the
       * rt_schedule() must be invoked.
       */
      rt_err_t rt_thread_suspend(rt_thread_t thread)
    • 注意:通常不应该使用这个函数来挂起线程本身,如果确实需要采用 rt_thread_suspend() 函数挂起当前任务,需要在调用 rt_thread_suspend() 函数后立刻调用 rt_schedule() 函数进行手动的线程上下文切换。只需要了解,不推荐使用;
    • 恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。
    • /**
       * This function will resume a thread and put it to system ready queue.
       *
       * @param thread the thread to be resumed
       *
       * @return the operation status, RT_EOK on OK, -RT_ERROR on error
       */
      rt_err_t rt_thread_resume(rt_thread_t thread)

    控制线程

    • 当需要对线程进行一些其他控制时,例如动态更改线程的优先级时,可以使用 rt_rtthread_control()函数

    • /**
       * This function will control thread behaviors according to control command.
       *
       * @param thread the specified thread to be controlled
       * @param cmd the control command, which includes
       *  RT_THREAD_CTRL_CHANGE_PRIORITY for changing priority level of thread;
       *  RT_THREAD_CTRL_STARTUP for starting a thread;
       *  RT_THREAD_CTRL_CLOSE for delete a thread.
       * @param arg the argument of control command
       *
       * @return RT_EOK
       */
      rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
    • 指示控制命令 cmd 当前支持的命令包括:

    • RT_THREAD_CTRL_CHANGE_PRIORITY:动态更改线程的优先级;

    • RT_THREAD_CTRL_STARTUP:开始运行一个线程,等同于 rt_thread_startup() 函数调用;

    • RT_THREAD_CTRL_CLOSE:关闭一个线程,等同于 rt_thread_delete() 函数调用。

    设置和删除空闲钩子

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

    • /**
       * @ingroup Hook
       * This function sets a hook function to idle thread loop. When the system performs
       * idle loop, this hook function should be invoked.
       *
       * @param hook the specified hook function
       *
       * @return RT_EOK: set OK
       *         -RT_EFULL: hook list is full
       *
       * @note the hook function must be simple and never be blocked or suspend.
       */
      rt_err_t rt_thread_idle_sethook(void (*hook)(void))
      
      
      /**
       * delete the idle hook on hook list
       *
       * @param hook the specified hook function
       *
       * @return RT_EOK: delete OK
       *         -RT_ENOSYS: hook was not found
       */
      rt_err_t rt_thread_idle_delhook(void (*hook)(void))
    • 空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。

    设置调度器钩子

    •  在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:
    • /**
       * This function will set a hook function, which will be invoked when thread
       * switch happens.
       *
       * @param hook the hook function
       */
      void rt_scheduler_sethook(void (*hook)(struct rt_thread *from, struct rt_thread *to))
    • 钩子函数 hook() 的声明如下:

    • void hook(struct rt_thread* from, struct rt_thread* to);

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

    线程应用实例

    创建线程

    • 创建一个动态线程,初始化一个静态线程,一个线程在运行完毕后自动被系统删除,另一个一直打印计数:
    • #include <rtthread.h>
      
      #define THREAD_PRIORITY         25
      #define THREAD_STACK_SIZE       512
      #define THREAD_TIMESLICE        5
      
      static rt_thread_t tid1 = RT_NULL;
      
      /* 线程 1 的入口函数 */
      static void thread1_entry(void *parameter)
      {
          rt_uint32_t count = 0;
      
          while (1)
          {
              /* 线程 1 采用低优先级运行,一直打印计数值 */
              rt_kprintf("thread1 count: %d
      ", count ++);
              rt_thread_mdelay(500);
          }
      }
      
      ALIGN(RT_ALIGN_SIZE)
      static char thread2_stack[1024];
      static struct rt_thread thread2;
      /* 线程 2 入口 */
      static void thread2_entry(void *param)
      {
          rt_uint32_t count = 0;
      
          /* 线程 2 拥有较高的优先级,以抢占线程 1 而获得执行 */
          for (count = 0; count < 10 ; count++)
          {
              /* 线程 2 打印计数值 */
              rt_kprintf("thread2 count: %d
      ", count);
          }
          rt_kprintf("thread2 exit
      ");
          /* 线程 2 运行结束后也将自动被系统脱离 */
      }
      
      /* 线程示例 */
      int thread_sample(void)
      {
          /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
          tid1 = rt_thread_create("thread1",
                                  thread1_entry, RT_NULL,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE);
      
          /* 如果获得线程控制块,启动这个线程 */
          if (tid1 != RT_NULL)
              rt_thread_startup(tid1);
      
          /* 初始化线程 2,名称是 thread2,入口是 thread2_entry */
          rt_thread_init(&thread2,
                         "thread2",
                         thread2_entry,
                         RT_NULL,
                         &thread2_stack[0],
                         sizeof(thread2_stack),
                         THREAD_PRIORITY - 1, THREAD_TIMESLICE);
          rt_thread_startup(&thread2);
      
          return 0;
      }
      
      /* 导出到 msh 命令列表中 */
      MSH_CMD_EXPORT(thread_sample, thread sample);
    • 运行结果
    •  | /
      - RT -     Thread Operating System
       / |      3.1.0 build Aug 24 2018
       2006 - 2018 Copyright by rt-thread team
      msh >thread_sample
      msh >thread2 count: 0
      thread2 count: 1
      thread2 count: 2
      thread2 count: 3
      thread2 count: 4
      thread2 count: 5
      thread2 count: 6
      thread2 count: 7
      thread2 count: 8
      thread2 count: 9
      thread2 exit
      thread1 count: 0
      thread1 count: 1
      thread1 count: 2
      thread1 count: 3
    • 关于删除线程:大多数线程是循环执行的,无需删除;而能运行完毕的线程,RT-Thread 在线程运行完毕后,自动删除线程,在 rt_thread_exit() 里完成删除动作。用户只需要了解该接口的作用,不推荐使用该接口;

    线程时间片轮转调度示例

    • 创建两个线程,在执行时会一直打印计数
    • #include <rtthread.h>
      
      #define THREAD_STACK_SIZE   1024
      #define THREAD_PRIORITY     20
      #define THREAD_TIMESLICE    10
      
      /* 线程入口 */
      static void thread_entry(void* parameter)
      {
          rt_uint32_t value;
          rt_uint32_t count = 0;
      
          value = (rt_uint32_t)parameter;
          while (1)
          {
              if(0 == (count % 5))
              {
                  rt_kprintf("thread %d is running ,thread %d count = %d
      ", value , value , count);
      
                  if(count> 200)
                      return;
              }
               count++;
           }
      }
      
      int timeslice_sample(void)
      {
          rt_thread_t tid = RT_NULL;
          /* 创建线程 1 */
          tid = rt_thread_create("thread1",
                                  thread_entry, (void*)1,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE);
          if (tid != RT_NULL)
              rt_thread_startup(tid);
      
      
          /* 创建线程 2 */
          tid = rt_thread_create("thread2",
                                  thread_entry, (void*)2,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE-5);
          if (tid != RT_NULL)
              rt_thread_startup(tid);
          return 0;
      }
      
      /* 导出到 msh 命令列表中 */
      MSH_CMD_EXPORT(timeslice_sample, timeslice sample);
    • 运行结果
    •   | /
      - RT -     Thread Operating System
       / |      3.1.0 build Aug 27 2018
       2006 - 2018 Copyright by rt-thread team
      msh >timeslice_sample
      msh >thread 1 is running ,thread 1 count = 0
      thread 1 is running ,thread 1 count = 5
      thread 1 is running ,thread 1 count = 10
      thread 1 is running ,thread 1 count = 15
      …
      thread 1 is running ,thread 1 count = 125
      thread 1 is rthread 2 is running ,thread 2 count = 0
      thread 2 is running ,thread 2 count = 5
      thread 2 is running ,thread 2 count = 10
      thread 2 is running ,thread 2 count = 15
      thread 2 is running ,thread 2 count = 20
      thread 2 is running ,thread 2 count = 25
      thread 2 is running ,thread 2 count = 30
      thread 2 is running ,thread 2 count = 35
      thread 2 is running ,thread 2 count = 40
      thread 2 is running ,thread 2 count = 45
      thread 2 is running ,thread 2 count = 50
      thread 2 is running ,thread 2 count = 55
      thread 2 is running ,thread 2 count = 60
      thread 2 is running ,thread 2 cunning ,thread 2 count = 65
      thread 1 is running ,thread 1 count = 135
      …
      thread 2 is running ,thread 2 count = 205

    线程调度器钩子示例

    • 在线程进行调度切换时,会执行调度,我们可以设置一个调度器钩子,这样可以在线程切换时,做一些额外的事情
    • #include <rtthread.h>
      
      #define THREAD_STACK_SIZE   1024
      #define THREAD_PRIORITY     20
      #define THREAD_TIMESLICE    10
      
      /* 针对每个线程的计数器 */
      volatile rt_uint32_t count[2];
      
      /* 线程 1、2 共用一个入口,但入口参数不同 */
      static void thread_entry(void* parameter)
      {
          rt_uint32_t value;
      
          value = (rt_uint32_t)parameter;
          while (1)
          {
              rt_kprintf("thread %d is running
      ", value);
              rt_thread_mdelay(1000); // 延时一段时间
          }
      }
      
      static rt_thread_t tid1 = RT_NULL;
      static rt_thread_t tid2 = RT_NULL;
      
      static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to)
      {
          rt_kprintf("from: %s -->  to: %s 
      ", from->name , to->name);
      }
      
      int scheduler_hook(void)
      {
          /* 设置调度器钩子 */
          rt_scheduler_sethook(hook_of_scheduler);
      
          /* 创建线程 1 */
          tid1 = rt_thread_create("thread1",
                                  thread_entry, (void*)1,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY, THREAD_TIMESLICE);
          if (tid1 != RT_NULL)
              rt_thread_startup(tid1);
      
          /* 创建线程 2 */
          tid2 = rt_thread_create("thread2",
                                  thread_entry, (void*)2,
                                  THREAD_STACK_SIZE,
                                  THREAD_PRIORITY,THREAD_TIMESLICE - 5);
          if (tid2 != RT_NULL)
              rt_thread_startup(tid2);
          return 0;
      }
      
      /* 导出到 msh 命令列表中 */
      MSH_CMD_EXPORT(scheduler_hook, scheduler_hook sample);
    • 运行结果

    •   | /
      - RT -     Thread Operating System
       / |      3.1.0 build Aug 27 2018
       2006 - 2018 Copyright by rt-thread team
      msh > scheduler_hook
      msh >from: tshell -->  to: thread1
      thread 1 is running
      from: thread1 -->  to: thread2
      thread 2 is running
      from: thread2 -->  to: tidle
      from: tidle -->  to: thread1
      thread 1 is running
      from: thread1 -->  to: tidle
      from: tidle -->  to: thread2
      thread 2 is running 

    参考

    • 《RT-Thread 编程指南》
  • 相关阅读:
    Python开发环境搭建
    Python逻辑判断顺序
    PyCharm快捷改变字体大小
    Python类型转换
    前端面试总结2020
    问题总结20-11-02至20-11-09
    问题总结20-11-30至20-12-06
    项目管理培训20-12-06
    日期计算
    数列分段
  • 原文地址:https://www.cnblogs.com/icefree/p/10805503.html
Copyright © 2020-2023  润新知