RT-Thread源码阅读(一)

前言

本文基于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线程

你可能感兴趣的:(RTOS,STM32,RT-Thread,FreeRTOS)