当前位置: 首页 > news >正文

【RT Thread】RTT内核对象机制详解

RT Thread的内核对象机制

在开篇,请允许我说一句:YES ! Object-Oriented Programming !

开宗明义,RTT的源码非常的“面向对象”,尽管它是用C写的,但是在具体设计上十分的OOP

RTT面向对象示例

先看一段代码

struct rt_object
{
#if RT_NAME_MAX > 0char        name[RT_NAME_MAX];                       /**< dynamic name of kernel object */
#elseconst char *name;                                    /**< static name of kernel object */
#endif /* RT_NAME_MAX > 0 */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 */
};
typedef struct rt_object *rt_object_t;                   /**< Type for kernel objects. */struct rt_ipc_object
{struct rt_object parent;                            /**< inherit from rt_object */rt_list_t suspend_thread;                 /**< threads pended on this resource */
};struct rt_mutex
{struct rt_ipc_object parent;                        /**< inherit from ipc_object */...struct rt_thread    *owner;                         /**< current owner of mutex */rt_list_t            taken_list;                    /**< the object list taken by thread */struct rt_spinlock   spinlock;
};
typedef struct rt_mutex *rt_mutex_t;
#endif /* RT_USING_MUTEX */

可以看到,rt thread中的mutex中有一个rt_ipc_object成员变量,而rt_ipc_object中又有rt_object 类型的成员变量。这比较类似OOP的继承的机制,rt_mutex继承了ipc_object的特性,rt_ipc_object 又继承了rt_object,这个rt_object就是RTT的内核对象。类似于Java里的根类Object类一样,绝大多数rtt的内核对象,包括Mutex,线程,事件,Mailbox等,都会包含一个rt_object根对象。接下来我们来拆分一下rt_object结构体

rt_object

struct rt_object
{
#if RT_NAME_MAX > 0char        name[RT_NAME_MAX];                       /**< dynamic name of kernel object */
#elseconst char *name;                                    /**< static name of kernel object */
#endif /* RT_NAME_MAX > 0 */rt_uint8_t  type;                                    /**< type of kernel object */rt_uint8_t  flag;                                    /**< flag of kernel object */#ifdef RT_USING_MODULEvoid      * module_id;                               /**< id of application module */
#endif /* RT_USING_MODULE */#ifdef RT_USING_SMARTrt_atomic_t lwp_ref_count;                           /**< ref count for lwp */
#endif /* RT_USING_SMART */rt_list_t   list;                                    /**< list node of kernel object */
};
typedef struct rt_object *rt_object_t;                   /**< Type for kernel objects. */
  1. name:根据RT_NAME_MAX宏的值选择不同的名称存储方式,可以指向常量名字也可以使用字符数组命名
  2. type:rt_uint8_t类型,标识内核对象的具体类型(如线程、信号量、互斥量等),见下文
  3. flag:rt_uint8_t类型,存储对象的配置标志和状态信息
  4. module_id:模块隔离,支持多模块环境下的对象管理,主要应用场景是在支持动态加载模块的系统中标识对象所属模块
  5. rt_list_t类型的双向链表节点,用于将同类对象链接到全局管理链表中,见下文

其中,RT_USING_MODULE宏可以选择性打开,可以避免为未启用功能分配不必要的内存,通过配置选项灵活定制内核功能并且适应不同硬件平台的资源约束

RT Thread将内核对象分为如下几类,这个是用于rt_object.type中标识内核对象的enum:

enum rt_object_class_type
{RT_Object_Class_Null          = 0x00,      /**< The object is not used. */RT_Object_Class_Thread        = 0x01,      /**< The object is a thread. */RT_Object_Class_Semaphore     = 0x02,      /**< The object is a semaphore. */RT_Object_Class_Mutex         = 0x03,      /**< The object is a mutex. */RT_Object_Class_Event         = 0x04,      /**< The object is a event. */RT_Object_Class_MailBox       = 0x05,      /**< The object is a mail box. */RT_Object_Class_MessageQueue  = 0x06,      /**< The object is a message queue. */RT_Object_Class_MemHeap       = 0x07,      /**< The object is a memory heap. */RT_Object_Class_MemPool       = 0x08,      /**< The object is a memory pool. */RT_Object_Class_Device        = 0x09,      /**< The object is a device. */RT_Object_Class_Timer         = 0x0a,      /**< The object is a timer. */RT_Object_Class_Module        = 0x0b,      /**< The object is a module. */RT_Object_Class_Memory        = 0x0c,      /**< The object is a memory. */RT_Object_Class_Channel       = 0x0d,      /**< The object is a channel */RT_Object_Class_ProcessGroup  = 0x0e,      /**< The object is a process group */RT_Object_Class_Session       = 0x0f,      /**< The object is a session */RT_Object_Class_Custom        = 0x10,      /**< The object is a custom object */RT_Object_Class_Unknown       = 0x11,      /**< The object is unknown. */RT_Object_Class_Static        = 0x80       /**< The object is a static object. */
};

