Rt-thread源码剖析(1)——内核对象
前言
该系列基于rtthread-nano的内核源码,来研究RTOS的底层逻辑,本文介绍RTT的内核对象,对于其他RTOS来说也可供参考,万变不离其宗,大家都是互相借鉴,实现不会差太多。
内核对象容器
首先要明确的一点是什么是内核对象,在RTT中,利用结构体之间的嵌套,实现了一种类似面向对象的设计。所以这里的内核对象,是指线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等等任何通过动态/静态创建出来的对象。
其次,需要知道的是,RTT是通过一个rt_object_container来管理这些所有的内核对象的。他是怎么做到的呢?我们来看一下(部分)代码就知道了(也可以直接看下面的总结)
//只展示部分代码
static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] =
{/* initialize object container - thread */{RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)},
#ifdef RT_USING_SEMAPHORE/* initialize object container - semaphore */{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)},
#endif
#ifdef RT_USING_MUTEX/* initialize object container - mutex */{RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex), sizeof(struct rt_mutex)},
#endif};
可以看到,本质上内核容器对象就是一个数组,里面的每个成员都是一个结构体如下所示
/*** The information of the kernel object*/
struct rt_object_information
{enum rt_object_class_type type; /**< object class type */rt_list_t object_list; /**< object list */rt_size_t object_size; /**< object size */
};
rt_list_t又是一个双向链表,因此,rt_object_container其实就是一个rt_object_information结构体构成的数组,这个结构体里面又维护了
1.每种内核对象的类型
2.一个双向链表用于链接每个同类内核对象
3.该内核对象创建时需要的内存大小
程序运行过程中,每个创建的内核对象都会被放在这个内核对象容器中,最终形成如下结构
内核对象创建
不同的内核对象会应用不同的创建函数,例如对于动态创建来说
线程创建 ——> rt_thread_create
定时器创建 ——> rt_timer_create
但只要查看这两个创建函数的源码,都可以看到他们都调用了同一个rt_object_allocate函数,这也是上文提到的,内核对象容器的部分意义所在。它抽象出了所有内核对象的一些共同属性,这些共同属性通过rt_object_allocate函数完成初始化,然后再通过自己的函数完成特有属性的初始化。
本文重点阐述object的创建删除等操作,特定内核对象的创建将在后文表述。
动态创建
RTT在动态创建内核对象时,用到了RT_KERNEL_MALLOC来动态分配内存,这里并不做展开,后续会专门对内存管理进行说明,现在我们看一下动态创建内核对象的源码
rt_object_t rt_object_allocate(enum rt_object_class_type type, const char *name)
{struct rt_object *object;rt_base_t level;struct rt_object_information *information;RT_DEBUG_NOT_IN_INTERRUPT;/* get object information */information = rt_object_get_information(type);RT_ASSERT(information != RT_NULL);object = (struct rt_object *)RT_KERNEL_MALLOC(information->object_size);if (object == RT_NULL){/* no memory can be allocated */return RT_NULL;}/* clean memory data of object */rt_memset(object, 0x0, information->object_size);/* initialize object's parameters *//* set object type */object->type = type;/* set object flag */object->flag = 0;/* copy name */rt_strncpy(object->name, name, RT_NAME_MAX);RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));/* lock interrupt */level = rt_hw_interrupt_disable();{/* insert object into information object list */rt_list_insert_after(&(information->object_list), &(object->list));}/* unlock interrupt */rt_hw_interrupt_enable(level);/* return object */return object;
}
代码做了以下这些工作:
1.通过RT_DEBUG_NOT_IN_INTERRUPT确认当前执行环境不在中断中,中断需要快速完成,动态申请内存显然是不合适的
2.根据不同对象类型动态申请一块内存
3.将该内存清零
4.给内核对象共有的属性:类型,名称,标志位赋值
5.将该内核对象插入内核对象容器
这里做一下展开说明,内核对象的结构体如下所示,他所需的大小也许是64字节:
struct rt_object
{char name[RT_NAME_MAX]; /**< name of kernel object */rt_uint8_t type; /**< type of kernel object */rt_uint8_t flag; /**< flag of kernel object */rt_list_t list; /**< list node of kernel object */
};
而一个具体的内核对象,如timer,他的结构体如下所示,他需要的内存大小远大于64字节
struct rt_timer
{struct rt_object parent; /**< inherit from rt_object */rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL];void (*timeout_func)(void *parameter); /**< timeout function */void *parameter; /**< timeout function's parameter */rt_tick_t init_tick; /**< timer timeout tick */rt_tick_t timeout_tick; /**< timeout tick */
};
在 rt_object_allocate 函数中
object = (struct rt_object *)RT_KERNEL_MALLOC(information->object_size);
申请的内存大小是具体的(如timer)需要的内存空间,然后用object对象先将他接收,把前64个字节进行赋值,其余内存空间进行清零,在rt_timer_create则对这些未0的字节进行赋值。
内核对象创建hook
RT_OBJECT_HOOK_CALL 是 RT-Thread 中用于调用钩子函数的宏定义。创建的这个object对象传递会被传递给钩子函数,接下来简单讲一下如何注册钩子函数
void my_attach_hook(struct rt_object *object)
{ // 用户自定义的处理代码
}
rt_object_attach_sethook(my_attach_hook);
在上述代码中,my_attach_hook 是用户定义的钩子函数,实现了对附加对象事件的处理。通过调用 rt_object_attach_sethook,将该函数注册为对象附加事件的钩子。当系统中有对象被附加时,my_attach_hook 会被自动调用。
静态创建
在内核对象进行静态创建时,内存的分配是在编译时就完成的,因此创建的时间是完全可控的,对于严格的实时操作系统来说,这样的分配方式是更值得推荐的,只是编程上会没有那么舒适我们以timer的静态创建为例
void rt_timer_init(rt_timer_t timer,const char *name,void (*timeout)(void *parameter),void *parameter,rt_tick_t time,rt_uint8_t flag)
{/* parameter check */RT_ASSERT(timer != RT_NULL);RT_ASSERT(timeout != RT_NULL);RT_ASSERT(time < RT_TICK_MAX / 2);/* timer object initialization */rt_object_init(&(timer->parent), RT_Object_Class_Timer, name);_timer_init(timer, timeout, parameter, time, flag);
}
可以看到他在创建时调用了
rt_object_init(&(timer->parent), RT_Object_Class_Timer, name);
任意静态的内核对象在创建时都会调用该函数,可以看到函数的入参是timer->paraent的地址,是在创建时就确定的一块地址空间;object_init的源码如下
void rt_object_init(struct rt_object *object,enum rt_object_class_type type,const char *name)
{rt_base_t level;struct rt_list_node *node = RT_NULL;struct rt_object_information *information;/* get object information */information = rt_object_get_information(type);RT_ASSERT(information != RT_NULL);rt_enter_critical();/* try to find object */for (node = information->object_list.next;node != &(information->object_list);node = node->next){struct rt_object *obj;obj = rt_list_entry(node, struct rt_object, list);if (obj) /* skip warning when disable debug */{RT_ASSERT(obj != object);}}rt_exit_critical();/* set object type to static */object->type = type | RT_Object_Class_Static;/* copy name */rt_strncpy(object->name, name, RT_NAME_MAX);RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));/* lock interrupt */level = rt_hw_interrupt_disable();/* insert object into information object list */rt_list_insert_after(&(information->object_list), &(object->list));/* unlock interrupt */rt_hw_interrupt_enable(level);
}
这里比起动态创建值得说的有如下几点:
1.在 rt_object_init 函数中,系统会遍历对象列表,检查是否存在同名对象。为了防止在遍历过程中其他线程对对象列表进行修改,可能导致数据不一致或系统崩溃,必须在遍历前关闭调度器,进入临界区。遍历完成后,再退出临界区,恢复调度器的正常运行。这种做法确保了对象管理操作的原子性和系统的稳定性
rt_enter_critical();/* try to find object */for (node = information->object_list.next;node != &(information->object_list);node = node->next){struct rt_object *obj;obj = rt_list_entry(node, struct rt_object, list);if (obj) /* skip warning when disable debug */{RT_ASSERT(obj != object);}}rt_exit_critical();
2.在往内核容器中插入内核对象的时候,采取了关闭硬件中断的做法,这里和上面关闭调度器的做法产生了差异,这是因为:
关闭调度器(调度锁):
通过调用 rt_enter_critical() 和 rt_exit_critical() 函数,可以进入和退出调度临界区。在调度锁持有期间,系统停止线程调度,不会发生线程切换,但仍然响应中断。这意味着当前线程独占 CPU 资源,其他线程即使优先级更高也无法抢占执行。这种方法适用于需要防止线程切换的临界区,但对中断的响应不受影响。
关闭中断:
通过调用 rt_hw_interrupt_disable() 和 rt_hw_interrupt_enable() 函数,可以禁用和启用中断。在中断被禁用期间,系统不会响应任何中断,包括时钟中断,从而也不会进行线程调度。这种方法确保了代码执行的原子性,适用于需要防止中断干扰的关键操作,例如修改与中断服务程序共享的数据。
为何在插入内核对象时关闭中断:
在向内核对象列表中插入新对象时,涉及对全局链表的修改。如果在插入过程中发生中断,且中断服务程序也对该链表进行操作,可能导致数据不一致或系统崩溃。因此,需要通过关闭中断来保护这段临界区代码,确保插入操作的原子性。
为何在遍历对象列表时关闭调度器:
在遍历对象列表时,如果其他线程可能对该列表进行修改(例如插入或删除对象),可能导致遍历过程中的数据不一致。通过关闭调度器,可以防止其他线程在遍历期间进行修改操作,确保遍历过程的安全性。由于遍历操作可能耗时较长,关闭中断会影响系统的实时性,因此选择关闭调度器以平衡安全性和实时性。
综上所述,检测内核容器中是否存在同名对象时,仅仅设计对内核容器的遍历查询;但插入时要修改内核容器的链表,修改链表时出现问题,将造成整个系统的崩溃,需要更加严格的保护
内核对象删除
内核对象的删除相对简单,静态删除和动态删除差异不大,仅仅是动态删除时会释放动态创建时malloc出来的内存,而静态没有这个逻辑;其余则仅仅需要将内核对象容器中的链表上将该对象删掉就行了,简单看下源码
动态删除
void rt_object_delete(rt_object_t object)
{rt_base_t level;/* object check */RT_ASSERT(object != RT_NULL);RT_ASSERT(!(object->type & RT_Object_Class_Static));RT_OBJECT_HOOK_CALL(rt_object_detach_hook, (object));/* reset object type */object->type = RT_Object_Class_Null;/* lock interrupt */level = rt_hw_interrupt_disable();/* remove from old list */rt_list_remove(&(object->list));/* unlock interrupt */rt_hw_interrupt_enable(level);/* free the memory of object */RT_KERNEL_FREE(object);
}
静态删除
void rt_object_detach(rt_object_t object)
{rt_base_t level;/* object check */RT_ASSERT(object != RT_NULL);RT_OBJECT_HOOK_CALL(rt_object_detach_hook, (object));/* reset object type */object->type = 0;/* lock interrupt */level = rt_hw_interrupt_disable();/* remove from old list */rt_list_remove(&(object->list));/* unlock interrupt */rt_hw_interrupt_enable(level);
}
相关文章:
Rt-thread源码剖析(1)——内核对象
前言 该系列基于rtthread-nano的内核源码,来研究RTOS的底层逻辑,本文介绍RTT的内核对象,对于其他RTOS来说也可供参考,万变不离其宗,大家都是互相借鉴,实现不会差太多。 内核对象容器 首先要明确的一点是什…...
Python 的历史进程
Python:编程世界中的瑞士军刀 在当今数字化飞速发展的时代,编程语言层出不穷,而 Python 无疑是其中一颗耀眼的明珠。它以其简洁优雅的语法、强大的功能以及广泛的应用领域,成为了全球开发者炙手可热的编程语言之一。 Python 是一种…...
十一、大数据治理平台总体功能架构
大数据治理平台的功能架构图中心主题:数据治理 核心重点是建立健全大数据资产管理框架,确保数据质量、安全性、可访问性和合规性。 大数据治理平台总体功能架构图 关键功能领域 1.数据资产平台(左侧) 此部分主要关注数据资产本身…...
STM32——HAL库开发笔记23(定时器4—输入捕获)(参考来源:b站铁头山羊)
定时器有四个通道,这些通道既可以用来作为输入,又可以作为输出。做输入的时候,可以使用定时器对外部输入的信号的时间参数进行测量;做输出的时候,可以使用定时器向外输出精确定时的方波信号。 一、输入捕获 的基本原理…...
爬虫反爬:CSS位置偏移反爬案例分析与实战案例
文章目录 1. 反爬机制概述2. 反爬原理3. 案例分析4. 破解思路5. 实战样例样例1:使用Python和Selenium破解CSS位置偏移反爬样例2:电商网站商品列表CSS位置偏移反爬破解 6. 总结 1. 反爬机制概述 CSS位置偏移反爬是一种常见的反爬虫技术,通过C…...
libGL.so.1: cannot open shared object file: No such file or directory-linux022
in <module> from PyQt5.QtGui import QPixmap, QFont, QIcon ImportError: libGL.so.1: cannot open shared object file: No such file or directory 这个错误信息表示XXXX 在启动时遇到问题,缺少 libGL.so.1 文件。libGL.so.1 是与 OpenGL 图形库相关的共…...
向量数据库milvus部署
官方文档 Milvus vector database documentationRun Milvus in Docker (Linux) | Milvus DocumentationMilvus vector database documentation 按部署比较简单,这里说一下遇到的问题 一:Docker Compose 方式部署 1、镜像无法拉取,(docker.io被禁) …...
修改`FSL Yocto Project Community BSP`用到的u-boot源码,使其能适配百问网(100ask)的开发板
前言 在博文 https://blog.csdn.net/wenhao_ir/article/details/145547974 中,我们利用官方提供的BSP(FSL Yocto Project Community BSP)构建了写到SD卡中的完整镜像,然后启动后发现存在不少问题,首要的问题就是u-boot不能识别网卡,在这篇博文中,我们就找到FSL Yocto Pro…...
(python)Arrow库使时间处理变得更简单
前言 Arrow库并不是简单的二次开发,而是在datetime的基础上进行了扩展和增强。它通过提供更简洁的API、强大的时区支持、丰富的格式化和解析功能以及人性化的显示,填补了datetime在某些功能上的空白。如果你需要更高效、更人性化的日期时间处理方式,Arrow库是一个不错的选择…...
Pytorch框架06-现有网络模型(修改/使用/保存/加载)
3.9 PyTorch网络模型的修改及使用 PyTorch 提供了多个预训练的网络模型,涵盖了广泛的计算机视觉任务,如图像分类、目标检测、语义分割等。这些预训练模型在 ImageNet 等大型数据集上进行了训练,并可以直接用于迁移学习或微调。 3.9.1 常见的…...
【亲测有效】百度Ueditor富文本编辑器添加插入视频、视频不显示、和插入视频后二次编辑视频标签不显示,显示成img标签,二次保存视频被替换问题,解决方案
【亲测有效】项目使用百度Ueditor富文本编辑器上传视频相关操作问题 1.百度Ueditor富文本编辑器添加插入视频、视频不显示 2.百度Ueditor富文本编辑器插入视频后二次编辑视频标签不显示,在编辑器内显示成img标签,二次保存视频被替换问题 问题1࿱…...
MySQL 使用 `WHERE` 子句时 `COUNT(*)`、`COUNT(1)` 和 `COUNT(column)` 的区别解析
文章目录 1. COUNT() 函数的基本作用2. COUNT(*)、COUNT(1) 和 COUNT(column) 的详细对比2.1 COUNT(*) —— 统计所有符合条件的行2.2 COUNT(1) —— 统计所有符合条件的行2.3 COUNT(column) —— 统计某一列非 NULL 的记录数 3. 性能对比3.1 EXPLAIN 分析 4. 哪种方式更好&…...
laravel11设置中文语言包
安装中文语言包 Laravel 11 默认没有内置完整中文语言包,推荐使用第三方维护的完整翻译: # 通过 Composer 安装语言包 composer require laravel-lang/common --dev# 发布中文语言文件到项目 php artisan lang:add zh_CN这会自动将中文语言文件生成到 l…...
二、IDE集成DeepSeek保姆级教学(使用篇)
各位看官老爷好,如果还没有安装DeepSeek请查阅前一篇 一、IDE集成DeepSeek保姆级教学(安装篇) 一、DeepSeek在CodeGPT中使用教学 1.1、Edit Code 编辑代码 选中代码片段 —> 右键 —> CodeGPT —> Edit Code, 输入自然语言可编辑代码,点击S…...
网络七层模型—OSI参考模型详解
网络七层模型:OSI参考模型详解 引言 在网络通信的世界中,OSI(Open Systems Interconnection)参考模型是一个基础且核心的概念。它由国际标准化组织(ISO)于1984年提出,旨在为不同厂商的设备和应…...
四、Redis主从复制与读写分离
一、环境搭建 准备环境 IP角色192.168.10.101Master192.168.10.102Slave192.168.10.103Slave 创建配置/数据/日志目录 # 创建配置目录 mkdir -p /usr/local/redis/conf # 创建数据目录 mkdir -p /usr/local/redis/data # 创建日志目录 mkdir -p /usr/local/redis/log修改配置…...
爬虫框架与库
爬虫框架与库是用于网络数据抓取的核心工具,帮助开发者高效地从网页中提取结构化数据。 Requests:用于发送HTTP请求。 BeautifulSoup:用于解析HTML和XML。 Scrapy:强大的爬虫框架,适合大规模爬取。 Selenium&#…...
【保姆级视频教程(二)】YOLOv12训练数据集构建:标签格式转换-划分-YAML 配置 避坑指南 | 小白也能轻松玩转目标检测!
【2025全站首发】YOLOv12训练数据集构建:标签格式转换-划分-YAML 配置 避坑指南 | 小白也能轻松玩转目标检测! 文章目录 1. 数据集准备1.1 标签格式转换1.2 数据集划分1.3 yaml配置文件创建 2. 训练验证 1. 数据集准备 示例数据集下载链接:P…...
数据如何安全“过桥”?分类分级与风险评估,守护数据流通安全
信息化高速发展,数据已成为企业的核心资产,驱动着业务决策、创新与市场竞争力。随着数据开发利用不断深入,常态化的数据流通不仅促进了信息的快速传递与共享,还能帮助企业快速响应市场变化,把握商业机遇,实…...
本地大模型编程实战(24)用智能体(Agent)实现智能纠错的SQL数据库问答系统(3)
本文将实现这样一个 智能体(Agent) : 可以使用自然语言对 SQLite 数据库进行查询。即:用户用自然语言提出问题,智能体也用自然语言根据数据库的查询结果回答问题。增加一个自动对查询中的专有名词进行纠错的工具,这将明显提升查询…...
Apache DolphinScheduler系列1-单节点部署及测试报告
文章目录 整体说明一、部署环境二、版本号三、部署方案四、部署步骤4.1、上传部署包4.2、创建外部数据库4.3、修改元数据库配置4.4、上传MySQLl驱动程序4.5、初始化外部数据库4.6、启停服务4.7、访问页面五、常见问题及解决方式5.1、时间不一致5.2、异常终止5.3、大量日志5.4、…...
Java+SpringBoot+Vue+数据可视化的音乐推荐与可视化平台(程序+论文+讲解+安装+调试+售后)
感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,我会一一回复,希望帮助更多的人。 系统介绍 在互联网技术以日新月异之势迅猛发展的浪潮下,5G 通信技术的普及、云计算能力…...
LVS+Keepalived 高可用集群搭建
一、高可用集群: 1.什么是高可用集群: 高可用集群(High Availability Cluster)是以减少服务中断时间为目地的服务器集群技术它通过保护用户的业务程序对外不间断提供的服务,把因软件、硬件、人为造成的故障对业务的影响…...
跟着AI学vue第十二章
第十二章:技术引领与社区共建 在熟练掌握Vue开发技能,并将其与前沿技术融合应用后, 第十二章是一个更具使命感与影响力的阶段,着重于引领技术发展方向和为社区贡献力量。 1. 推动Vue技术创新与实践 探索前沿技术融合࿱…...
PydanticToolsParser 工具(tool call)把 LLM 生成的文本转成结构化的数据(Pydantic 模型)过程中遇到的坑
PydanticToolsParser 的作用 PydanticToolsParser 是一个工具,主要作用是 把 LLM 生成的文本转成结构化的数据(Pydantic 模型),让代码更容易使用这些数据进行自动化处理。 换句话说,AI 生成的文本通常是自然语言&…...
python-leetcode-乘积最大子数组
152. 乘积最大子数组 - 力扣(LeetCode) class Solution:def maxProduct(self, nums: List[int]) -> int:if not nums:return 0max_prod nums[0]min_prod nums[0]result nums[0]for i in range(1, len(nums)):if nums[i] < 0:max_prod, min_prod…...
江协科技/江科大-51单片机入门教程——P[1-1] 课程简介P[1-2] 开发工具介绍及软件安装
本教程也力求在玩好单片机的同时了解一些计算机的基本概念,了解电脑的一些基本操作,了解电路及其元器件的基本理论,为我们学习更高级的单片机,入门IT和信息技术行业,打下一定的基础。 目录 1.课程简介 2.开发工具及…...
简单介绍JVM
1.什么是JVM? JVM就是Java虚拟机【Java Virtual Machine】,简称JVM。主要部分包括类加载子系统,运行时数据区,执行引擎,本地方法库等,接下来我们一一介绍 2.类加载子系统 JVM中运行的就是我们日常写的JA…...
【对话推荐系统】Towards Topic-Guided Conversational Recommender System 论文阅读
Towards Topic-Guided Conversational Recommender System 论文阅读 Abstract1 Introduction2 Related Work2.1 Conversation System2.2 Conversational Recommender System2.3 Dataset for Conversational Recommendation 3 Dataset Construction3.1 Collecting Movies for Re…...
当下弹幕互动游戏源码开发教程及功能逻辑分析
当下很多游戏开发者或者想学习游戏开发的人,想要了解如何制作弹幕互动游戏,比如直播平台上常见的那种,观众通过发送弹幕来影响游戏进程。需要涵盖教程的步骤和功能逻辑的分析。 首先,弹幕互动游戏源码开发教程部分应该分步骤&…...
