内核双向循环链表

内核双向循环链表

与普通双向循环链表之所以不同,是因为内核双向循环链表没有数据部分,所以要使用就需要创建一个结构体,里面的成员为你的数据部分,以及双向循环链表,即将双向循环链表作为一个结构体成员进行封装,如:

struct stu{
		char name[128];
		int math;
		int id;
		
		struct list_head list;
};

在Linux源代码树的include/linux/list.h文件中,采用了一种类型无关的双循环链表实现方式。其思想是将指针prevnext从具体的数据结构中提取出来构成一种通用的"双链表"数据结构list_head。如果需要构造某类对象的特定链表,则在其结构(被称为宿主数据结构)中定义一个类型为list_head类型的成员,通过这个成员将这类对象连接起来,形成所需链表,并通过通用链表函数对其进行操作。其优点是只需编写通用链表函数,即可构造和操作不同对象的链表,而无需为每类对象的每种列表编写专用函数,实现了代码的重用。

struct list_head数据结构如下:

struct list_head {
    struct list_head *next, *prev;
};

定义和初始化

Linux 的每个双循环链表都有一个链表头,链表头也是一个节点,只不过它不嵌入到宿主数据结构中,即不能利用链表头定位到对应的宿主结构,但可以由之获得虚拟的宿主结构指针。
LIST_HEAD()宏可以同时完成定义链表头,并初始化这个双循环链表为空。例如:LIST_HEAD(head)

添加节点

头插

static inline void list_add(struct list_head *new, struct  list_head *head)

{
       __list_add(new, head, head->next);
}

list_add()调用底层__list_add()

static inline void __list_add(struct list *new,
             struct list *prev,
             struct list *next)
{
    next->prev = new;
    new->next = next;
    new->prev = prev;
    prev->next = new;
}

普通的在两个非空结点中插入一个结点,注意newprevnext都不能是空值。prev可以等于next,此时在只含头节点的链表中插入新节点。

尾插

static inline void list_add_tail(struct list *new, struct list *head)
{
    __list_add(new, head->prev, head);
}

list_add和list_add_tail虽然原型一样,但调用底层函数__list_add时传递了不同的参数,从而实现了在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 = LIST_POISON1;
       entry->prev = LIST_POISON2;
}

#define LIST_POISON1 ((void *) 0x00100100)

#define LIST_POISON2 ((void *) 0x00200200)

相当于NULL,使其节点不可被访问

获取宿主对象指针

container_of(pos, type ,member)

#define container_of(ptr, type, member) ({      
const typeof( ((type *)0)->member ) *__mptr = (ptr); 
(type *)( (char *)__mptr - offsetof(type,member) );})

typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。

offsetof(type,member)就是取结构体中的成员相对于地址0的偏移地址,也就是成员变量相对于结构体变量首地址的偏移。

  • ((TYPE *)0) 将零转型为TYPE类型指针;

  • ((TYPE *)0)->MEMBER 访问结构中的数据成员;

  • &(TYPE *)0 )->MEMBER )取出数据成员的地址;

  • (size_t)(&(((TYPE*)0)->MEMBER))结果转换类型,结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址;

用结构体成员变量的地址减去偏移量则是结构体的首地址。

遍历

#define list_for_each_safe(pos, n, head) /

       for (pos = (head)->next, n = pos->next; pos != (head); /

              pos = n, n = pos->next)

在for循环中n暂存pos下一个节点的地址,避免因pos节点被释放而造成的断链。

#define list_for_each_entry_safe(pos, tmp, head, member)        \
    for (pos = __container_of((head)->next, pos, member),       \
     ¦tmp = __container_of(pos->member.next, pos, member);       \
     ¦&pos->member != (head);                    \
    ¦pos = tmp, tmp = __container_of(pos->member.next, tmp, member)

list_for_each_entry_safe()函数可以不用先使用container_of获得宿主结构体指针,也将pos->next保存了起来,所以比较安全。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值