在libhv中,用到了链表的数据结构(比如说,struct hloop_s中的 struct list_head idles),其定义和实现在list.h中。
首先看一下结构体:
struct list_head {
struct list_head *next, *prev;
};
#define list_node list_head
可以看到,只包含了两个指针,而没有数据域,后面看一下到底是怎么使用的。
初始化结构体
static inline void list_init(struct list_head *list)
{
list->next = list;
list->prev = list;
}
就是将指针指向自己。
添加数据
static inline void __list_add(struct list_head *n,
struct list_head *prev,
struct list_head *next)
{
next->prev = n;
n->next = next;
n->prev = prev;
prev->next = n;
}
向前插入
static inline void list_add(struct list_head *n, struct list_head *head)
{
__list_add(n, head, head->next);
}
尾插入
static inline void list_add_tail(struct list_head *n, struct list_head *head)
{
__list_add(n, head->prev, head);
}
尾插入的时候,因为head的prev指针指向的是最后一个元素,所以插在最后一个元素和head之间,就是尾插入了。
删除
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
删除是指从链表中移除,并没有真正的释放。调用接口
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
//entry->next = NULL;
//entry->prev = NULL;
}
元素替换
static inline void list_replace(struct list_head *old,
struct list_head *n)
{
n->next = old->next;
n->next->prev = n;
n->prev = old->prev;
n->prev->next = n;
}
移动元素
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
将元素从原链表中移除,添加到另一个链表中
判空
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
另一种多cpu情况下的判空
static inline int list_empty_careful(const struct list_head *head)
{
struct list_head *next = head->next;
return (next == head) && (next == head->prev);
}
作者的解释是:
- Description:
- tests whether a list is empty and checks that no other CPU might be in the process of modifying either member (next or prev)
- NOTE: using list_empty_careful() without synchronization can only be safe if the only activity that can happen to the list entry is list_del_init(). Eg. it cannot be used if another CPU could re-list_add() it.
这里的意思是,使用list_empty_careful()只有其他cpu仅仅使用list_del_init()的时候,才不需要同步。看一下list_del_init()
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
这里在删除entry指针的时候,会对head指针初始化
#define INIT_LIST_HEAD list_init
static inline void list_init(struct list_head *list)
{
list->next = list;
list->prev = list;
}
如果其他cpu调用的list_del的话,这里没有初始化prev和next指针,所以使用list_empty_careful的时候,可能会出现next != head->prev的情况。(不是很确定理解是否正确)
如何使用?
看一下空闲事件的数据结构
struct hidle_s {
HEVENT_FIELDS
uint32_t repeat;
//private:
struct list_node node;
};
可以看到,是将list_node定义到数据结构里面了。
那么添加节点的过程就是:
hidle_t* hidle_add(hloop_t* loop, hidle_cb cb, uint32_t repeat) {
hidle_t* idle;
HV_ALLOC_SIZEOF(idle);
idle->event_type = HEVENT_TYPE_IDLE;
idle->priority = HEVENT_LOWEST_PRIORITY;
idle->repeat = repeat;
list_add(&idle->node, &loop->idles);
EVENT_ADD(loop, idle, cb);
loop->nidles++;
return idle;
}
那么,知道list_node的情况下,如何获取struct hidle_s结构体里的其他数据呢?
其实很简单,回看一下上一篇里取空间事件的代码
static int hloop_process_idles(hloop_t* loop) {
int nidles = 0;
struct list_node* node = loop->idles.next;
hidle_t* idle = NULL;
while (node != &loop->idles) {
idle = IDLE_ENTRY(node);
node = node->next;
if (idle->repeat != INFINITE) {
--idle->repeat;
}
if (idle->repeat == 0) {
// NOTE: Just mark it as destroy and remove from list.
// Real deletion occurs after hloop_process_pendings.
__hidle_del(idle);
}
EVENT_PENDING(idle);
++nidles;
}
return nidles;
}
#define IDLE_ENTRY(p) container_of(p, hidle_t, node)
#define container_of(ptr, type, member) \
((type*)((char*)(ptr) - offsetof(type, member)))
#endif
这里ptr是list_node的指针,type是hidle_t结构体名,member是hidle_t结构体中list_node的变量名
回看一下hidle_t结构体
struct hidle_s { //hidle_s =hidle_t
HEVENT_FIELDS
uint32_t repeat;
//private:
struct list_node node;
};
而offsetot()函数可以计算出变量在所在结构体的偏移量,那么就可以计算出结构体的地址了,也就可以访问结构体里的其他成员变量了。
这里的设计思路,和一般情况下,prev,next指针直接与data部数据放在一个结构体里的设计还有不同的。很有意思。
以上就是list.h部分源码的分析过程,更过的接口可以看list.h的源码,不是很难。
下一篇讲解libhv用到的堆的源码设计。