3429|2

530

帖子

4

TA的资源

一粒金砂(高级)

楼主
 

【RT-Thread读书笔记】4. RT-Thread 学习6章读后感(一) [复制链接]

  本帖最后由 传媒学子 于 2019-4-27 21:54 编辑

【RT-Thread读书笔记】4. RT-Thread 学习6章读后感
---线程的定义和线程切换的实现(一)

此次分享对应的是《RT-thread内核实现与应用开发实战指南》的第六章中6.1-6.3的内容。

线程是RTOS的基础,非常重要,线程的切换是用汇编代码来实现的,应该就是汇编直接编写arm指令代码,实现线程切换,这样做的好处,相对于C来讲应该就是切换的速度更快。

为什么用汇编来实现线程切换?
可能就是用汇编更接近与底层,相对于C来讲,速度可能更快,因为在RTOS中,线程切换是一个非常频繁的操作,线程切换会使用栈或者堆等存储或者读取上下文。

1.线程的定义:
在裸机系统中,系统就一个线程,即main函数中的大循环;在RTOS中,有很多这样的大循环,每个大循环简称为一个线程。通过任务调度器等完成县城切换。

2.创建线程:
在裸机系统中,会有全局变量,子函数调用,以及中断发生,在系统运行时,这些东西存放在哪里,我们不用十分关注。但在RTOS中,存在很多线程,我们必须弄清楚这些东西的存放位置,这样才能调用它们,保证RTOS的正确运行。

通常这些参数会被存放在栈中,栈一般是RAM中的一段空间。在RTOS中,线程之间是相互独立的,互不干扰的,因此,我们要为每个线程分配自己独立的栈空间。因此,我们会发现为什么RTOS会占用比裸机系统要大一些的RAM空间。

定义线程栈:
  1. [/align]rt_uint8_t rt_flag1_thread_stack[512];
  2. rt_uint8_t rt_flag2_thread_stack[512];
复制代码


在RT-Thread中,所有涉及到数据类型的地方,都会在rtdef.h中重新定义:
  1. /* RT-Thread basic data type definitions */
  2. typedef signed   char                   rt_int8_t;      /**<  8bit integer type */
  3. typedef signed   short                  rt_int16_t;     /**< 16bit integer type */
  4. typedef signed   long                   rt_int32_t;     /**< 32bit integer type */
  5. typedef unsigned char                   rt_uint8_t;     /**<  8bit unsigned integer type */
  6. typedef unsigned short                  rt_uint16_t;    /**< 16bit unsigned integer type */
  7. typedef unsigned long                   rt_uint32_t;    /**< 32bit unsigned integer type */
  8. typedef int                             rt_bool_t;      /**< boolean type */

  9. /* 32bit CPU */
  10. typedef long                            rt_base_t;      /**< Nbit CPU related date type */
  11. typedef unsigned long                   rt_ubase_t;     /**< Nbit unsigned CPU related data type */

  12. typedef rt_base_t                       rt_err_t;       /**< Type for error number */
  13. typedef rt_uint32_t                     rt_time_t;      /**< Type for time stamp */
  14. typedef rt_uint32_t                     rt_tick_t;      /**< Type for tick count */
  15. typedef rt_base_t                       rt_flag_t;      /**< Type for flags */
  16. typedef rt_ubase_t                      rt_size_t;      /**< Type for size number */
  17. typedef rt_ubase_t                      rt_dev_t;       /**< Type for device */
  18. typedef rt_base_t                       rt_off_t;       /**< Type for offset */

  19. /* boolean type definitions */
  20. #define RT_TRUE                         1               /**< boolean true  */
  21. #define RT_FALSE                        0               /**< boolean fails */
复制代码


3.定义线程函数:每个线程都是一个无限循环且不能返回的函数,其实就是以前裸机系统的main函数。

4.定义线程控制块:线程控制块是保存每个线程运行所需的所有信息的地方,例如线程的栈指针,线程名称,线程的各种参数等。系统可以通过线程控制块,实现对线程的调度。
      rt-thread中声明如下:
  1. /*
  2. *************************************************************************
  3. *                               线程结构体
  4. *************************************************************************
  5. */

  6. struct rt_thread
  7. {
  8.         void        *sp;                  /* 线程栈指针 */
  9.         void        *entry;                  /* 线程入口地址 */
  10.         void        *parameter;              /* 线程形参 */        
  11.         void        *stack_addr;      /* 线程起始地址 */
  12.         rt_uint32_t stack_size;       /* 线程栈大小,单位为字节 */
  13.         
  14.         rt_list_t   tlist;            /* 线程链表节点 */
  15. };
  16. typedef struct rt_thread *rt_thread_t;
