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线程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/624878.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

编写递归算法,计算二叉树T中叶子结点的数目。

【题目】编写递归算法&#xff0c;计算二叉树T中叶子结点的数目。 二叉链表类型定义∶ typedef struct BiTNode { TElemType data; struct BiTNode *lchild,*rchild; } BiTNode,*BiTree; 要求实现下列函数∶ int Leaves(BiTree T); /* 计算二叉树T中叶子结点的数目*/ #include …

适当催一下没确认订单的国外客户

在一片美丽的森林里&#xff0c;住着两只小鸟。其中一只非常勤奋&#xff0c;每天都早早起床&#xff0c;练习飞翔和觅食。而另外一个小鸟却非常懒惰&#xff0c;每天总是赖在窝里&#xff0c;不愿意努力&#xff0c;懒惰的小鸟总是想&#xff1a;反正有那只勤奋的鸟儿在&#…

【Linux】初识Linux及几个基本指令

Hello everybody!算算时间我已经有一个多月没有更新啦&#xff01;因为本专业是纺织工程&#xff0c;所以一直在复习应付期末考试\(0^◇^0)/。那好&#xff0c;废话不多说。让我们进入今天的主题&#xff01; 关于Linux系统可能很多同学不是很熟悉&#xff0c;有的人可能听过&…

如何在网络爬虫中解决CAPTCHA?使用Python进行网络爬虫

网络爬虫是从网站提取数据的重要方法。然而&#xff0c;在进行网络爬虫时&#xff0c;常常会遇到一个障碍&#xff0c;那就是CAPTCHA&#xff08;全自动公共图灵测试以区分计算机和人类&#xff09;。本文将介绍在网络爬虫中解决CAPTCHA的最佳方法&#xff0c;并重点介绍CapSol…

华为OD机试真题-分配土地-Python-OD统一考试(C卷)

题目描述&#xff1a; 从前有个村庄&#xff0c;村民们喜欢在各种田地上插上小旗子&#xff0c;旗子上标识了各种不同的数字。某天集体村民决定将覆盖相同数字的最小矩阵形的土地的分配给为村里做出巨大贡献的村民&#xff0c;请问&#xff0c;此次分配土地&#xff0c;做出贡献…

软件测试|使用matplotlib绘制多种饼图

简介 Matplotlib是一个强大的数据可视化库&#xff0c;它允许我们创建各种类型的图表&#xff0c;包括饼图。饼图是一种用于显示数据分布的常见图表类型。在本文中&#xff0c;我们将介绍如何使用Matplotlib创建不同类型的饼图&#xff0c;并提供示例代码。 创建标准饼图 首…

maven配置阿里云镜像源

要配置阿里云镜像源&#xff0c;需要在Maven的配置文件中添加以下内容&#xff1a; 打开Maven安装目录下的conf文件夹&#xff0c;找到settings.xml文件。 在settings.xml文件中&#xff0c;找到标签&#xff0c;如果没有则需要手动添加。 在标签中添加以下内容&#xff1a; …

【python】打包exe文件

使用PyInstaller可以将Python脚本打包成可执行的.exe文件。pyinstaller就是一种常用的打包方式&#xff0c;其中参数&#xff1a; --onefile参数表示将所有依赖项和脚本打包成一个单独的可执行文件&#xff0c;方便分发和执行。--noconsole参数表示在运行程序时不显示控制台窗…

Linux进程【2】进程地址空间(+页表详解哦)

fork 引言&#xff08;程序地址空间&#xff09;进程地址空间进程地址空间mm_struct 虚拟地址到物理地址的转化总结 引言&#xff08;程序地址空间&#xff09; 在之前的学习过程中&#xff0c;我们认识了内存与地址&#xff0c;并且了解了在程序地址空间中的基本分区&#xf…

2000年第五次人口普查数据,shp/excel格式均有,划分年龄段、性别占比等字段

基本信息. 数据名称: 第五次人口普查数据 数据格式: Shp、excel 数据时间: 2000年 数据几何类型: 面 数据坐标系: WGS84坐标系 数据来源&#xff1a;第五次人口普查数据 数据字段&#xff1a; 序号字段名称字段说明1a2000_zrks2000年_常住人口&#xff08;人&…

