前言
本文基于RT-Thread V4.1.1和STM32F103(Cortex-M3)
本文旨在理解RT-Thread设计的基本逻辑,为了让文章简短易懂,所以展出的源码都是精简过的,不会把开关中断,宏选择等放在讲解代码中。
可以看懂基本逻辑后查看源码领悟具体细节。
关于RT-Thread的移植可以参考
STM32F103移植RT-Thread完整过程
基本数据结构与操作
双向链表的定义
struct rt_list_node
{struct rt_list_node *next; /**< point to next node. */struct rt_list_node *prev; /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t; /**< Type for lists. */
将节点 n 插入到 l 后面,分4步完成
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{l->next->prev = n;n->next = l->next;l->next = n;n->prev = l;
}
将节点 n 插入到 l 前面,分4步完成
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{l->prev->next = n;n->prev = l->prev;l->prev = n;n->next = l;
}
// 链表初始化 即自己指向自己
rt_inline void rt_list_init(rt_list_t *l)// 判断链表是否为空
rt_inline int rt_list_isempty(const rt_list_t *l)// 获取链表长度
rt_inline unsigned int rt_list_len(const rt_list_t *l)
在RT-Thread中所有对象(线程,信号量等)都会有list元素,如下操作是通过list地址反推对象地址,如下是以rt_thread线程对象为例:
#define rt_list_entry(node, type, member) rt_container_of(node, type, member)
#define rt_container_of(ptr, type, member) ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))
找到当前链表所在结构体的首地址,巧妙的利用&((type *)0)->member算了链表的偏移量,使用示例如下:
struct rt_thread *thread;thread = rt_list_entry(list->next, struct rt_thread, tlist);struct rt_thread
{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 */void *sp; /**< stack point */void *entry; /**< entry */void *parameter; /**< parameter */void *stack_addr; /**< stack address */rt_uint32_t stack_size; /**< stack size */......
}
启动RTOS
在没有OS的工程中,是从main()
中开始运行的
RT-Thread 支持多种平台和多种编译器,而 rtthread_startup()
函数是 RT-Thread 规定的统一启动入口
一般执行顺序是:系统先从启动文件开始运行,然后进入 RT-Thread 的启动函数rtthread_startup()
,最后进入用户入口函数 main()
使用GCC编译时需要修改启动文件
使用MDK时可以不用修改,可以使用$Sub$$main
,如果可以参考博文
int rtthread_startup(void)
{rt_hw_interrupt_disable();/* 板级初始化:需在该函数内部进行系统堆的初始化 */rt_hw_board_init();/* 打印 RT-Thread 版本信息 */rt_show_version();/* 定时器初始化 */rt_system_timer_init();/* 调度器初始化 */rt_system_scheduler_init();/* 由此创建一个用户 main 线程 */rt_application_init();/* 定时器线程初始化 */rt_system_timer_thread_init();/* 空闲线程初始化 */rt_thread_idle_init();/* 启动调度器 */rt_system_scheduler_start();/* 不会执行至此 */return 0;
}
rt_hw_board_init()
用来初始化硬件资源,比如非常重要的systick
中断
rt_show_version()
用来打印版本信息
在rt_system_timer_init()
中主要初始化了_timer_list
static rt_list_t _timer_list[1];
其余函数在后续章节介绍
调度器初始化
与调度相关的有两个非常重要的变量,在rt_system_scheduler_init()
中就是初始化这两个变量
rt_thread_priority_table 是一个ready链表数组,同一优先级的线程放同一链表中
rt_list_t rt_thread_priority_table[RT_THREAD_PRIORITY_MAX];
rt_thread_ready_priority_group 是一个32位整型数,每1位都代表着对应优先级是否有ready的线程,0 优先级代表最高优先级
rt_uint32_t rt_thread_ready_priority_group;
与其相关的操作节选如下:
// 线程启动(UP)或改变优先级的时候赋值
thread->number_mask = 1 << thread->current_priority; // rt_schedule_insert_thread 中调用
rt_thread_ready_priority_group |= thread->number_mask;// rt_schedule_remove_thread中调用
rt_thread_ready_priority_group &= ~thread->number_mask;
顺带介绍一下与优先级相关的函数
_scheduler_get_highest_priority_thread()
获取已经ready的最高优先级线程指针
其中__rt_ffs()
函数用来计算整数中从低位开始的第一个非零位的位置,和内建函数__builtin_ffs()
功能一致
static struct rt_thread* _get_highest_priority_thread(rt_ubase_t *highest_prio)
{register struct rt_thread *highest_priority_thread;register rt_ubase_t highest_ready_priority;highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;/* get highest ready priority thread */highest_priority_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,struct rt_thread,tlist);*highest_prio = highest_ready_priority;return highest_priority_thread;
}
rt_schedule_insert_thread()
将线程插入调度列表
void rt_schedule_insert_thread(struct rt_thread *thread)
{/* READY thread, insert to ready queue */thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);/* insert thread to ready list */rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));rt_thread_ready_priority_group |= thread->number_mask;
}
rt_schedule_remove_thread()
将线程从调度列表中移除
void rt_schedule_remove_thread(struct rt_thread *thread)
{/* remove thread from ready list */rt_list_remove(&(thread->tlist));if (rt_list_isempty(&(rt_thread_priority_table[thread->current_priority]))){// 需要通过rt_list_isempty() 判断同优先级是否有其他已ready线程 没有才清除对应位rt_thread_ready_priority_group &= ~thread->number_mask;}
}
rt_enter_critical() 和 rt_exit_critical()
调度锁,注意和关中断rt_hw_interrupt_disable()区分
void rt_enter_critical(void)
{rt_scheduler_lock_nest ++;
}void rt_exit_critical(void)
{rt_scheduler_lock_nest --;if (rt_scheduler_lock_nest <= 0){rt_scheduler_lock_nest = 0;if (rt_current_thread){/* if scheduler is started, do a schedule */rt_schedule();}}
}
创建用户 main 线程
调用创建线程函数,这里以静态创建为例
void rt_application_init(void)
{rt_thread_t tid;tid = &main_thread;result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);RT_ASSERT(result == RT_EOK);rt_thread_startup(tid);
}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)
{/* initialize thread object */rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name);return _thread_init(thread, name, entry, parameter, stack_start, stack_size, priority, tick);
}
这里先通过rt_object_init
函数给线程类型句柄rt_thread_t tid
初始化rt_object
部分
需要说明是,RT-Thrad中所有对象(线程,信号量等)的结构体开头都包括
rt_object
rt_object
中有对象类型,名称等信息
线程初始化
线程初始化,相关解释见注释
static rt_err_t _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_list_init(&(thread->tlist));// 初始化线程入口和参数thread->entry = (void *)entry;thread->parameter = parameter;// 初始化栈空间大小thread->stack_addr = stack_start;thread->stack_size = stack_size;// 将栈空间全部初始化为'#',后续可以以此来看栈空间最大被使用了多少rt_memset(thread->stack_addr, '#', thread->stack_size);// 栈初始化,后续解释thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)), (void *)_thread_exit);// 优先级初始化thread->current_priority = priority;thread->number_mask = 0;// 分配可运行的时间片thread->init_tick = tick;thread->remaining_tick = tick;/* error and flags */thread->error = RT_EOK;thread->stat = RT_THREAD_INIT;/* initialize cleanup function and user data */thread->cleanup = 0;thread->user_data = 0;// 线程定时器初始化,后续解释rt_timer_init(&(thread->thread_timer), thread->name, _thread_timeout, thread, 0, RT_TIMER_FLAG_ONE_SHOT);// 线程初始化回调函数RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));return RT_EOK;
}
struct exception_stack_frame
{rt_uint32_t r0;rt_uint32_t r1;rt_uint32_t r2;rt_uint32_t r3;rt_uint32_t r12;rt_uint32_t lr;rt_uint32_t pc;rt_uint32_t psr;
};struct stack_frame
{/* r4 ~ r11 register */rt_uint32_t r4;rt_uint32_t r5;rt_uint32_t r6;rt_uint32_t r7;rt_uint32_t r8;rt_uint32_t r9;rt_uint32_t r10;rt_uint32_t r11;struct exception_stack_frame exception_stack_frame;
};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)[i] = 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 *//* return task's current stack address */return stk;
}
第一眼看这个代码,可能会有一个疑问,入参stack_addr
的入参 - sizeof(rt_ubase_t)
函数开始又 + sizeof(rt_uint32_t)
,加4减4既不是多此一举?
向下增长的栈 - sizeof(rt_ubase_t)
是对应着当前栈顶,例如栈空间buff[100],不减的话栈指向buff[100],访问就会溢出
thread.c
作为内核文件,向上/下增长的栈入参都为栈顶/底位置,没毛病
对于cpuport.c
不同的单片机会有不同的内容,架构可能不一样,主要区别如下:
-
当SP指针指向的地址空间没有存放有效数据,则称之为空堆栈
-
当SP指针指向的地址空间存放有有效数据,则称之为满堆栈
因此针对满堆栈,写入数据的流程为先移动SP指针再填写有效数据;而对于空堆栈则是先填写有效数据再移动堆栈指针
由满堆栈、空堆栈与向上增长堆栈、向下增长堆栈,共可组成四种组合:
- 向上递增满堆栈(满增)
- 向下递增满堆栈(满减)
- 向上递增空堆栈(空增)
- 向下递增空堆栈(空简)
The stack must also conform to the following constraint at a public interface:
• SP mod 8 = 0. The stack must be double-word aligned.
Cortex-M是满减堆栈,AAPCS中还要求栈作为调用入口时保持8字节对齐
所以不难理解如下内容,当然8字节对齐可能造成4字节空间浪费
stk = stack_addr + sizeof(rt_uint32_t);
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
stk -= sizeof(struct stack_frame);
观察后续代码可知,R4-R11在地址小的空间,也会被先出栈,此时返回的栈指针stk指向的就是R4,返回给thread->sp
这里有几个寄存器非常重要
- r0:第一个参数,即
thread->parameter
- pc:函数入口,即
thread->entry
- lr:线程return后的入口,即
_thread_exit
,用来回收资源
加入调度
rt_err_t rt_thread_startup(rt_thread_t thread)
{thread->number_mask = 1L << thread->current_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_err_t rt_thread_resume(rt_thread_t thread)
{rt_base_t level;if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_SUSPEND){return -RT_ERROR;}// 如果在suspend列表中则移除rt_list_remove(&(thread->tlist));// 定时器相关,后续介绍rt_timer_stop(&thread->thread_timer);// 加入调度列表rt_schedule_insert_thread(thread);RT_OBJECT_HOOK_CALL(rt_thread_resume_hook, (thread));return RT_EOK;
}void rt_schedule_insert_thread(struct rt_thread *thread)
{rt_base_t level;/* it's current thread, it should be RUNNING thread */if (thread == rt_current_thread){thread->stat = RT_THREAD_RUNNING | (thread->stat & ~RT_THREAD_STAT_MASK);goto __exit;}/* READY thread, insert to ready queue */thread->stat = RT_THREAD_READY | (thread->stat & ~RT_THREAD_STAT_MASK);// 加入到调度链表/* there is no time slices left(YIELD), inserting thread before ready list*/if((thread->stat & RT_THREAD_STAT_YIELD_MASK) != 0){rt_list_insert_before(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}/* there are some time slices left, inserting thread after ready list to schedule it firstly at next time*/else{rt_list_insert_after(&(rt_thread_priority_table[thread->current_priority]),&(thread->tlist));}// 说明对应的优先级有线程rt_thread_ready_priority_group |= thread->number_mask;
}
rt_thread_idle_init();
同理,后续详细介绍IDLE线程