RT-Thread通过一个精心设计的分类管理系统来管理所有内核对象,主要基于 object.c 文件中的实现。使用静态的_object_container全局数组来管理全局内核对象链表,数组里每一种内核类型(比如RT_Object_Class_Thread)对应一个rt_object_information,rt_object_information中包含内核类型信息,存储所有该类型内核对象的obejct_list和一个用于互斥访问的spinlock锁,这这里只节选部分。

/*** 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 */struct rt_spinlock        spinlock;
};static struct rt_object_information _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), RT_SPINLOCK_INIT},
#ifdef RT_USING_SEMAPHORE/* initialize object container - semaphore */{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore), RT_SPINLOCK_INIT},
#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), RT_SPINLOCK_INIT},
#endif
...
...
...
#ifdef RT_USING_HEAP{RT_Object_Class_Custom, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Custom), sizeof(struct rt_custom_object), RT_SPINLOCK_INIT},
#endif
};

那么既然是一个静态的全局数组,也就是所有的线程,进程访问都是这个全局数组,在没有启用MMU的时候,也就是不使用虚拟内存的时候,大家直接访问_object_container所在的数据段.data的物理地址。在打开了MMU的情况下,进程有自己的虚拟地址空间,如何确保用户进程和内核进程都访问同一个_object_container呢?很显然_object_container位于内核虚拟地址空间内核虚拟地址空间的核心特性是 “全局共享且映射一致”—— 所有线程(包括内核线程和用户线程)的页表中,内核虚拟地址到物理地址的映射是完全相同的。对于内核线程而言,这意味着:

  1. 它访问的内核虚拟地址(如存放内核对象的全局静态数组地址 0xFFFF0000),会被 MMU 映射到固定的物理内存(如内核数据段所在的物理页);
  2. 其他内核线程访问同一个虚拟地址 0xFFFF0000 时,MMU 会映射到同一块物理内存,因此所有内核线程对内核数据的操作是全局可见、协同一致的。

如何操作全局内核对象

那么在访问的时候如何保证互斥访问的同时提高并发性,减少占用这个全局临界资源的时间就很关键了,让我们来分析下rtt访问内核对象全局数组的逻辑。

获取内核对象链表:rt_object_get_information()

首先,根据内核对象类型type获取对应的rt_object_information 结构体指针,该结构体中有对应类型的链表。

struct rt_object_information *
rt_object_get_information(enum rt_object_class_type type)
{int index;type = (enum rt_object_class_type)(type & ~RT_Object_Class_Static);for (index = 0; index < RT_Object_Info_Unknown; index ++)if (_object_container[index].type == type) return &_object_container[index];return RT_NULL;
}

初始化内核对象:rt_object_allocate()

然后就是声明内核对象并且加入到对应的全局内核对象链表中。RTT给了两套方法:一个是使用rt_object_allocate声明动态内核对象,这个内核对象会开辟在内核空间的堆上,另外一个使用rt_object_init的则是声明静态内核变量,这些内核变量在编译的时候就已经完成初始化,静态对象适用于那些在系统整个生命周期都需要存在的内核对象,如系统线程、设备对象等。接下来只演示rt_object_allocate,该函数给出内核对象名字和类型,RTT负责将他动态初始化并且插入对应的队列中

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;
#ifdef RT_USING_MODULEstruct rt_dlmodule *module = dlmodule_self();
#endif /* RT_USING_MODULE */RT_DEBUG_NOT_IN_INTERRUPT;//确保不在中断上下文中调用此函数/* 通过类型获取对象容器信息,并进行空指针断言 */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;}/* 清除原值并初始化type和flag */rt_memset(object, 0x0, information->object_size);/* initialize object's parameters */object->type = type;/* set object flag */object->flag = 0;#if RT_NAME_MAX > 0rt_strncpy(object->name, name, RT_NAME_MAX - 1); /* copy name */
#elseobject->name = name;
#endif /* RT_NAME_MAX > 0 */RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));level = rt_spin_lock_irqsave(&(information->spinlock));#ifdef RT_USING_MODULEif (module){rt_list_insert_after(&(module->object_list), &(object->list));object->module_id = (void *)module;}else
#endif /* RT_USING_MODULE */{/* insert object into information object list */rt_list_insert_after(&(information->object_list), &(object->list));}rt_spin_unlock_irqrestore(&(information->spinlock), level);return object;
}

