• 回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

【RT-thread学习记】:Thread线程机制及应用

下面开讲一下rtt(rtthread的简称,全称打着实在太长了)的干货,

线程是rtt的最基本的单位,管理的核心,像是信号量,事件,消息,邮箱,还有内存管理其它都是以线程为单位展开的,你可以类比在裸奔年代的函数,函数的出现就是为了以其为单位对于软件需要实现的功能而存在的,其实线程的本质也是就是函数,但是她加入很多你以前可能没考虑过的因素,因为实时性的要求:

比如,你在裸奔时代如何实现抢占一个函数的处理,最终返回该函数呢,答案很简单:中断嘛,那再进一步,如何抢断中断的任务呢,答案也可以脱口而出:嵌套中断嘛,那我就剩下最后一个问题,那你能把所有函数都放在中断中,并按照嵌套中断管理排好顺序让他们执行吗?

我想针对这个问题还是有些困难的,因为中断机制有其特殊性,在于她的中断方式,还有中断嵌套的管理问题,你可以无限制进行嵌套吗?这肯定也是不行,就算是处理器允许,你想想你得要个多大栈才进行压栈处理啊。太不现实。

但是线程的出现就是为了解决这个实时性的问题,她可以实现函数级的抢占,不存在嵌套的问题。因为每个线程都有自己的栈,所以你也不要担心嵌套问题带来的栈溢出。

如何理解一个线程的内部运行机制:其实你可以考虑,假如合成你如何实现函数级的抢占功能。等你实现了也就理解了。


全部回复(6)
正序查看
倒序查看
2018-10-31 12:45
mark          
0
回复
2018-11-11 16:33

接下来看一下一个线程应该拥有哪些组成部分,才能实现线程的功能,首先内核负责线程的切换管理,但是这不意味着你定义函数就OK了,但是线程的基本功能还是要靠一个线程入口函数的函数来实现,基本功能都在这里面写,和你之前写的功能函数有相同又有一些不同:

/*线程入口*/
static void thread_entry(void* parameter)
{
rt_uint32_t count = 0;
rt_uint32_t no = (rt_uint32_t) parameter; /*获得线程的入口参数*/

while(1)
{
/*打印线程计数值输出*/
rt_kprintf("thread%d count: %d\n",no,count++);

/*休眠10个os tick*/
rt_thread_delay(10);
}
}

赋值粘贴的时候因为不带格式,所以看起来有点诡异,不管了,先来看的本体,是一个函数无疑 static 代表是个静态函数,其它文件中不能直接调用,void返回值类型为空,void*类型的参数指针,全地图兼容参数类型,最大的亮点在于体内有个while(1)看着是不是有点神奇,这一下还能执行别的东西了吗?实际是可以的,假如在没有自身和主动挂起和抢占的时候,她是一直执行的,但是她有个rt_thread_delay(10)会让她主动让出处理器的控制权。


0
回复
2018-11-11 16:51
@程序小白
接下来看一下一个线程应该拥有哪些组成部分,才能实现线程的功能,首先内核负责线程的切换管理,但是这不意味着你定义函数就OK了,但是线程的基本功能还是要靠一个线程入口函数的函数来实现,基本功能都在这里面写,和你之前写的功能函数有相同又有一些不同:/*线程入口*/staticvoidthread_entry(void*parameter){rt_uint32_tcount=0;rt_uint32_tno=(rt_uint32_t)parameter;/*获得线程的入口参数*/while(1){/*打印线程计数值输出*/rt_kprintf("thread%dcount:%d\n",no,count++);/*休眠10个ostick*/rt_thread_delay(10);}}赋值粘贴的时候因为不带格式,所以看起来有点诡异,不管了,先来看的本体,是一个函数无疑static代表是个静态函数,其它文件中不能直接调用,void返回值类型为空,void*类型的参数指针,全地图兼容参数类型,最大的亮点在于体内有个while(1)看着是不是有点神奇,这一下还能执行别的东西了吗?实际是可以的,假如在没有自身和主动挂起和抢占的时候,她是一直执行的,但是她有个rt_thread_delay(10)会让她主动让出处理器的控制权。

接下来来讨论一下有关于栈的问题,在裸奔时代,当中断触发时,当前运行的函数被打断,函数的执行的相关环境参数需要保存在栈中,等中断执行完毕后,再将栈中的保存的参数写入CPU寄存器中,恢复函数执行环境,这个栈叫做系统栈,定义的具体位置如下:

你可以手动调整,200不够就用400,但是你还是要在裸编程时候注意,这个函数嵌套的问题,过深的嵌套依然会导致你的栈溢出,因为栈不可能无限的大,所以需要你精简一些不必要的函数,牺牲理解性,缓解栈的压力,当你引入RTT后,你就需要手动在定义一个叫做任务栈的玩意儿,她是用任务被抢占和切换时候存储线程入口函数的储存,这样对于线程抢占和切换你就不用担心栈溢出了,因为一个线程一个栈,虽然物理开销多了,但是他可以让你走的更远。

如下:定义,可手动更改大小

#define THREAD_STACK_SIZE       512     //线程栈的大小 512

static rt_uint8_t thread1_stack[THREAD_STACK_SIZE];

