本文基于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 */
......
}
在没有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;
顺带介绍一下与优先级相关的函数
获取已经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;
}
将线程插入调度列表
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;
}
将线程从调度列表中移除
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_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();
}
}
}
调用创建线程函数,这里以静态创建为例
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
这里有几个寄存器非常重要
thread->parameter
thread->entry
_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线程