复制代码


我们看到,这个结构体中,定义中使用了指针,其实指针这个东西真的很简单,就是一个tag,因为实际的物理地址比较长,一般是0xXXXXXXXXX, 为了方便,我们定义了一个指针,这个指针就代表这个实际的地址,而指针也是一个变量,它也会占用内存,因此会有指针的指针。建议好好理解一下指针, 因为有些老师在教书的时候,给大家灌输一种指针很难学,指针容易出错的错误理念,造成我们谈指色变,反正我是深受其害。希望有些老师在教书的时候,还是要客观的讲述一些东西,指针很重要,而不是指针很难用。这里多说了一点东西,有些地方可能理解有误,大家还是以书本讲述为准。
5.线程创建函数的实现:
      首先,是初始化函数:
  1. #include <rtthread.h>
  2. #include <rthw.h>

  3. rt_err_t rt_thread_init(struct rt_thread *thread,
  4.                         void (*entry)(void *parameter),
  5.                         void             *parameter,
  6.                         void             *stack_start,
  7.                         rt_uint32_t       stack_size)
  8. {
  9.         rt_list_init(&(thread->tlist));
  10.         
  11.         thread->entry = (void *)entry;
  12.         thread->parameter = parameter;

  13.         thread->stack_addr = stack_start;
  14.         thread->stack_size = stack_size;
  15.         
  16.         /* 初始化线程栈,并返回线程栈指针 */
  17.         thread->sp = (void *)rt_hw_stack_init( thread->entry,
  18.                                                    thread->parameter,
  19.                                                                        (void *)((char *)thread->stack_addr + thread->stack_size - 4) );
  20.         
  21.         return RT_EOK;
  22. }
复制代码

5.1实现链表相关函数
rt_list_init(&(thread->tlist)); 初始化线程链表节点,我们把线程控制块挂在链表中。
  1. /*
  2. *************************************************************************
  3. *                               双向链表结构体
  4. *************************************************************************
  5. */
  6. struct rt_list_node
  7. {
  8.     struct rt_list_node *next;              /* 指向后一个节点 */
  9.     struct rt_list_node *prev;              /* 指向前一个节点 */
  10. };
  11. typedef struct rt_list_node rt_list_t;                  
复制代码


初始化链表节点:就是将节点中的next和pre这两个节点指针指向节点本身。
双向链表的插入,删除 对于实现任务控制是很重要的知识,建议大家认真掌握和理解这些内容,这些知识数据结构上都会有详细介绍,在这本书中也有很多介绍,这里就不多说了。