其实关键的还是下面spin_lock操作的那一段:
rt_base_t rt_spin_lock_irqsave(&(information->spinlock))rt_spin_unlock_irqrestore(&(information->spinlock), level)

功能:获取自旋锁并保存当前中断状态,和普通的rt_spin_lock的上锁函数,不一样适用于 线程上下文与中断上下文可能同时竞争资源 的场景(即中断服务程序也需要访问该共享资源)。例如:线程 A 和 UART 中断服务程序都需要操作同一个数据队列,此时必须用 rt_spin_lock_irqsave在获取锁时关闭中断,确保线程持有锁期间不会被中断打断,避免 ISR 介入竞争;释放锁时恢复中断状态,保证系统中断响应的正常性。通过关闭中断,彻底避免了 “线程持有锁时被中断,ISR 自旋等待锁” 导致的死锁。

在使用rt_spin_lock_irqsave的时候,会返回保存的中断状态标志 level,比如原来是关中断的,那么在spinlock上锁关中断后,解锁时还需要维持关中断的状态;如果是开中断的,那么spinlock解锁的时候也需要从关中断恢复到原先的开中断。这是为了精确恢确保中断状态恢复到调用前的确切状态,并且支持函数嵌套调用。

可以看到,每个内核对象对应的链表都会有一个spinlock用于控制对链表的互斥访问。

通过name查找内核对象

既然所有的内核对象都是在静态的内核对象表中的,那么查找函数也会相当的通用——无论是mutex还是sem,是Memery对象还是Thread,他们的查找函数最终都是通过rt_object_find这个函数去在静态的内核对象表中查找。下面省去一些类型检查的代码

rt_object_t rt_object_find(const char *name, rt_uint8_t type)
{struct _obj_find_param param ={.match_name = name,.matched_obj = RT_NULL,};...rt_object_for_each(type, _match_name, &param);return param.matched_obj;
}rt_err_t rt_object_for_each(rt_uint8_t type, rt_object_iter_t iter, void *data)
{struct rt_object *object = RT_NULL;struct rt_list_node *node = RT_NULL;struct rt_object_information *information = RT_NULL;rt_base_t level;rt_err_t error;information = rt_object_get_information((enum rt_object_class_type)type);/* parameter check */if (information == RT_NULL){return -RT_EINVAL;}/* which is invoke in interrupt status */RT_DEBUG_NOT_IN_INTERRUPT;/* enter critical */level = rt_spin_lock_irqsave(&(information->spinlock));/* try to find object */rt_list_for_each(node, &(information->object_list)){object = rt_list_entry(node, struct rt_object, list);if ((error = iter(object, data)) != RT_EOK){rt_spin_unlock_irqrestore(&(information->spinlock), level);return error >= 0 ? RT_EOK : error;}}rt_spin_unlock_irqrestore(&(information->spinlock), level);return RT_EOK;
}

和上面的一样,也是在操作object_list的时候加上spin_lock_irqsave函数保护object_list互斥访问。

http://www.dtcms.com/a/394571.html

相关文章:

  • Seata分布式事务
  • 用例图讲解
  • makefile原理
  • AUTOSAR CP开发流程总结
  • 通过VNC实现树莓派远程桌面访问
  • linux信号done
  • BeanUtils.copyProperties 映射规则详解
  • 物联网 frid卡控制
  • LeetCode刷题记录----322.零钱兑换(Medium)
  • 2015/07 JLPT听力原文 问题四
  • Redis集群实验
  • 昇腾生态双支柱:MindSpore 与 CANN 的全栈技术解析
  • YOLO系列——实时屏幕检测
  • 牛客算法基础noob49 上三角矩阵判定
  • autosar 中OS模块理解
  • 通俗范畴论17.2 向量空间的对偶与双对偶
  • huggingface_hub 安装部署问题汇总
  • 在我的Java项目中为什么使用AllArgsConstructor注解注入的方式启动报错了:
  • π0:一个 VLA 流匹配模型用于通用机器人控制(又称 pi0)
  • Information theorem-Entropy
  • 编译原理实验报告——词法分析程序
  • 整体设计 完整的逻辑链条 之4 认知逻辑视角 —— 前序驱动的认知演进体系 之2
  • C/C++正则表达式PCRE2库
  • 基于python大数据的声乐信息分类评测系统
  • 永磁同步电机无速度算法--改进型超螺旋滑模观测器
  • Linux0.12的中断处理过程源码分析
  • 进程控制(Linux)
  • 【C++】——string类的使用(详细讲解)
  • 借助 Amazon ECS 全新的内置蓝绿部署功能,加速安全的软件发布进程
  • 【脑电分析系列】第24篇:运动想象BCI系统构建:CSP+LDA/SVM与深度学习方法的对比研究