在RTT内核启动之rt_hw_board_init硬件板级初始化函数-CSDN博客这篇文章中,我们介绍完了板级硬件初始化函数,这篇文章中我们继续介绍系统定时器的初始化函数。
rt_timer_list定时器列表
与定时器相关的有一个很重要的系统定时器列表,他在timer.c文件中被定义:
/* hard timer list */
static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL];
这是一个rt_list_t类型(该类型是一个双向链表)的数组,数组大小由宏RT_TIMER_SKIP_LIST_LEVEL来决定,这个宏在rtdef.h文件中被定义,默认情况下这个宏数值为1,也就是数组大小就是1,即数组只有一个成员。
/**
* Double List structure
*/
struct rt_list_node
{
struct rt_list_node *next; /**< point to next node. */
struct rt_list_node *prev; /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t; /**< Type for lists. */
内核对象定时器rt_timer结构体
除此之外我们再来说一下内核对象定时器rt_timer结构体的相关内容,下面是这个结构体的原型以及时钟和定时器的一些相关的宏定义:
最后一行中,我们定义了一个新的类型别名:rt_timer_t,它实际上是struct rt_timer *
类型的结构体指针。
我们先来解释一些结构体中的一些变量。
- parent
第一个结构体类型变量:parent
,这里通过定义一个“父类”的结构体变量,实现了类似面向对象编程中继承的概念,继承了RTT的内核对象。
因为定时器也属于内核对象,也会在自身结构体里面包含一个内核对象类型的成员,通过这个成员可以将定时器挂到系统对象容器中。
上面这句高亮的部分是我在查阅相关资料的时候在野火的RTT教程中看到的一句话。至于如何通过这个成员将定时器挂到系统对象容器中,我的猜测是这样的(仅仅是个人的猜测,还没有详细的研究这一部分代码):
在timer.c文件中,有这么一个定时器初始化函数,下面是函数原型:
/**
* This function will initialize a timer, normally this function is used to
* initialize a static timer object.
*
* @param timer the static timer object
* @param name the name of timer
* @param timeout the timeout function
* @param parameter the parameter of timeout function
* @param time the tick of timer
* @param flag the flag of 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)
{
/* timer check */
RT_ASSERT(timer != RT_NULL);
/* timer object initialization */
rt_object_init((rt_object_t)timer, RT_Object_Class_Timer, name);
_rt_timer_init(timer, timeout, parameter, time, flag);
}
RTM_EXPORT(rt_timer_init);
我们可以注意到这个函数中还嵌套了一个内核对象的初始化函数rt_object_init,下面是这个函数的原型:
/**
* This function will initialize an object and add it to object system
* management.
*
* @param object the specified object to be initialized.
* @param type the object type.
* @param name the object name. In system, the object's name must be unique.
*/
void rt_object_init(struct rt_object *object,
enum rt_object_class_type type,
const char *name)
{
register rt_base_t temp;
struct rt_object_information *information;
#ifdef RT_USING_MODULE
struct rt_dlmodule *module = dlmodule_self();
#endif
/* get object information */
information = rt_object_get_information(type);
RT_ASSERT(information != RT_NULL);
/* initialize object's parameters */
/* 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 */
temp = rt_hw_interrupt_disable();
#ifdef RT_USING_MODULE
if (module)
{
rt_list_insert_after(&(module->object_list), &(object->list));
object->module_id = (void *)module;
}
else
#endif
{
/* insert object into information object list */
rt_list_insert_after(&(information->object_list), &(object->list));
}
/* unlock interrupt */
rt_hw_interrupt_enable(temp);
}
其中RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object));这个宏定义应该起到了将定时器挂在到了系统对象容器中的作用。这个宏首先”CALL“了一个钩子函数,然后调用了rt_object_attach_hook这个钩子函数,根据这个钩子函数的命名,我们可以大概的猜到他的作用:将内核对象attach(连接)到了系统对象容器。
-
row[RT_TIMER_SKIP_LIST_LEVEL]
这也是一个rt_list_t类型的数组,是定时器自身的结点,通过该结点可以实现将定时器插入到系统定时器列表,数组的长度是由宏RT_TIMER_SKIP_LIST_LEVEL来决定,默认情况下数组长度为1。 -
void (*timeout_func)(void *parameter);
这是一个超时回调函数指针,用于处理定时器溢出事件。 -
void *parameter;
这是超时回调函数需要的参数,从而实现处理可变参数的函数。 -
init_tick
这个变量是定时实际需要延时的时间,单位是tick。 -
timeout_tick
这个变量储存的是当定时器达到延时时间后系统时基计数器rt_rick(在clock.c文件中被定义)的实际值,也就是定时器的超时时刻。举个例子,假设有一个线程需要延时10个tick,那么init_tick=10,而此时的rt_tick如果是12,那么这时候timeout_tick就等于init_tick + rt_tick=22。
我们再来看系统定时器的初始化函数:
/**
* @ingroup SystemInit
*
* This function will initialize system timer
*/
void rt_system_timer_init(void)
{
int i;
for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
这个初始化函数先遍历了系统定时器列表,然后将定时器列表中的每一个结点都进行了初始化
这样做的好处有很多:
1. 链表的自包含性
将链表节点的 next
和 prev
都指向自身,使得该节点在逻辑上形成了一个空链表的形式。这样,单个节点就可以表示一个独立的空链表,这种自包含性使得链表操作更加简洁和一致。
rt_inline void rt_list_init(rt_list_t *l) { l->next = l->prev = l; }
2. 简化链表操作
初始化后的节点可以直接作为链表操作的起点,无需额外的检查和特殊处理。例如,在插入、删除节点时,可以统一处理而不需要区分节点是否已经在链表中或链表是否为空。
3. 避免空指针问题
将节点的 next
和 prev
初始化为指向自身,有助于避免链表操作中常见的空指针引用问题。在操作空链表或单节点链表时,不会因为未初始化的指针而发生崩溃。
4. 提高代码可读性
这种初始化方式使链表初始化代码更加直观,易于理解和维护。开发人员可以明确知道一个新初始化的节点是独立的,不属于任何链表。
5. 一致性检查
通过检查节点的 next
和 prev
是否指向自身,可以快速判断一个节点是否是一个独立的空链表的一部分。这对于调试和确保链表操作的一致性非常有用。
另外链表的初始化函数在rtservice.c文件中被定义,这个文件中储存的是操作系统服务层函数。例如链表的增删改查函数。
/**
* @brief initialize a list
*
* @param l list to be initialized
*/
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
在rt_system_timer_init函数对系统定时器初始化完成之后,后面的rt_system_timer_thread_init函数将会对系统定时器线程进行初始化并启动这一线程。