5.2 rt_hw_stack_init()函数

  1. #include <rtthread.h>

  2. /*
  3. *************************************************************************
  4. *                                 数据类型
  5. *************************************************************************
  6. */
  7. struct exception_stack_frame
  8. {
  9.     /* 异常发生时自动保存的寄存器 */
  10.         rt_uint32_t r0;
  11.     rt_uint32_t r1;
  12.     rt_uint32_t r2;
  13.     rt_uint32_t r3;
  14.     rt_uint32_t r12;
  15.     rt_uint32_t lr;
  16.     rt_uint32_t pc;
  17.     rt_uint32_t psr;
  18. };

  19. struct stack_frame
  20. {
  21.     /* r4 ~ r11 register
  22.           异常发生时需手动保存的寄存器 */
  23.     rt_uint32_t r4;
  24.     rt_uint32_t r5;
  25.     rt_uint32_t r6;
  26.     rt_uint32_t r7;
  27.     rt_uint32_t r8;
  28.     rt_uint32_t r9;
  29.     rt_uint32_t r10;
  30.     rt_uint32_t r11;

  31.     struct exception_stack_frame exception_stack_frame;
  32. };
  33. /*
  34. *************************************************************************
  35. *                                 全局变量
  36. *************************************************************************
  37. */

  38. /* 用于存储上一个线程的栈的sp的指针 */
  39. rt_uint32_t rt_interrupt_from_thread;

  40. /* 用于存储下一个将要运行的线程的栈的sp的指针 */
  41. rt_uint32_t rt_interrupt_to_thread;

  42. /* PendSV中断服务函数执行标志 */
  43. rt_uint32_t rt_thread_switch_interrupt_flag;


  44. /*
  45. *************************************************************************
  46. *                                 函数实现
  47. *************************************************************************
  48. */
  49. /* 线程栈初始化 */
  50. rt_uint8_t *rt_hw_stack_init(void       *tentry,
  51.                              void       *parameter,
  52.                              rt_uint8_t *stack_addr)
  53. {
  54.         
  55.         
  56.         struct stack_frame *stack_frame;
  57.         rt_uint8_t         *stk;
  58.         unsigned long       i;
  59.         
  60.         
  61.         /* 获取栈顶指针
  62.          rt_hw_stack_init 在调用的时候,传给stack_addr的是(栈顶指针)*/
  63.         stk  = stack_addr + sizeof(rt_uint32_t);
  64.         
  65.         /* 让stk指针向下8字节对齐 */
  66.         stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
  67.         
  68.         /* stk指针继续向下移动sizeof(struct stack_frame)个偏移 */
  69.         stk -= sizeof(struct stack_frame);
  70.         
  71.         /* 将stk指针强制转化为stack_frame类型后存到stack_frame */
  72.         stack_frame = (struct stack_frame *)stk;
  73.         
  74.         /* 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame)
  75.         个内存初始化为0xdeadbeef */
  76.         for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
  77.         {
  78.                         ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;[/i]
  79. [i]        }[/i]
  80.         
  81.         /* 初始化异常发生时自动保存的寄存器 */
  82.         stack_frame->exception_stack_frame.r0  = (unsigned long)parameter; /* r0 : argument */
  83.         stack_frame->exception_stack_frame.r1  = 0;                        /* r1 */
  84.         stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */
  85.         stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */
  86.         stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */
  87.         stack_frame->exception_stack_frame.lr  = 0;                        /* lr */
  88.         stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */
  89.         stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */
  90.         
  91.         /* 返回线程栈指针 */
  92.         return stk;
  93. }

复制代码


下面是错误代码的定义:
  1. /*
  2. *************************************************************************
  3. *                               错误码定义
  4. *************************************************************************
  5. */
  6. /* RT-Thread 错误码重定义 */
  7. #define RT_EOK                          0               /**< There is no error */
  8. #define RT_ERROR                        1               /**< A generic error happens */
  9. #define RT_ETIMEOUT                     2               /**< Timed out */
  10. #define RT_EFULL                        3               /**< The resource is full */
  11. #define RT_EEMPTY                       4               /**< The resource is empty */
  12. #define RT_ENOMEM                       5               /**< No memory */
  13. #define RT_ENOSYS                       6               /**< No system */
  14. #define RT_EBUSY                        7               /**< Busy */
  15. #define RT_EIO                          8               /**< IO error */
  16. #define RT_EINTR                        9               /**< Interrupted system call */
  17. #define RT_EINVAL                       10              /**< Invalid argument */
复制代码


下面在主函数中初始化线程:
  1. int main(void)
  2. {        
  3.         /* 硬件初始化 */
  4.         /* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */        
  5.         
  6.         /* 调度器初始化 */
  7.         rt_system_scheduler_init();
  8.         
  9.         
  10.         /* 初始化线程 */
  11.         rt_thread_init( &rt_flag1_thread,                 /* 线程控制块 */
  12.                         flag1_thread_entry,               /* 线程入口地址 */
  13.                         RT_NULL,                          /* 线程形参 */
  14.                         &rt_flag1_thread_stack[0],        /* 线程栈起始地址 */
  15.                         sizeof(rt_flag1_thread_stack) );  /* 线程栈大小,单位为字节 */
  16.         /* 将线程插入到就绪列表 */
  17.         rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
  18.         
  19.         /* 初始化线程 */
  20.         rt_thread_init( &rt_flag2_thread,                 /* 线程控制块 */
  21.                         flag2_thread_entry,               /* 线程入口地址 */
  22.                         RT_NULL,                          /* 线程形参 */
  23.                         &rt_flag2_thread_stack[0],        /* 线程栈起始地址 */
  24.                         sizeof(rt_flag2_thread_stack) );  /* 线程栈大小,单位为字节 */
  25.         /* 将线程插入到就绪列表 */
  26.         rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
  27.         
  28.         /* 启动系统调度器 */
  29.         rt_system_scheduler_start();
  30. }
复制代码



