【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. */
- name:根据RT_NAME_MAX宏的值选择不同的名称存储方式,可以指向常量名字也可以使用字符数组命名
- type:rt_uint8_t类型,标识内核对象的具体类型(如线程、信号量、互斥量等),见下文
- flag:rt_uint8_t类型,存储对象的配置标志和状态信息
- module_id:模块隔离,支持多模块环境下的对象管理,主要应用场景是在支持动态加载模块的系统中标识对象所属模块
- 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位于内核虚拟地址空间内核虚拟地址空间的核心特性是 “全局共享且映射一致”—— 所有线程(包括内核线程和用户线程)的页表中,内核虚拟地址到物理地址的映射是完全相同的。对于内核线程而言,这意味着:
- 它访问的内核虚拟地址(如存放内核对象的全局静态数组地址 0xFFFF0000),会被 MMU 映射到固定的物理内存(如内核数据段所在的物理页);
- 其他内核线程访问同一个虚拟地址 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, ¶m);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互斥访问。