0
回复
2018-11-11 16:59
@程序小白
接下来来讨论一下有关于栈的问题,在裸奔时代,当中断触发时,当前运行的函数被打断,函数的执行的相关环境参数需要保存在栈中,等中断执行完毕后,再将栈中的保存的参数写入CPU寄存器中,恢复函数执行环境,这个栈叫做系统栈,定义的具体位置如下:[图片]你可以手动调整,200不够就用400,但是你还是要在裸编程时候注意,这个函数嵌套的问题,过深的嵌套依然会导致你的栈溢出,因为栈不可能无限的大,所以需要你精简一些不必要的函数,牺牲理解性,缓解栈的压力,当你引入RTT后,你就需要手动在定义一个叫做任务栈的玩意儿,她是用任务被抢占和切换时候存储线程入口函数的储存,这样对于线程抢占和切换你就不用担心栈溢出了,因为一个线程一个栈,虽然物理开销多了,但是他可以让你走的更远。如下:定义,可手动更改大小#defineTHREAD_STACK_SIZE    512   //线程栈的大小512staticrt_uint8_tthread1_stack[THREAD_STACK_SIZE];

有了线程栈和线程入口函数,还需要线程控制块的辅佐,加上rtt的内核部分就可以实现线程的抢占和切换了,下面我们看看线程控制块的内部具体构造:其实相对比较复杂,主要是为了辅佐rtt的任务调度完成对于线程的控制,所以内部的所有信息看不懂也不要紧,你知道当你定义一个线程时必须要定义一个线程控制块就足够了:

/*
*线程控制块
*/
struct rt_thread
{
/*RT-Thread根对象定义*/
char name[RT_NAME_MAX];  /*对象的名称*/
rt_uint8_t type;         /*对象的类型*/
rt_uint8_t flags;        /*对象的参数*/
#ifdef RT_USING_MODULE
void *module_id;         /*线程所在的模块ID*/
#endif
rt_list_t list;          /*对象链表*/

rt_list_t tlist;         /*线程链表*/

/*栈指针及入口*/
void* sp;                /*线程的栈指针*/
void* entry;             /*线程入口*/
void* parameter;         /*线程入口参数*/
void* stack_addr;        /*线程栈地址*/
rt_uint16_t stack_size;  /*线程栈大小*/

rt_err_t error;          /*线程错误号*/

rt_uint8_t stat;         /*线程状态*/

/*优先级相关域*/
rt_uint8_t current_priority;  /*当前优先级*/
rt_uint8_t init_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)
/*时间相关域*/
rt_uint32_t event_set;
rt_uint8_t  event_info;
#endif

rt_ubase_t init_tick;       /*线程初始tick*/
rt_ubase_t remaining_tick;    /*线程当次运行剩余tick*/

  struct rt_timer thread_timer; /*线程定时器*/
  
  /*当线程退出时,需要执行的清理函数*/
  void(*cleanup)(struct rt_thread *tid);
  rt_uint32_t user_data;        /*用户数据*/

};

0
回复
2018-11-11 17:14
@程序小白
接下来来讨论一下有关于栈的问题,在裸奔时代,当中断触发时,当前运行的函数被打断,函数的执行的相关环境参数需要保存在栈中,等中断执行完毕后,再将栈中的保存的参数写入CPU寄存器中,恢复函数执行环境,这个栈叫做系统栈,定义的具体位置如下:[图片]你可以手动调整,200不够就用400,但是你还是要在裸编程时候注意,这个函数嵌套的问题,过深的嵌套依然会导致你的栈溢出,因为栈不可能无限的大,所以需要你精简一些不必要的函数,牺牲理解性,缓解栈的压力,当你引入RTT后,你就需要手动在定义一个叫做任务栈的玩意儿,她是用任务被抢占和切换时候存储线程入口函数的储存,这样对于线程抢占和切换你就不用担心栈溢出了,因为一个线程一个栈,虽然物理开销多了,但是他可以让你走的更远。如下:定义,可手动更改大小#defineTHREAD_STACK_SIZE    512   //线程栈的大小512staticrt_uint8_tthread1_stack[THREAD_STACK_SIZE];

总结一下,线程三大要素:线程控制块、线程栈、线程入口,当你需要使用线程时候,这仨部分在定义的时候是必须的,但是当你仅仅定义了这三个部分,还不能让线程正常的工作,因为线程的真正的调度工作是在RTT的内核中实现的,内核的实现说实话我也是只知一不知其二,展开讲以我的水准真的讲不明白,前期只考虑应用,所以干脆不讲了,但是你知道她是主导地位就可以了。

你定义了线程的三大要素,只是完成了线程的实体,要赋予其灵魂,让其真的开始工作,你还需要把它与rtt关联的在一起,你不需要rtt的内核是如何工作的,rtt给你开放了API函数作为接口,你可以把你定义的线程与RTT建立联系,就可以了。接下来将RTT有关线程的API。

0
回复
2018-12-25 17:46
@程序小白
总结一下,线程三大要素:线程控制块、线程栈、线程入口,当你需要使用线程时候,这仨部分在定义的时候是必须的,但是当你仅仅定义了这三个部分,还不能让线程正常的工作,因为线程的真正的调度工作是在RTT的内核中实现的,内核的实现说实话我也是只知一不知其二,展开讲以我的水准真的讲不明白,前期只考虑应用,所以干脆不讲了,但是你知道她是主导地位就可以了。你定义了线程的三大要素,只是完成了线程的实体,要赋予其灵魂,让其真的开始工作,你还需要把它与rtt关联的在一起,你不需要rtt的内核是如何工作的,rtt给你开放了API函数作为接口,你可以把你定义的线程与RTT建立联系,就可以了。接下来将RTT有关线程的API。
RT-thread学习系列更多精彩内容】PS:点击可直接跳转阅读

               本帖内容】RT-thread学习之Thread线程机制及应用

                RT-thread学习之一直走下去

                RT-thread学习之RTT 的与众不同
                RT-thread学习之object对象管理机制
                RT-thread学习之线程间的同步
                RT-thread学习之设备层框架设计浅析
0
回复