RT-Thread源码阅读(1)——基本框架
前言
本文基于RT-Thread V4.1.1和STM32F103(Cortex-M3)
编译环境为STM32CubeIDE(GCC)
本文旨在理解RT-Thread设计的基本逻辑,为了让文章简短易懂,所以展出的源码都是精简过的,一些好理解的开关中断,宏代码等会省略掉
可以看懂基本逻辑后查看源码领悟具体细节
关于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)
{l->next = l->prev = l;
}// 判断链表是否为空
rt_inline int rt_list_isempty(const rt_list_t *l)
{return l->next == l;
}// 获取链表长度
rt_inline unsigned int rt_list_len(const rt_list_t *l)
{unsigned int len = 0;const rt_list_t *p = l;while (p->next != l){p = p->next;len ++;}return len;
}
通过元素找到内核对象地址
在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_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;}
}
创建用户 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
中有对象类型,名称等信息
开关中断简述
开关中断函数总是成对的出现,rt_hw_interrupt_disable在关中断的同时,会返回关之前的中断状态
所以rt_hw_interrupt_enable并不一定是真的开中断了,只有最外层的rt_hw_interrupt_enable才会真的开中断
rt_base_t rt_hw_interrupt_disable(void);
void rt_hw_interrupt_enable(rt_base_t level);
.global rt_hw_interrupt_disable.type rt_hw_interrupt_disable, %function
rt_hw_interrupt_disable:MRS R0, PRIMASKCPSID IBX LR.global rt_hw_interrupt_enable.type rt_hw_interrupt_enable, %function
rt_hw_interrupt_enable:MSR PRIMASK, R0BX LR