由于这一章内容较多,而且重要,我这边分为多期来学习这一章,今天先介绍到这里。

总结:
本文主要对RT-Thread的线程定义以及如何创建线程进行了梳理,创建线程又包括: 定义线程栈,定义线程函数,定义线程控制块,实现线程创建函数等内容,涵盖了数据结构的指针、栈、双向链表的知识,涉及C语言结构体等内容。
同时,结合rt-thread源码,学习了rt-thread关于这些内容的设计和实现。

下面会就书中“6.4实现就绪列表、6.5实现调度器、6.6实现main函数”等内容进行学习和梳理。

此内容由EEWORLD论坛网友传媒学子原创,如需转载或用于商业用途需征得作者同意并注明出处


最新回复

我想问问你,创建线程是在单独的一个.c文件中吗?  详情 回复 发表于 2019-5-6 14:02
点赞 关注(2)
 

回复
举报

103

帖子

0

TA的资源

一粒金砂(中级)

沙发
 
我想问问你,创建线程是在单独的一个.c文件中吗?
 
个人签名坚持自己的坚持,终究会有拨开云雾的一天
 
 

回复

530

帖子

4

TA的资源

一粒金砂(高级)

板凳
 
沈婷婷 发表于 2019-5-6 14:02
我想问问你,创建线程是在单独的一个.c文件中吗?

库建好了,在哪里都行。
给你举个野火的实例:

  1. /**
  2.   *********************************************************************
  3.   * @file    main.c
  4.   * @author  fire
  5.   * [url=home.php?mod=space&uid=252314]@version[/url] V1.0
  6.   * [url=home.php?mod=space&uid=311857]@date[/url]    2018-xx-xx
  7.   * [url=home.php?mod=space&uid=159083]@brief[/url]   RT-Thread 3.0 + STM32 工程模版
  8.   *********************************************************************
  9.   * @attention
  10.   *
  11.   * 实验平台:野火 F429挑战者 STM32 开发板
  12.   * 论坛    :[url]http://www.firebbs.cn[/url]
  13.   * 淘宝    :[url]https://fire-stm32.taobao.com[/url]
  14.   *
  15.   **********************************************************************
  16.   */

  17. /*
  18. *************************************************************************
  19. *                             包含的头文件
  20. *************************************************************************
  21. */
  22. #include "board.h"
  23. #include "rtthread.h"


  24. /*
  25. *************************************************************************
  26. *                               变量
  27. *************************************************************************
  28. */
  29. /* 定义线程控制块 */
  30. static rt_thread_t led1_thread = RT_NULL;

  31. /*
  32. *************************************************************************
  33. *                             函数声明
  34. *************************************************************************
  35. */
  36. static void led1_thread_entry(void* parameter);


  37. /*
  38. *************************************************************************
  39. *                             main 函数
  40. *************************************************************************
  41. */
  42. /**
  43.   * @brief  主函数
  44.   * @param  无
  45.   * @retval 无
  46.   */
  47. int main(void)
  48. {       
  49.     /*
  50.          * 开发板硬件初始化,RTT系统初始化已经在main函数之前完成,
  51.          * 即在component.c文件中的rtthread_startup()函数中完成了。
  52.          * 所以在main函数中,只需要创建线程和启动线程即可。
  53.          */
  54.        
  55.         led1_thread =                          /* 线程控制块指针 */
  56.     rt_thread_create( "led1",              /* 线程名字 */
  57.                       led1_thread_entry,   /* 线程入口函数 */
  58.                       RT_NULL,             /* 线程入口函数参数 */
  59.                       512,                 /* 线程栈大小 */
  60.                       3,                   /* 线程的优先级 */
  61.                       20);                 /* 线程时间片 */
  62.                   
  63.     /* 启动线程,开启调度 */
  64.    if (led1_thread != RT_NULL)
  65.         rt_thread_startup(led1_thread);
  66.     else
  67.         return -1;
  68. }

  69. /*
  70. *************************************************************************
  71. *                             线程定义
  72. *************************************************************************
  73. */

  74. static void led1_thread_entry(void* parameter)
  75. {       
  76.     while (1)
  77.     {
  78.         LED1_ON;
  79.         rt_thread_delay(500);   /* 延时500个tick */
  80.         
  81.         LED1_OFF;     
  82.         rt_thread_delay(500);   /* 延时500个tick */                                

  83.     }
  84. }

  85. /********************************END OF FILE****************************/
复制代码
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表