|
[导读] 前面写了些文章分享C语言面向对象设计的一些个人体会,个人认为RT-Thread内核对于面向对象实现思想是一个非常好的设计。向这些在基础软件上深耕的国人大牛们致敬。本文基于学习RT-Thread内核设计的初衷,来分享一下个人对于其内核对象子系统设计的理解与体会。在此,也给各位RT-Thread原创大牛们打call,分享本文也期望有更多的盆友去学习并使用RT_Thread。 ( ~' F1 Y! [' ~) c RT-Tread内核架构 RT-Thread,全称是 Real Time-Thread,顾名思义,它是一个嵌入式实时多线程操作系统,基本属性之一是支持多任务,允许多个任务同时运行并不意味着处理器在同一时刻真地执行了多个任务。其内核架构如下图所示:
RT-Thread 内核及底层结构 对于各部分的功能,这里不做展开描述。RT-Tread内核吸引我的方面:
所以如果是RTOS应用或者开发从业者,面对这么优秀且比较容易深入学习的内核,如果不去好好读读,实在有点可惜。要去体会RT-Thread对象设计思想,从其对内核对象object的管理入手,不失为一个非常好的切入点。 什么是RT-Thread内核对象管理?RT-Thread 采用内核对象管理系统来访问 / 管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象既可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。通过这种内核对象的设计方式,RT-Thread 做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。 RT-Thread 内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。对象容器中包含了每类内核对象的信息,包括对象类型,大小等。对象容器给每类内核对象分配了一个链表,所有的内核对象都被链接到该链表上,如图 RT-Thread 的内核对象容器及链表如下图所示:
RT-Thread 的内核对象容器及链表 参考自:https://www.rt-thread.org/docume ... ual/basic/basic/#_7 这个集中管理的内核对象容器在内存的开销方面代价很小,但却具有高度的灵活性,从设计的角度看其代码也非常利于扩展,增加新的内核对象类别,以及对于相应的内核对象功能的裁剪适配。 内核对象主要干什么?RT-Thread内核对象子系统其主体实现代码为object.c,本文尝试从整体到局部来尝试解读其设计思想。object.c这个子系统从外部以黑盒的角度看,就个人理解主要实现了这样些用例需求:
所以个人理解内核对象管理器,主要是为其他内核功能模块提供数据管理支撑,属于内核底层支持功能组件,并从设计上兼顾了可扩展、可裁剪的需求。0 b: ]6 Q5 R& l9 ~% G, ~: p& t, W RT-Thread内核对象子系统其主要核心数据结构如下:
其中rt_object_class_type枚举定义内核对象类别: enum rt_object_class_type3 }/ P3 e% ^" L9 y; k. g{/ n/ v5 i/ G4 x- {; W RT_Object_Class_Null = 0, /* 未使用 */ RT_Object_Class_Thread, /* thread对象 */% G2 D- N# D3 S3 ` RT_Object_Class_Semaphore, /* semaphore对象 */ RT_Object_Class_Mutex, /* mutex对象 */) U0 m! i9 L: B RT_Object_Class_Event, /* event对象 */ RT_Object_Class_MailBox, /* mail box对象 */. ?, m7 U# A$ H0 [' U% C3 O RT_Object_Class_MessageQueue, /* message queue */1 J- e3 t# {6 v1 J7 T6 ?5 B* _ RT_Object_Class_MemHeap, /* memory heap */ RT_Object_Class_MemPool, /* memory pool */. G: s/ v* l P Y8 O$ `; X R RT_Object_Class_Device, /* device对象 */* {* q2 A/ }6 [ RT_Object_Class_Timer, /* timer对象 */ RT_Object_Class_Module, /* module */ RT_Object_Class_Unknown, /* unknown */ RT_Object_Class_Static = 0x80 /*8位类型变量高位置1表示静态对象 */ };6 T2 W& \8 m+ F8 h4 F L 而rt_object_information则抽象了对象类型,加入了一个双向链表指针数据域rt_list_node,从而将同类别的内核对象利用该双链指针链接起来,这些同类别的内核对象具有如下可能的特点:
如此以来,将这些内核对象在空间上不连续的变量,利用链表形成了可统一管理、可增可删、可检索的逻辑结构。 而rt_object_container内核容器,其本质是一个内核对象索引表,主要集中管理了下面的信息:
利用宏将相应的链表进行选编译,在内核关键数据进行了裁剪管理。而对于内核本身的扩展性而言,如果需要增加新的内核功能,可以方便的增加新的内核对象类,并能方便的加入到这个内核对象容器中,利用公共的对外接口,实现统一管理,而不必对数据管理层进行额外的接口设计。 实现了哪些对外接口呢?有了这样一个优雅的数据结构设计,那么基于这样一个数据结构设计,相应就很容易实现其内核对象集中管理的对外服务接口,那么其主要的服务接口有哪些呢?
其中一部分主要接口实现对象的增加\删除\检索等,这里以rt_object_init接口为例,来简要分析一下其实现: void rt_object_init(struct rt_object *object,& g1 Y3 i8 c4 D* t8 j- l( senum rt_object_class_type type,5 W) x& j0 o2 M y+ P const char *name)7 `6 q! u! ?0 A { register rt_base_t temp; struct rt_list_node *node = RT_NULL;( a4 z. V5 d o% @) Q struct rt_object_information *information;( b! Y$ i. e% J3 B/ r2 l' U B #ifdef RT_USING_MODULE% ~% _8 x. t) a/ ^ j: u struct rt_dlmodule *module = dlmodule_self();/ c' V% D" b- l5 w a+ G* s #endif/ _2 d+ m0 V- a* n8 ?- L , I9 y8 `3 {; Z5 i/ q/ t /*1. 在容器中找到这是什么对象类*/" C1 d2 e" I# ~& W2 [; H( B information = rt_object_get_information(type);0 O; V( R7 L. m( \1 L& _. y( K RT_ASSERT(information != RT_NULL);8 _* O! X3 [2 ]) F, [' B 3 w7 P. S `7 D# M6 } /* check object type to avoid re-initialization */" }5 ?* y5 O: r/ W' f6 v8 k7 W 4 }6 q; Y% \ c( {! R /* 进入临界区保护 */' j9 m3 g# n8 y* q0 ^& [# F rt_enter_critical();: I/ Y/ w5 r. F /* try to find object */ for (node = information->object_list.next; node != &(information->object_list); node = node->next) { struct rt_object *obj;0 r( V4 v6 D* o$ E* Q* g obj = rt_list_entry(node, struct rt_object, list);! l+ |/ ^3 d3 Z" g- d% o if (obj) /* skip warning when disable debug */ {3 e( K9 D; i" h. `; L l# H RT_ASSERT(obj != object); }( w& e) q3 d% Z0 k; |0 V } /* 离开临界区 */0 _) O" j% N. m" j& n* \: q rt_exit_critical(); ! a: ^6 l( Y- u' ] /* 初始化对象参数,并置为静态标记 */ , D$ ^$ n/ @# h object->type = type | RT_Object_Class_Static; rt_strncpy(object->name, name, RT_NAME_MAX);# x. j, H% x, l* X RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object)); : H* K* X$ i2 n# F /* 禁止硬件中断 */" s1 Q9 x' D" a N temp = rt_hw_interrupt_disable();+ C% L- i+ C1 \9 Q, \ 4 h1 ^1 L6 K+ _/ ~$ [/ g3 _ #ifdef RT_USING_MODULE) D& n0 L1 R* g% d* v1 j# u: L; r G if (module) { rt_list_insert_after(&(module->object_list), &(object->list)); object->module_id = (void *)module; }# y( B) W, ^) @- Z7 i; x: M else9 s, M0 a2 C/ C) s# K2 I #endif {+ E7 ~* _2 f5 ]* J' x3 j1 E /* 对象插入容器中相应对象分支链连 */, s6 M/ k% {" p( H% ^" [! [9 N rt_list_insert_after(&(information->object_list), &(object->list)); }5 E# G1 X" J% _! U /* 开硬件中断 */9 p2 ]' Z% N8 o rt_hw_interrupt_enable(temp); }# `2 b/ v% v0 q1 _# m% j
而对于动态内核对象而言,其差异在于内核对象本身是动态申请的,这里需要注意的是向内核堆申请的,而不是C堆申请的,至于什么是内核堆,以及为什么要设计内核堆,之前有写过一篇文章分享,有兴趣可以去看看。 内核对象有什么相互继承关系?RT-Thread管网上给出了这样一个相互关系图:
如果不去具体看相应数据结构,或许不易理解为啥有这样一张图。这里以上图中其中几个内核对象来撸一撸其相互关系:
或许有盆友会问,为啥rt_thread对象中明明没有直接包含rt_object,那为啥说rt_thread也是继承自rt_object呢?如果你细看看上图rt_thread中红框框出来的数据域就恍然大悟了,即便没有直接包含,但在内存中框里的内容就是rt_object的数据内容,所以利用指针转换就可以方便访问了,至于为什么是这样?我想可能是历史原因吧?所以rt_thread结构体前面几个数据域的位置是不可以修改的。这里还有盆友可能会问为什么ipc线程通信相关内核对象需要单独拎出来一个父结构体呢?我想应该是此类具有相同的一些共性,具有一些类似的特点。这也是对象设计提取共性进而抽象封装的一个体现。( R$ F7 C; A8 z( V' U- G4 e! `8 t- ] 本文大致学习总结了一下RT-Thread内核对象子系统的设计思路的理解,从这里个人总结了一些启示:
( ^5 o5 O ~7 s' j |
微信公众号
手机版