libhv学习笔记4:list.h分析

本文详细介绍了Linux内核中的链表数据结构,包括初始化、添加、删除、移动和判断链表状态等操作。通过具体示例展示了如何在`hidle_s`结构体中使用链表,以及如何访问结构体内的其他数据成员。文章还讨论了在多CPU环境下链表操作的注意事项和同步问题。
摘要由CSDN通过智能技术生成

在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用到的堆的源码设计。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值