web块级如何居中,关于css/html居中问题

1. text-align&#xff1a;center&#xff1b; 可以实现其内部元素水平居中&#xff0c;通常用于字体水平居中&#xff0c;初学者也可以用于简单块级居中。这种方法对行内元素 (inline)&#xff0c;行内块 (inline-block)&#xff0c;行内表 (inline-table)&#xff0c;inline…

实现一个MYSQL工具类,包含判断创建数据表是否存在,创建数据表

可以使用Python的MySQLdb模块来实现一个MYSQL工具类。下面是一个简单的实现示例&#xff1a; import MySQLdbclass MySQLTool:def __init__(self, host, user, password, database):self.host hostself.user userself.password passwordself.database databasedef connect…

“/bin/bash“: stat /bin/bash: no such file or directory: unknown

简介&#xff1a;常规情况下&#xff0c;在进入容器时习惯使用 /bin/bash为结尾&#xff0c;如&#xff1a;docker exec -it test-sanic /bin/bash&#xff0c; 但是如果容器本身使用了精简版&#xff0c;只装了sh命令&#xff0c;未安装bash。这时就会抛出"/bin/bash&quo…

Eureka 本机集群实现

距离上次发布博客已经一年多了&#xff0c;主要就是因为考研&#xff0c;没时间学习技术的内容&#xff0c;现在有时间继续完成关于代码方面的心得&#xff0c;希望跟大家分享。 今天在做一个 Eureka 的集群实现&#xff0c;我是在本电脑上跑的&#xff0c;感觉这个挺有意思&a…

Linux的网络文件共享服务之FTP服务

一.存储类型 1.1 存储类型分为三种 直连式存储&#xff1a;Direct-Attached Storage&#xff0c;简称DAS 存储区域网络&#xff1a;Storage Area Network&#xff0c;简称SAN&#xff08;可以使用空间&#xff0c;管理也是你来管理&#xff09; 网络附加存储&#xff1a;Net…

Day 48 动态规划 9

198. 打家劫舍1 代码随想录 1. 思路 本体是非常简单的动态规划问题&#xff0c;dp[i]就代表0-i这些家可以抢劫到的最大金额&#xff0c;分两种情况进行讨论。一个是抢当前的不抢之前的&#xff0c;一个是不抢当前的。代码如下&#xff1a; class Solution { public:int rob(v…

Sqoop作业调度:自动化数据传输任务

自动化数据传输任务是大数据处理中的一个重要方面&#xff0c;可以定期执行Sqoop作业&#xff0c;确保数据在不同系统之间的同步。本文将深入探讨如何使用Sqoop作业调度来自动化数据传输任务&#xff0c;并提供详细的示例代码和全面的内容&#xff0c;以帮助大家更好地理解和应…

基于RBF的时间序列预测,基于BP神经网络的时间序列预测

目录 完整代码和数据下载链接:基于RBF的时间序列预测,基于BP神经网络的时间序列预测(代码完整,数据齐全)资源-CSDN文库 https://download.csdn.net/download/abc991835105/88742382 RBF的详细原理 RBF的定义 RBF理论 易错及常见问题 RBF应用实例,基于RBF的时间序列预测,…

二叉树简介

二叉树 二叉树是每个节点最多有两个子树的树结构&#xff0c;通常子树被称作“左子树”和“右子树”。 二叉树的遍历 二叉树的遍历主要有三种方式&#xff1a;前序遍历、中序遍历和后序遍历。 前序遍历&#xff1a;访问根节点 --> 遍历左子树 --> 遍历右子树中序遍历&…

三、MyBatis 多表映射

本章概要 多表映射概念对一映射对多映射多表映射总结 多表映射优化多表映射总结 3.1 多表映射概念 多表查询结果映射思路 开发中有很多** 多表查询**需求&#xff0c;这种情况如何让进行处理&#xff1f; MyBatis 思想是&#xff1a;数据库不可能永远是你所想或所需的那个样…