RT-Thread自动初始化机制
自动初始化机制是指初始化函数不需要被显示调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。
例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数,代码如下:
init rt_hw_usart_init(void)
{
//注册串口1设备
rt_hw_serial_register(&serial1, "uart1",
RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,uart);
return 0;
}
INIT_BOARD_EXPORT(rt_hw_usart_init); //使用组件自动初始化机制
示例代码最后的INIT_BOARD_EXPORT(rt_hw_usart_init)表示使用自动初始化功能,按照这种方式,rt_hw_usart_init()函数就会被系统自动调用,那么它是在哪里被调用的?
在系统启动流程图中,有两个函数:rt_conponents_board_init()与rt_components_ini(),
其后的带底色方框内部的函数表示被自动初始化的函数,其中:
- board init functions为所有通过通过INIT_BOARD_EXPORT(fn)申明的初始化函数。
- “pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。
- “device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。
- “components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。
- “enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。
- “application init functions” 为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。
rt_components_board_init()函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过INIT_BOARD_EXPORT(fn)申明的初始化函数表,并调用各个函数。
rt_conponents_init()函数会在操作系统运行起来之后创建的main线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。
rt_conponents_init()函数会遍历通过剩下的其它几个宏申明的初始化函数表。
RT-Thread的自动初始化机制使用了自定义RTI符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成了一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。
用来实现自动初始化功能的宏接口定义详细描述如下表所示:
- INIT_BOARD_EXPORT(fn):非常早期的初始化,此时调度器还未启动。
- INIT_BOARD_EXPORT(fn):主要用于纯软件的初始化,没有太多依赖的函数。
- INIT_DEVICE_EXPORT(fn):外设驱动初始化相关,比如网卡设备。
- INIT_CONPONENTS_EXPORT(fn):组件初始化,比如文件系统或者LWIP。
- INIT_ENV_EXPORT(fn):系统环境初始化,比如挂载文件系统。
- INIT_APP_EXPORT(fn):应用初始化,比如GUI应用。
初始化函数主动通过这些宏接口进行申明,如INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到RTI符号段,该符号段位于内存分布的RO段,该RTI符号段中的所有函数在系统初始化时会被自动调用。
静态对象和动态对象
RT-Thread内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。内核对象分为两类:静态内核对象和动态内核对象。
静态内核对象通常放在RW段和ZI段中,在系统启动后在程序中初始化;动态内核对象则是从内存堆中创建,而后手工做初始化。
/* 线程 1 的对象和运行时用到的栈 */
static struct rt_thread thread1;
static rt_uint8_t thread1_stack[512];
/* 线程 1 入口 */
void thread1_entry(void* parameter)
{
int i;
while (1)
{
for (i = 0; i < 10; i ++)
{
rt_kprintf("%d\n", i);
/* 延时 100ms */
rt_thread_mdelay(100);
}
}
}
/* 线程 2 入口 */
void thread2_entry(void* parameter)
{
int count = 0;
while (1)
{
rt_kprintf("Thread2 count:%d\n", ++count);
/* 延时 50ms */
rt_thread_mdelay(50);
}
}
/* 线程例程初始化 */
int thread_sample_init()
{
rt_thread_t thread2_ptr;
rt_err_t result;
/* 初始化线程 1 */
/* 线程的入口是 thread1_entry,参数是 RT_NULL
* 线程栈是 thread1_stack
* 优先级是 200,时间片是 10 个 OS Tick
*/
result = rt_thread_init(&thread1,
"thread1",
thread1_entry, RT_NULL,
&thread1_stack[0], sizeof(thread1_stack),
200, 10);
/* 启动线程 */
if (result == RT_EOK) rt_thread_startup(&thread1);
/* 创建线程 2 */
/* 线程的入口是 thread2_entry, 参数是 RT_NULL
* 栈空间是 512,优先级是 250,时间片是 25 个 OS Tick
*/
thread2_ptr = rt_thread_create("thread2",
thread2_entry, RT_NULL,
512, 250, 25);
/* 启动线程 */
if (thread2_ptr != RT_NULL) rt_thread_startup(thread2_ptr);
return 0;
}
在这个例子中,thread1是一个静态线程对象,内存空间,包括线程控制块thread1与栈空间thread1_stack都是编译时决定的,因为代码中都不存在初始值,统一放在未初始化数据段中。
thread2运行中用到的空间都是动态分配的。
静态对象会占用RAM空间,不依赖于内存堆管理器,内存分配时间确定。动态对象则依赖于内存堆管理器,运行时申请RAM空间,当对象被删除后,占用的RAM空间被释放。
内核对象管理架构
RT-Thread采用内核对象管理系统来访问/管理所有内核对象,内核对象包含了内核中绝大部分设施,这些内核对象可以是静态分配的静态对象,也可以是从系统内存堆中分配的动态对象。
通过这种内核对象的设计方式,RT-Thread做到了不依赖于具体的内存分配方式,系统的灵活性得到极大的提高。
RT-Thread内核对象包括:线程,信号量,互斥量,事件,邮箱,消息队列和定时器,内存池,设备驱动等。
对象容器中包含了每类内核对象的信息,包括对象类型、大小等。对象容器给每类内核对象法分配了一个链表,所有的内核对象都被链接到该链表上。
此图显示了RT-Thread中各类内核对象的派生和继承关系。
对于每一种具体内核对象和对象控制块,除了基本结构外,还有自己的扩展属性(私有属性)。
例如对于线程控制块,在基类对象基础上进行扩展,增加了线程状态、优先级等属性。这些属性在基类对象的操作中不会用到,只有在与具体线程相关的操作中才会使用。
因此,从面向对象的观点,可以认为每一种具体对象是抽象对象的派生,继承了基本对象的属性并在此基础上扩展了与自己相关的属性。
这种设计方法的优点有:
- 提高了系统的可重用性和扩展性,增加新的对象类别很容易,只需要继承通用对象的属性再加少量扩展即可。
- 提供了统一的对象操作方式,简化了各种具体对象的操作,提高了系统的可靠性。
上图中由对象控制块rt_object派生出来的有:
- 线程对象
- 内存池对象
- 定时器对象
- 设备对象
- IPC对象(Inter-Process Communication,进程间通信。IPC对象的作用是进行线程间同步与通信);由 IPC 对象派生出信号量、互斥量、事件、邮箱与消息队列、信号等对象。
对象控制块
内核对象控制块的数据结构:
struct rt_object
{
//内核对象名称
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flag;
rt_list_t list;
}
enum rt_object_class_type
{
RT_Object_Class_Thread = 0, /* 对象为线程类型 */
#ifdef RT_USING_SEMAPHORE
RT_Object_Class_Semaphore, /* 对象为信号量类型 */
#endif
#ifdef RT_USING_MUTEX
RT_Object_Class_Mutex, /* 对象为互斥量类型 */
#endif
#ifdef RT_USING_EVENT
RT_Object_Class_Event, /* 对象为事件类型 */
#endif
#ifdef RT_USING_MAILBOX
RT_Object_Class_MailBox, /* 对象为邮箱类型 */
#endif
#ifdef RT_USING_MESSAGEQUEUE
RT_Object_Class_MessageQueue, /* 对象为消息队列类型 */
#endif
#ifdef RT_USING_MEMPOOL
RT_Object_Class_MemPool, /* 对象为内存池类型 */
#endif
#ifdef RT_USING_DEVICE
RT_Object_Class_Device, /* 对象为设备类型 */
#endif
RT_Object_Class_Timer, /* 对象为定时器类型 */
#ifdef RT_USING_MODULE
RT_Object_Class_Module, /* 对象为模块 */
#endif
RT_Object_Class_Unknown, /* 对象类型未知 */
RT_Object_Class_Static = 0x80 /* 对象为静态对象 */
};
如果是静态对象,对象类型的最高位将是1(是RT_Object_Class_Static与其他对象类型的或操作),否则就是动态对象,系统最多能够容纳的对象类别数目是127个。
内核对象容器的数据结构
struct rt_object_information
{
enum rt_object_class_type type;
rt_list_t object_list;
rt_size_t object_size;
};
一类对象由一个rt_object_information结构体来管理,每一个这类对象的具体实例都通过链表的形式挂接在object_list上。而这一类对象的内存尺寸由object_size标识出来(每一类对象的具体实例,他们占有的内存块大小都是相同的)。
初始化对象
在使用一个未初始化的静态对象前必须先对其进行初始化。
初始化对象使用以下接口:
void rt_object_init(struct rt_object* object,
enum rt_object_class_type type,
const char* name)
当调用这个函数进行对象初始化时,系统会把这个对象放置到对象容器中进行管理,即初始化对象的一些参数,然后把这个对象节点插入到对象容器的对象链表中,对该函数的输入参数的描述如下表:
分配对象
上述描述的都是对象初始化、脱离的接口,都是面向对象内存块已经有的情况下,而动态的对象则可以在需要时申请,不需要时释放出内存空间给其他应用使用。
rt_object_t rt_object_allocate(enum rt_object_class_type type ,
const char* name)
系统首先根据对象类型获取对象信息(特别是对象类型的大小信息以用于系统能够分配正确大小的内存数据块),而后从内存堆中分配对象所对应大小的内存空间,然后再对该对象进行必要的初始化,最后将其插入到它所在的对象容器链表中。
如何遍历内核对象
以遍历所有线程为例:
rt_thread_t thread = RT_NULL;
struct rt_list_node *node = RT_NULL;
struct rt_object_information *information = RT_NULL;
information = rt_object_get_information(RT_Object_Class_Thread);
rt_list_for_each(node, &(information->object_list))
{
thread = (rt_thread_t)rt_list_entry(node, struct rt_object, list);
/* 比如打印所有thread的名字 */
rt_kprintf("name:%s\n", thread->name);
}
以遍历所有互斥量为例:
rt_mutex_t mutex = RT_NULL;
struct rt_list_node *node = RT_NULL;
struct rt_object_information *information = RT_NULL;
information = rt_object_get_information(RT_Object_Class_Mutex);
rt_list_for_each(node, &(information->object_list))
{
mutex = (rt_mutex_t)rt_list_entry(node, struct rt_object, list);
/* 比如打印所有mutex的名字 */
rt_kprintf("name:%s\n", mutex->parent.parent.name);
}