1、线程工作机制

1.1 线程控制块

  1. struct rt_thread
  2. {
  3. /* rt object */
  4. char name[RT_NAME_MAX]; /**< the name of thread */
  5. rt_uint8_t type; /**< type of object */
  6. rt_uint8_t flags; /**< thread's flags */
  7. rt_list_t list; /**< the object list */
  8. rt_list_t tlist; /**< the thread list */
  9. /* stack point and entry */
  10. void *sp; /**< stack point 栈指针*/
  11. void *entry; /**< entry */
  12. void *parameter; /**< parameter */
  13. void *stack_addr; /**< stack address point 栈地址指针*/
  14. rt_uint32_t stack_size; /**< stack size */
  15. /* error code */
  16. rt_err_t error; /**< error code */
  17. rt_uint8_t stat; /**< thread status 线程状态*/
  18. /* priority */
  19. rt_uint8_t current_priority; /**< current priority */
  20. rt_uint8_t init_priority; /**< initialized priority */
  21. rt_uint32_t number_mask;
  22. ...
  23. rt_ubase_t init_tick; /**< thread's initialized tick */
  24. rt_ubase_t remaining_tick; /**< remaining tick */
  25. struct rt_timer thread_timer; /**< built-in thread timer */
  26. void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */
  27. rt_uint32_t user_data; /**< private user data beyond this thread */
  28. };

注:cleanup函数指针指向的函数,会在线程退出的时候,被idle线程回调一次,执行用户设置的清理现场等工作。

1.2 线程属性

☐ 线程栈
RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
☐ 线程状态
image.png
☐ 线程优先级
RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行
☐ 时间片
每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。
注意:
作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU使用权的动作,如循环中调用延时函数或者主动挂起。

1.3 线程状态之间切换

image.png
注:就绪状态和运行状态是等同的

2、线程相关操作

2.1 创建和删除线程

  1. #include <rtthread.h>
  2. #define DBG_TAG "main"
  3. #define DBG_LVL DBG_LOG
  4. #include <rtdbg.h>
  5. rt_thread_t rtt_ntr1 = NULL;
  6. void th_entry1(void *parameter)
  7. {
  8. while(1)
  9. {
  10. rt_kprintf("the entry is running......\n");
  11. rt_thread_mdelay(1000);
  12. }
  13. }
  14. int main(void)
  15. {
  16. rtt_ntr1 = rt_thread_create("test",th_entry1,NULL,1024,20,5);
  17. if(rtt_ntr1 == RT_NULL)
  18. {
  19. LOG_E("rt_thread_create error .....\n");
  20. return -RT_ENOMEM;
  21. }
  22. else {
  23. LOG_E("rt_thread_create successful .....\n");
  24. rt_thread_startup(rtt_ntr1);
  25. }
  26. }

结束时自己就调用下面这个函数,所以我们一般不使用它。

  1. rt_err_t rt_thread_delete(rt_thread_t thread)

TIPS:动态线程和静态线程

RT-Thread中支持静态和动态两种定义方式。用线程来举例的话,rt_thread_init对应静态定义方式,rt_thread_create对应动态定义方式。
使用静态定义方式时,必须先定义静态的线程控制块,并且定义好堆栈空间,然后调用rt_thread_init来完成线程的初始化工作。采用这种方式,线程控制块和堆栈占用的内存会放在RW段,这段空间在编译时就已经确定,它不是可以动态分配的,所以不能被释放,而只能使用rt_thread_detach函数将该线程控制块从对象管理器中脱离。 使用动态定义方式rt_thread_create时,RT-Thread会动态申请线程控制块和堆栈空间。在编译时,编译器是不会感知到这段空间的,只有在程序运行时,RT-Thread才会从系统堆中申请分配这段内存空间,当不需要使用该线程时,调用rt_thread_delete函数就会将这段申请的内存空间重新释放到内存堆中。
这两种方式各有利弊,静态定义方式会占用RW/ZI空间,但是不需要动态分配内存,运行时效率较高,实时性较好。 动态方式不会占用额外的RW/ZI空间,占用空间小,但是运行时需要动态分配内存,效率没有静态方式高。
总的来说,这两种方式就是空间和时间效率的平衡,可以根据实际环境需求选择采用具体的分配方式

2.2初始化和脱离线程

线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象:

  1. struct rt_thread th2;
  2. void th_entry2(void *parameter)
  3. {
  4. while(1)
  5. {
  6. rt_kprintf("the entry2 is running......\n");
  7. rt_thread_mdelay(1000);
  8. }
  9. }
  10. int main(void)
  11. {
  12. int ret2 = 0;
  13. ret2 = rt_thread_init(&th2, "test2", th_entry2, NULL, th2_stack, sizeof(th2_stack), 19, 5);
  14. if(ret2 < 0)
  15. {
  16. LOG_E("rt_thread2_create error .....\n");
  17. return ret2;
  18. }
  19. else {
  20. LOG_E("rt_thread2_create successful .....\n");
  21. rt_thread_startup(&th2);
  22. }
  23. //rt_thread_detach(&th2); //脱离静态线程
  24. }

2.3获得当前线程

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

  1. rt_thread_t rt_thread_self(void)

2.4 让出处理器资源

  1. rt_err_t rt_thread_yield(void)

2.5 线程睡眠

  1. rt_err_t rt_thread_sleep(rt_tick_t tick)
  2. rt_err_t rt_thread_delay(rt_tick_t tick)
  3. rt_err_t rt_thread_mdelay(rt_int32_t ms)

2.6 控制线程函数

  1. rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)

2.7 设置和删除idle线程hook函数

  1. /**
  2. * @ingroup Hook
  3. * This function sets a hook function to idle thread loop. When the system performs
  4. * idle loop, this hook function should be invoked.
  5. *
  6. * @param hook the specified hook function
  7. *
  8. * @return RT_EOK: set OK
  9. * -RT_EFULL: hook list is full
  10. *
  11. * @note the hook function must be simple and never be blocked or suspend.
  12. */
  13. rt_err_t rt_thread_idle_sethook(void (*hook)(void))
  14. /**
  15. * delete the idle hook on hook list
  16. *
  17. * @param hook the specified hook function
  18. *
  19. * @return RT_EOK: delete OK
  20. * -RT_ENOSYS: hook was not found
  21. */
  22. rt_err_t rt_thread_idle_delhook(void (*hook)(void))

注意:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。

2.8 设置调度器hook函数

在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用

  1. /**
  2. * This function will set a hook function, which will be invoked when thread
  3. * switch happens.
  4. *
  5. * @param hook the hook function
  6. */
  7. void rt_scheduler_sethook(void (*hook)(struct rt_thread *from, struct rt_thread *to))
  1. void shcedule_hook1(struct rt_thread *from, struct rt_thread *to)
  2. {
  3. rt_kprintf("form %s-----> to %s.....\n",from->name,to->name);
  4. }
  5. rt_scheduler_sethook(shcedule_hook1);

image.png