RT-Thread源码阅读(一)

本文详细介绍了RT-ThreadV4.1.1在STM32F103微控制器上的应用,涉及基本数据结构如双向链表的操作,以及RTOS核心功能如线程管理、调度器初始化和中断处理等内容。
摘要由CSDN通过智能技术生成

前言

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

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我会尽力回答你的问题。首先,RT-Thread是一个实时操作系统,它提供了一些基本的实时操作系统功能,例如线程、信号量、消息队列、定时器等。其中,定时器是一种非常重要的实时操作系统功能,它可以用来在指定的时间间隔内执行一些操作。 在RT-Thread中,定时器的实现是基于软件定时器,即在内核中维护一个定时器列表,每个定时器都有一个超时时间。当定时器到期时,内核会调用定时器回调函数来执行相应的操作。 下面是RT-Thread定时器的源码分析: 1. 定时器结构体 在RT-Thread中,定时器的结构体定义如下: ```c struct rt_timer { char name[RT_NAME_MAX]; /* 定时器名称 */ rt_list_t list; /* 定时器列表 */ rt_tick_t timeout_tick; /* 定时器超时时间 */ rt_tick_t init_tick; /* 定时器初始时间 */ rt_uint8_t flag; /* 定时器标志 */ rt_uint8_t state; /* 定时器状态 */ void (*timeout_func)(void* parameter); /* 定时器回调函数 */ void* parameter; /* 回调函数参数 */ }; ``` 可以看到,定时器结构体包含了定时器的名称、超时时间、标志、状态、回调函数等信息。 2. 定时器创建 在RT-Thread中,定时器的创建函数是rt_timer_create(),它的函数原型如下: ```c rt_err_t rt_timer_create(rt_timer_t *timer, const char *name, void (*timeout_func)(void* parameter), void* parameter, rt_tick_t time, rt_uint8_t flag); ``` 其中,timer表示定时器指针,name表示定时器名称,timeout_func表示定时器回调函数,parameter表示回调函数参数,time表示定时器超时时间,flag表示定时器标志。 rt_timer_create()函数会在内核中创建一个定时器,并将定时器添加到定时器列表中。如果创建成功,函数返回RT_EOK,否则返回错误码。 3. 定时器启动 在RT-Thread中,定时器的启动函数是rt_timer_start(),它的函数原型如下: ```c rt_err_t rt_timer_start(rt_timer_t timer); ``` rt_timer_start()函数会启动指定的定时器,并将其状态设置为RT_TIMER_FLAG_ACTIVATED。如果启动成功,函数返回RT_EOK,否则返回错误码。 4. 定时器停止 在RT-Thread中,定时器的停止函数是rt_timer_stop(),它的函数原型如下: ```c rt_err_t rt_timer_stop(rt_timer_t timer); ``` rt_timer_stop()函数会停止指定的定时器,并将其状态设置为RT_TIMER_FLAG_DEACTIVATED。如果停止成功,函数返回RT_EOK,否则返回错误码。 5. 定时器删除 在RT-Thread中,定时器的删除函数是rt_timer_delete(),它的函数原型如下: ```c rt_err_t rt_timer_delete(rt_timer_t timer); ``` rt_timer_delete()函数会删除指定的定时器,并释放相应的资源。如果删除成功,函数返回RT_EOK,否则返回错误码。 以上就是RT-Thread定时器的源码分析,希望能对你有所帮助。如果你有其他问题,可以继续问我。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值