1、前言
RT-Thread是一个嵌入式实时操作系统,而且提供了丰富的组件库,文档资料的支持也比较全面,可以说大大降低了项目的开发时间。而且在其源码中融入了很多linux方面的编程技巧,非常值得学习和借鉴。以后我将在本系列中剖析一些源码中的关键点,记录一下学习的过程,同时也希望能为疑惑的您提供一些思路。
本章主要以基本内核对象结构为切入点,讲述其如何进行对链表上的设备进行查找和添加。
2、设备相关结构体
rtdef.h 文件中定义了相关的结构体,这里相关的对象类型和设备类型枚举我就不放上了,防止代码过长,影响观看。
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. */
/**
* Base structure of Kernel object
*/
struct rt_object
{
char name[RT_NAME_MAX]; /**< name of kernel object */
rt_uint8_t type; /**< type of kernel object */
rt_uint8_t flag; /**< flag of kernel object */
#ifdef RT_USING_MODULE
void *module_id; /**< id of application module */
#endif
rt_list_t list; /**< list node of kernel object */
};
typedef struct rt_object *rt_object_t; /**< Type for kernel objects. */
/**
* 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 */
};
typedef struct rt_device *rt_device_t;
/**
* Device structure
*/
struct rt_device
{
struct rt_object parent; /**< inherit from rt_object */
enum rt_device_class_type type; /**< device type */
rt_uint16_t flag; /**< device flag */
rt_uint16_t open_flag; /**< device open flag */
rt_uint8_t ref_count; /**< reference count */
rt_uint8_t device_id; /**< 0 - 255 */
/* device call back */
rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);
rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);
/* common device interface */
rt_err_t (*init) (rt_device_t dev);
rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag);
rt_err_t (*close) (rt_device_t dev);
rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);
rt_err_t (*control)(rt_device_t dev, rt_uint8_t cmd, void *args);
void *user_data; /**< device private data */
};
根据以上结构体可以看出,struct rt_list_node是一个的双向链表,struct rt_object是基本的内核对象,内核中线程、信号量等等对象类型都由其构成,struct rt_object_information是描述这个对象的信息,包括对象类型和对象的大小,struct rt_device是设备的结构体。看到这里应该会有一个疑问,struct rt_object_information怎么去描述设备对象,以及struct rt_object_information中为什么是object_list,有什么好处?
3、rt_object容器定义
在文件object.c定义了下面的宏和数组:
#define _OBJ_CONTAINER_LIST_INIT(c) \
{&(rt_object_container[c].object_list), &(rt_object_container[c].object_list)}
struct rt_object_information rt_object_container[RT_Object_Class_Unknown] =
{
/* initialize object container - thread */
{RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Thread), sizeof(struct rt_thread)},
#ifdef RT_USING_SEMAPHORE
/* initialize object container - semaphore */
{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Semaphore), sizeof(struct rt_semaphore)},
#endif
#ifdef RT_USING_MUTEX
/* initialize object container - mutex */
{RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Mutex), sizeof(struct rt_mutex)},
#endif
#ifdef RT_USING_EVENT
/* initialize object container - event */
{RT_Object_Class_Event, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Event), sizeof(struct rt_event)},
#endif
#ifdef RT_USING_MAILBOX
/* initialize object container - mailbox */
{RT_Object_Class_MailBox, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_MailBox), sizeof(struct rt_mailbox)},
#endif
#ifdef RT_USING_MESSAGEQUEUE
/* initialize object container - message queue */
{RT_Object_Class_MessageQueue, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_MessageQueue), sizeof(struct rt_messagequeue)},
#endif
#ifdef RT_USING_MEMHEAP
/* initialize object container - memory heap */
{RT_Object_Class_MemHeap, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_MemHeap), sizeof(struct rt_memheap)},
#endif
#ifdef RT_USING_MEMPOOL
/* initialize object container - memory pool */
{RT_Object_Class_MemPool, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_MemPool), sizeof(struct rt_mempool)},
#endif
#ifdef RT_USING_DEVICE
/* initialize object container - device */
{RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Device), sizeof(struct rt_device)},
#endif
/* initialize object container - timer */
{RT_Object_Class_Timer, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Timer), sizeof(struct rt_timer)},
#ifdef RT_USING_MODULE
/* initialize object container - module */
{RT_Object_Class_Module, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Module), sizeof(struct rt_module)},
#endif
};
从定义的内容可以了解到,定义了一个struct rt_object_information类型的数组rt_object_container,其中关于设备信息的一行为 {RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Device), sizeof(struct rt_device)} 。要注意的点是 _OBJ_CONTAINER_LIST_INIT(RT_Object_Class_Device) 这是对 rt_list_t一个双向链表指针进行赋值, _OBJ_CONTAINER_LIST_INIT该宏的定义见数组上方,该宏的定义为将 rt_list_t的 next 和 prev指向 rt_list_t一个双向链表指针,也就是指向定义的对象指针,这里为什么这么使用呢?先卖一个关子让我们继续往下看。
4、设备查找
在 device.c文件中定义了设备的注册函数,函数体如下:
/**
* This function registers a device driver with specified name.
*
* @param dev the pointer of device driver structure
* @param name the device driver's name
* @param flags the flag of device
*
* @return the error code, RT_EOK on initialization successfully.
*/
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
{
if (dev == RT_NULL)
return -RT_ERROR;
if (rt_device_find(name) != RT_NULL)
return -RT_ERROR;
rt_object_init(&(dev->parent), RT_Object_Class_Device, name);
dev->flag = flags;
dev->ref_count = 0;
return RT_EOK;
}
函数的流程为,先对设备驱动结构指针进行判断,判断为 RT_NULL时直接返回错误。然后根据设备名称进行查找设备,如果设备不存在就初始化对象,如果对象存在就返回错误。首先我们先看一下 rt_device_find的定义,也是在device.c中,函数体如下:
rt_device_t rt_device_find(const char *name)
{
struct rt_object *object;
struct rt_list_node *node;
struct rt_object_information *information;
extern struct rt_object_information rt_object_container[];
/* enter critical */
if (rt_thread_self() != RT_NULL)
rt_enter_critical();
/* try to find device object */
information = &rt_object_container[RT_Object_Class_Device];
for (node = information->object_list.next;
node != &(information->object_list);
node = node->next)
{
object = rt_list_entry(node, struct rt_object, list);
if (rt_strncmp(object->name, name, RT_NAME_MAX) == 0)
{
/* leave critical */
if (rt_thread_self() != RT_NULL)
rt_exit_critical();
return (rt_device_t)object;
}
}
/* leave critical */
if (rt_thread_self() != RT_NULL)
rt_exit_critical();
/* not found */
return RT_NULL;
}
首先 information = &rt_object_container[RT_Object_Class_Device]; 是取设备类型的struct rt_object_information指针,然后for循环对struct rt_object_information中的双向链表object_list进行遍历。其中 node = information->object_list.next是对node赋值初值也就是information->object_list 链表的第二个节点 node != &(information->object_list) 是结束条件,为什么这样写呢,回到第二节卖的关子,其实information->object_list 链表是一条首尾相连的链表,判断链的结束也就是应该以回到首节点为条件 information->object_list就是首节点,node = node->next是指向链表的下一个节点。进入for循环里面,rt_list_entry又是一个宏,该宏的定义如下:
#define rt_list_entry(node, type, member) \
((type *)((char *)(node) - (unsigned long)(&((type *)0)->member)))
其中 (char *)(node) 是将node强制转换为char * 类型,这样做的作用是为了指针按照字节进行偏移计算,(unsigned long)(&((type *)0)->member) 是编译器的特殊用法,作用是计算member相对于 type * 的偏移。那么 rt_list_entry(node, struct rt_object, list); 表示的就是node减去list在结构体rt_object的偏移,最后将其转换为struct rt_object类型,它的作用是什么呢?node是rt_list_t类型的指针是怎么转换成rt_object类型的指针呢?带着这个问题让我们回到设备注册函数rt_device_register。
5、对象初始化
在rt_device_register 函数的rt_object_init(&(dev->parent), RT_Object_Class_Device, name); 中进行了对象的初始化,dev->parent是设备驱动结构中的 struct rt_object类型变量。进入object.c文件中 rt_object_init,函数体如下:
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
/* get module object information */
information = (rt_module_self() != RT_NULL) ?
&rt_module_self()->module_object[type] : &rt_object_container[type];
#else
/* get object information */
information = &rt_object_container[type];
#endif
/* 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();
/* insert object into information object list */
rt_list_insert_after(&(information->object_list), &(object->list));
/* unlock interrupt */
rt_hw_interrupt_enable(temp);
}
这里面需要关注的是rt_list_insert_after(&(information->object_list), &(object->list)); 它的作用是将object->list插入到information->object_list链表的后面,函数定义如下:
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
l->next->prev = n;
n->next = l->next;
l->next = n;
n->prev = l;
}
根据之前的推断我们知道rt_list_t是一个首尾相连的双向链表,结构如下图所示:
那么l->next->prev就是header节点的prev指向,他应该指向新插入的节点也就是n,n->next = l->next; 就是新节点的next指向header节点,l->next = n; 是l的next指向新插入的下一个节点也就是n,n->prev = l; 新插入的节点n的prev也就指向了他的前一个节点也就是l,这样就完了节点的插入。这里插入的是 &(dev->parent),这样就实现了设备串联成一条链表。
看到这里应该也就知道了上一节提出的问题,因为rt_list_t是被rt_object包含的 ,知道rt_object对象中的rt_list_t元素的地址,再减去rt_list_t元素相对于rt_object的偏移,就知道了rt_object对象首个元素的地址,也就是rt_object对象的地址。通过对rt_object中name的匹配实现了设备的查找操作。
6、总结
RT-Thread设备通过首尾相连的双向链表进行设备的管理,实现方式类似于JAVA中的多态,只不过它是通过rt_list_entry宏进行偏移计算实现向上转型,而不是通过强制类型转换进行实现,这样做的好处是不用将向下转型的结构体对象放在父结构体的首地址即可实现,增加了灵活性。