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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

html css js网页制作成品——HTML+CSS甜品店网页设计(5页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…

Trae根据原型设计稿生成微信小程序密码输入框的踩坑记录

一、需求描述 最近经常使用Trae生成一些小组件和功能代码&#xff08;对Trae赶兴趣的可以看之前的文章《TraeAi上手体验》&#xff09;&#xff0c;刚好在用uniapp开发微信小程序时需要开发一个输入密码的弹框组件&#xff0c;于是想用Trae来实现。原型设计稿如下&#xff1a;…

斩波放大器

目录 简介 自稳零斩波放大器 噪声 简介 双极性放大器的失调电压为25 μV&#xff0c;漂移为0.1 μV/C。斩波放大器尽管存在一些不利影 响&#xff0c;但可提供低于5 μV的失调电压&#xff0c;而且不会出现明显的失调漂移&#xff0c; 以下图1给出了基本的斩波放大器电路图。…

windows设置暂停更新时长

windows设置暂停更新时长 win11与win10修改注册表操作一致 &#xff0c;系统界面不同 1.打开注册表 2.在以下路径 \HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键新建 DWORD 32位值&#xff0c;名称为FlightSettingsMaxPauseDays 根据需求填写数…

DIALOGPT:大规模生成式预训练用于对话响应生成

摘要 我们提出了一个大规模、可调节的神经对话响应生成模型&#xff0c;DIALOGPT&#xff08;对话生成预训练变换器&#xff09;。该模型训练于从2005年至2017年间Reddit评论链中提取的1.47亿次类似对话的交流&#xff0c;DIALOGPT扩展了Hugging Face的PyTorch变换器&#xff…

Mac端不显示正常用户名,变成192的解决方法

今天打开终端&#xff0c;本应该显示机器名的&#xff0c;但是此时显示了192。 问题原因&#xff1a; 当路由器的DNS使用默认的 192.168.1.1 或 192.168.0.1 的时候 Terminal 里的计算机名 会变成 localhost。当路由器的DNS使用自定义的 例如 运营商的DNS 或者 公共DNS的时候 …

SD 卡无屏安装启动树莓派5

最近想用一下树莓派5&#xff0c;拿出来一看&#xff0c;是 Micro-HMDI 的接口&#xff0c;手头正好没有这个接口线&#xff0c;便研究如何在没有显示屏的情况下&#xff0c;安装启动树莓派。 一、使用 Raspberry Pi Imager 烧录 SD 卡 选择 Raspberry Pi Imager 来烧录 SD 卡…

Xlua 编译 Windows、UWP、Android、iOS 平台支持库

Xlua 编译 Windows、UWP、Android、iOS 平台支持库 Windows&#xff1a; 安装 Visual Studio&#xff08;推荐 2017 或更高版本&#xff09; 安装 CMake&#xff08;https://cmake.org/&#xff09; macOS&#xff1a; 安装 Xcode 和命令行工具 安装 CMake 检查 cmake 是否安…

npm : 无法加载文件 E:\ProgramFiles\Nodejs\npm.ps1,因为在此系统上禁止运行脚本。

这个错误是因为 Windows 系统的 PowerShell 执行策略 限制了脚本的运行。默认情况下&#xff0c;PowerShell 的执行策略是 Restricted&#xff0c;即禁止运行任何脚本。以下是解决该问题的步骤&#xff1a; 1. 检查当前执行策略 打开 PowerShell&#xff08;管理员权限&#x…

基于专利合作地址匹配的数据构建区域协同矩阵

文章目录 地区地址提取完成的处理代码 在专利合作申请表中&#xff0c;有多家公司合作申请。在专利权人地址中&#xff0c; 有多个公司的地址信息。故想利用这里多个地址。想用这里的地址来代表区域之间的专利合作情况代表区域之间的协同、协作情况。 下图是专利合作表的一部分…

若依vue plus环境搭建

继前面文章若依系统环境搭建记录-CSDN博客 把ruoyi vue plus也摸索了下。 作者是疯狂的狮子&#xff0c;dromara/RuoYi-Vue-Plus 初始化文档&#xff1a;项目初始化&#xff0c;环境搭建的视频&#xff1a;RuoYi-Vue-Plus 5.0 搭建与运行_哔哩哔哩_bilibili 上来就列出了一…

在ubuntu如何安装samba软件?

我们在开发过程中&#xff0c;经常修改代码&#xff0c;可以安装samba文件来实现&#xff0c;把ubuntu的存储空间指定为我们win上的一个磁盘&#xff0c;然后我们在或者磁盘里面创建.c文件&#xff0c;进行代码修改和编写。samba能将linux的文件目录直接映射到windows&#xff…

论文阅读笔记:Deep Face Recognition: A Survey

论文阅读笔记&#xff1a;Deep Face Recognition: A Survey 1 介绍2 总览2.1 人脸识别组件2.1.1 人脸处理2.1.2 深度特征提取2.1.3 基于深度特征的人脸对比 3 网络结构和损失函数3.1 判别损失函数的演化3.1.1 基于欧式距离的损失3.1.2 基于角度/余弦边距的损失3.1.3 Softmax损失…

使用 Polars 进行人工智能医疗数据分析(ICU数据基本测试篇)

引言 在医疗领域&#xff0c;数据就是生命的密码&#xff0c;每一个数据点都可能蕴含着拯救生命的关键信息。特别是在 ICU 这样的重症监护场景中&#xff0c;医生需要实时、准确地了解患者的病情变化&#xff0c;以便做出及时有效的治疗决策。而随着医疗技术的飞速发展&#x…

Fiddler在Windows下抓包Https

文章目录 1.Fiddler Classic 配置2.配置浏览器代理自动代理手动配置浏览器代理 3.抓取移动端 HTTPS 流量&#xff08;可选&#xff09;解决抓取 HTTPS 失败问题1.Fiddler证书过期了 默认情况下&#xff0c;Fiddler 无法直接解密 HTTPS 流量。需要开启 HTTPS 解密&#xff1a; 1…

Anaconda安装 超详细版 (2025版)

目录 第一步&#xff1a;下载anaconda安装包 官网下载&#xff1a;Anaconda | Built to Advance Open Source AI 清华大学镜像站下载&#xff08;速度较快&#xff09; 第二步&#xff1a;安装anaconda 第三步&#xff1a;验证安装 扩展 创建conda基本环境 激活conda环…

想知道两轮差速方形底盘 URDF 咋做,ROS2 配 Rviz 咋显示吗?看这里!

视频讲解 想知道两轮差速方形底盘 URDF 咋做&#xff0c;ROS2 配 Rviz 咋显示吗&#xff1f;看这里&#xff01; 模型概述 一个方形底盘和两个差速驱动轮 URDF 代码 <?xml version"1.0" encoding"utf-8"?> <robot name"diff"> …

轻量化网络设计|ShuffleNet:深度学习中的轻量化革命

一、引言 在深度学习中&#xff0c;卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;无疑是大家最耳熟能详的算法之一。自诞生以来&#xff0c;CNN 在图像分类、目标检测、语义分割等众多计算机视觉任务中取得了令人瞩目的成就&#xff0c;…

最好Wordpree+Apache+PHP安装教程

前提需要 PHP的安装最少需要7.4以上Mysql的安装&#xff0c;直接默认最新版就行APache服务器&#xff08;HTTP服务器&#xff0c;只有用这个你的软件才能在服务器上运行&#xff09; 安装apache 安装 sudo apt install apache2查看防火墙 sudo ufw app list如果有 Apache那…

Linux实操——在服务器上直接从百度网盘下载(/上传)文件

Linux Linux实操——在服务器上直接从百度网盘下载&#xff08;/上传&#xff09;文件 文章目录 Linux前言一、下载并安装bypy工具二、认证并授权网盘账号三、将所需文件转移至目的文件夹下四、下载文件五、上传文件六、更换绑定的百度云盘账户 前言 最近收到一批很大的数据&…