先说点题外话,看了RTT的源代码,其实还挺感慨的,RTT源代码结构清晰,完整的面向对象编程方式,命名规范,实在是非常难得。希望能在之后的发展中继续把关代码风格审核,不要丢掉这个优秀的习惯。
首先还是先了解下rt_thread(基本版)类所包含的成员:
1.与object完全一样的部分,这个地方按道理是可以直接使用struct rt_object parent的,事实上除了rt_thread类以外,其他类也都是这样做的。
2.tlist用于rt_thread的一个双向链表,把每个rt_thread实例通过链表链接起来,方便通过name成员来查找对应的rt_thread.
3.sp一个数据指针,指向线程栈当前使用的地址,线程切换之后,用来保存MCU中寄存器sp中的数据。
4.entry一个函数指针,指向线程的入口函数。
5.parameter一个数据指针,指向传入线程的参数的数据地址。
6.stack_addr一个数据指针,指向为线程分配的栈所有数据的顶部,一旦分配完成在线程删除之前是一个固定值。
7.stack_size保存所分配的栈空间的大小。
8.error保存与线程相关的错误标志。
RTT申明了以下11种错误:
```#define RT_EOK 0 /*< There is no error /
define RT_ERROR 1 /*< A generic error happens /
define RT_EtiMEOUT 2 /*< Timed out / define RT_EFULL 3 /*< The resource is full /
define RT_EEMPTY 4 /*< The resource is empty /
define RT_ENOMEM 5 /*< No memory /
define RT_ENOSYS 6 /*< No system /
define RT_EBUSY 7 /*< Busy /
define RT_EIO 8 /*< IO error /
define RT_EINTR 9 /*< Interrupted system call /
define RT_EINVAL 10 /*< Invalid argument /```
9.stat 保存线程现在处在的状态。
RTT的线程有以下7(6)种状态:
```#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 /
define RT_THREAD_STAT_MASK 0x0f```
10.current_priority,init_priority这两个成员一个用来保存当前优先级,一个用来保存初始化的时候的优先级,当任务被启动的时候current_priority会被重新赋值为init_priority
11.number_mask与线程调度算法有关的一个参数,在调度器板块再来看这个部分。
12.init_tick,remaining_tick这两个成员在有相同优先级的线程同时运行的时候生效,init_tick记录当前线程在有相同优先级任务的时候的时间片长度,如init_tick=20则当运行20个tick后会进行线程切换。
13.thread_timer实例化了一个rt_timer类,该rt_timer主要用来定时唤醒线程,当线程获取s信号量或者互斥锁或邮箱、队列的时候会拥有一个超时时间,此时会配置这个软定时器,时间到后对线程进行唤醒。
14.cleanup一个函数指针,指向一个清理函数,当线程退出的时候,将会执行该函数。
15.user_data用户数据
```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 /
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;
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 */
};```
看完rt_thread包含的成员,挺多的,但每个成员的作用,还算是比较清晰的。那么我们继续来聊thread相关的函数。
主要是以下几个函数,
```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);
rt_err_t rt_thread_detach(rt_thread_t thread);
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_t rt_thread_self(void);
rt_thread_t rt_thread_find(char *name);
rt_err_t rt_thread_startup(rt_thread_t thread);
rt_err_t rt_thread_delete(rt_thread_t thread);
rt_err_t rt_thread_yield(void);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void arg);
rt_err_t rt_thread_suspend(rt_thread_t thread);
rt_err_t rt_thread_resume(rt_thread_t thread);
void rt_thread_timeout(void parameter);```
可以看到作为继承于object的子类,rt_thread类基本上重写了object相关的所有函数,现在来逐一分析每个函数所做的操作:
一、首先是rt_thread_init()以及rt_thread_create()
rt_thread_init()与rt_thread_create()的差异同样是在一个是静态线程的初始化,一个是动态的创建线程,函数内部的差异仅仅是在rt_thread_create()会为创建的线程从堆中申请栈空间并为要实例化的rt_thread申请内存。
这两个函数可以看做C++中的构造函数。
```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)
{
struct rt_thread thread;
void stack_start;
/**申请内存**/
thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread,
name);
if (thread == RT_NULL)
return RT_NULL;
stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
if (stack_start == RT_NULL)
{
/* allocate stack failure */
rt_object_delete((rt_object_t)thread);
return RT_NULL;
}
/*/
/**成员初始化*/
_rt_thread_init(thread,
name,
entry,
parameter,
stack_start,
stack_size,
priority,
tick);
/*/
return thread;
}
RTM_EXPORT(rt_thread_create);```
两个函数最终都在_rt_thread_init()这个私有函数中进行成员初始化。
_rt_thread_init主要执行以下操作:
1.双向链表指针指向自身。
2.赋值入口函数地址。
3.赋值入口函数参数。
4.线程栈相关的初始化:
赋值栈顶地址,赋值栈大小。
将栈空间所有ram设置为’#’(一般是用做检测栈的使用率)。
调用rt_hw_stack_init()对栈顶数据进行初始化(这个设计到线程切换时,保存当前线程的相应PC地址,临时变量,以及MCU寄存器的值等既保存线程的上下文),之后会详细介绍这个函数。
5.初始化优先级。
6.初始化init_tick,remaining_tick (作用上文提过)
7.初始化错误标志。
8.初始化线程状态。
9.初始化clean_up函数和user_data,这里直接初始化为0,默认不使用。
10.初始化定时器,将定时器的time_out回调设置为rt_thread_timeout(),这个函数会将线程从阻塞队列中移出,加入就绪队列,并将error成员置为RT_ETIMEOUT
11.调用rt_thread_inited_hook()钩子函数
```static rt_err_t _rt_thread_init(struct rt_thread *thread,
说到rt_hw_stack_init()函数,首先需要介绍下MCU里的程序的运行规律,在裸机环境下,程序的临时变量,函数的参数,代码运行的地址(PC)统统放在一个叫栈的地方,栈是MCU RAM 里面一段连续的内存空间,栈的大小一般在启动文件或者链接脚本里面指定, 最后由 C 库函数_main 进行初始化。
但是, 在多线程的RTOS中,每个线程都是独立的,互不干扰的,所以要为每个线程都分配独立的栈空间,这个栈空间可以是一个预先定义好的全局数组(静态创建的线程), 也可以是动态分配的一段内存空间(动态创建的线程),但归根结底它们都存在于 MCU的RAM 中。
学过汇编指令的肯定都熟悉,当MCU执行中断或者跳转函数的时候,有一个保护现场的动作,把MCU相应的寄存器数据,SP指针,PC地址PUSH入栈,这些入栈出栈操作有些是MCU自动完成的,有些必须要通过程序要完成。入栈结束后再执行跳转,当中断结束后,要返回当前程序位置的时候再从栈中把数据取出来,重新跳转到之前程序位置,恢复临时变量的值,这就叫恢复现场。
这个两个步骤其实也就是RTOS实现线程切换的最根本的原理。
重新回到rt_hw_stack_init()函数,它其实完成的就是对线程栈的最基础的初始化。
首先在栈顶空出了4个字节,然后进行了8字节对齐操作,然后留出了空间用来存储stack_frame中的内容,stack_frame包含与CPU、FPU相关的一些寄存器(R0-R11,PC,LR等等)。
```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);
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)* = 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 */
if USE_FPU
stack_frame->flag = 0;
endif / USE_FPU /
/* return task's current stack address */
return stk;
}```
二、rt_err_t rt_thread_detach()和rt_thread_delete()
这两个函数都用来删除线程的,区别是第一个是作用到静态创建的线程,第二个的作用是删除动态创建的线程。
1.参数检查。
2.将线程移出调度器队列(线程将不会再被调度)
3.将线程的timer移出timer队列(timer将不会进行计时)
4.设置stat成员为RT_THREAD_CLOSE
5.对于动态创建的线程,直接将线程加入rt_thread_defunct队列,对于静态的线程,只有在有clean_up函数的时候才加入rt_thread_defunct。在idle任务中,会对处在defunct队列中的线程进行释放内存等收尾工作。
```rt_err_t rt_thread_delete(rt_thread_t thread)
{
rt_base_t lock;
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread) == RT_FALSE);
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
/* remove from schedule */
rt_schedule_remove_thread(thread);
}
/* release thread timer */
rt_timer_detach(&(thread->thread_timer));
/* change stat */
thread->stat = RT_THREAD_CLOSE;
/* disable interrupt */
lock = rt_hw_interrupt_disable();
/* insert to defunct thread list */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
/* enable interrupt */
rt_hw_interrupt_enable(lock);
return RT_EOK;
}
rt_err_t rt_thread_detach(rt_thread_t thread)
{
rt_base_t lock;
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
RT_ASSERT(rt_object_is_systemobject((rt_object_t)thread));
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_INIT)
{
/* remove from schedule */
rt_schedule_remove_thread(thread);
}
/* release thread timer */
rt_timer_detach(&(thread->thread_timer));
/* change stat */
thread->stat = RT_THREAD_CLOSE;
/* detach object */
rt_object_detach((rt_object_t)thread);
if (thread->cleanup != RT_NULL)
{
/* disable interrupt */
lock = rt_hw_interrupt_disable();
/* insert to defunct thread list */
rt_list_insert_after(&rt_thread_defunct, &(thread->tlist));
/* enable interrupt */
rt_hw_interrupt_enable(lock);
}
return RT_EOK;
}```
三、rt_err_t rt_thread_startup()线程启动函数
启动函数主要执行了:
1.参数检查
2.将current_priority 重新赋值为init_priority
3.为number_mask赋值,调度器会使用,之后再说
4.将stat置为RT_THREAD_SUSPEND挂起状态
5.调用rt_thread_resume启动线程。
6.判断当前是否有线程在运行(既调度器是否启动),若有则启动一次调度(此时若线程具有最高优先级,则会切换到该线程开始运行)
```rt_err_t rt_thread_startup(rt_thread_t thread)
{
/ thread check /
RT_ASSERT(thread != RT_NULL);
RT_ASSERT((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_INIT);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
/* set current priority to init priority */
thread->current_priority = thread->init_priority;
/* calculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1L << thread->number;
thread->high_mask = 1L << (thread->current_priority & 0x07); /* 3bit */
else
thread->number_mask = 1L << thread->current_priority;
endif
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d
",
thread->name, thread->init_priority));
/* change thread stat */
thread->stat = RT_THREAD_SUSPEND;
/* then resume it */
rt_thread_resume(thread);
if (rt_thread_self() != RT_NULL)
{
/* do a scheduling */
rt_schedule();
}
return RT_EOK;
}```
四、rt_thread_control()函数
该函数根据参数cmd的不同有几种不同的功能,
cmd可取的值为:
RT_THREAD_CTRL_CHANGE_PRIORITY 修改线程的优先级 RT_THREAD_CTRL_STARTUP 启动线程 RT_THREAD_CTRL_CLOSE 关闭删除线程
只有修改优先级是独有的功能
```rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
{
register rt_base_t temp;
/* thread check */
RT_ASSERT(thread != RT_NULL);
RT_ASSERT(rt_object_get_type((rt_object_t)thread) == RT_Object_Class_Thread);
switch (cmd)
{
case RT_THREAD_CTRL_CHANGE_PRIORITY:
/* disable interrupt */
temp = rt_hw_interrupt_disable();
/* for ready thread, change queue */
if ((thread->stat & RT_THREAD_STAT_MASK) == RT_THREAD_READY)
{
/* remove thread from schedule queue first */
rt_schedule_remove_thread(thread);
/* change thread priority */
thread->current_priority = *(rt_uint8_t *)arg;
/* recalculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1 << thread->number;
thread->high_mask = 1 << (thread->current_priority & 0x07); /* 3bit */
else
thread->number_mask = 1 << thread->current_priority;
endif
/* insert thread to schedule queue again */
rt_schedule_insert_thread(thread);
}
else
{
thread->current_priority = *(rt_uint8_t *)arg;
/* recalculate priority attribute */
if RT_THREAD_PRIORITY_MAX > 32
thread->number = thread->current_priority >> 3; /* 5bit */
thread->number_mask = 1 << thread->number;
thread->high_mask = 1 << (thread->current_priority & 0x07); /* 3bit */
else
thread->number_mask = 1 << thread->current_priority;
endif
}
/* enable interrupt */
rt_hw_interrupt_enable(temp);
break;
case RT_THREAD_CTRL_STARTUP:
return rt_thread_startup(thread);
ifdef RT_USING_HEAP
case RT_THREAD_CTRL_CLOSE:
return rt_thread_delete(thread);
endif
default:
break;
}
return RT_EOK;
}```
五、其他函数
(1)rt_thread_t rt_thread_self(void); (2)rt_thread_t rt_thread_find(char *name); (3)rt_err_t rt_thread_yield(void); (4)rt_err_t rt_thread_delay(rt_tick_t tick); (5)rt_err_t rt_thread_mdelay(rt_int32_t ms); (6)rt_err_t rt_thread_suspend(rt_thread_t thread); (7)rt_err_t rt_thread_resume(rt_thread_t thread); (8)void rt_thread_timeout(void *parameter);
(1)获取当前正在运行的线程指针。
(2)通过线程名字获取线程的句柄。
(3)yield这个单词不好翻译,简单的来说这个函数的功能就是进行一次线程切换,把当前线程加入到就绪队列的最末尾,同时执行就绪队列的第一个线程。
(4)(5)延时当前线程,具体方式是将当前线程悬挂,并启动线程的timer,当时间到了之后通过time_out回调唤醒任务。
(6)挂起线程。
挂起线程会将线程移出调度器队列,使得线程无法被调度,将线程状态置为RT_THREAD_SUSPEND,并关闭线程的timer,最后会调用rt_thread_suspend_hook()钩子函数
(7)恢复线程
恢复线程会将线程移出挂起队列(当线程被相应信号挂起的时候,就会被加入到对应信号的挂起队列中,等待信号被释放),重新加入调度器就绪队列。
(8)线程timer的time_out回调。
将线程的error设置为 -RT_ETIMEOUT,并加线程移出当前的队列,重新加入调度器就绪队列,并执行一次调度。
原作者:yushigengyu