先说点题外话,看了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
|