目录

​原理​

​前言​

​2  定义​

​3  list提供的操作方法​

​使用​

​(二)结构体初始化​

​(三)增加结点​

​(四)删除结点​

​(五)替换结点​

​(六)结点搬移​

​(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点​



原理

前言

linux kernel里的很多数据结构都很经典, list链表就是其中之一,本文将从以下几方面介绍list链表:list的定义、list提供的操作方法、注意事项、使用实例

linux kernel里的很多数据结构都很经典, list链表就是其中之一

 

本篇要介绍的内容:

  1. list的定义
  2. list提供的操作方法
  3. 注意事项
  4. 使用实例

list链表

1  List 所在文件

List的所有操作可以在 include/linux/list.h找到;

List head的定义可以在 include/linux/types.h找到;

 

2  定义

实际上这就是一个双向循环链表, 且有一个头指针

list head的定义:

【数据结构】linux 内核的list_头插法

这个定义中只有前向和后向指针,没任何的数据部分, 那我们基本上就知道了, 它不是被单独使用的,而是把它嵌入到用户定义的struct中, 将用户定义的数据结构串起来,作成list;

思想很巧妙, 对用户定义的数据结构侵入性很小, 实现了c++中std::List模板的功能;

虽然这个定义是叫head, 但其实嵌入到用户定义的数据结构中的也是这个.(需要注意的一点是,头结点head是不使用的,这点需要注意。使用list_head组织的链表的结构如下图所示:)

【数据结构】linux 内核的list_初始化_02

3  list提供的操作方法

初始化

//静态初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
//调用INIT_LIST_HEAD 来初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
WRITE_ONCE(list->next, list);
list->prev = list;
}

 

插入操作

将一个元素插入到两个元素之间, 即将 new插入到prev和next中, 这个函数是下面在头部和尾部插入的实现基础

/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
if (!__list_add_valid(new, prev, next))
return;
//前后指针改写赋值
next->prev = new;
new->next = next;
new->prev = prev;
WRITE_ONCE(prev->next, new);
}

在头部插入, 在头指针第一个元素间插入

static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}

 

【数据结构】linux 内核的list_#define_03

在尾部插入,在最后一个元素和头指针间插入, 因为是循环链表嘛~

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

 

 

【数据结构】linux 内核的list_#define_04

删除操作 

删除两个元素之间的元素

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
WRITE_ONCE(prev->next, next);
}

删除一个已知元素entry

static inline void __list_del_entry(struct list_head *entry)
{
if (!__list_del_entry_valid(entry))
return;

__list_del(entry->prev, entry->next);
}

 

替换操作

都是指针的变换

static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}

 

移动操作

搬移就是将一个结点从一个链表删除之后,加入到其他的新的链表当中。这里提供了两个接口:

static inline void list_move(struct list_head *list, struct list_head *head)

static inline void list_move_tail(struct list_head *list,struct list_head *head)

前者是加入的时候使用头插法,后者使用的是尾插法。

//将一个元素移动到另一个list的头部
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del(list->prev, list->next);
list_add(list, head);
}
//将一个元素移动到另一个list的队尾
static inline void list_move_tail(struct list_head *list,struct list_head *head)
{
__list_del(list->prev, list->next);
list_add_tail(list, head);
}

 

拆分操作

 

将一个队列由指定的位置拆成两个队列

list是新队列的head指针, 包括的元素从原head队列的第一个元素到entry, head队列仅包括余下的元素

【数据结构】linux 内核的list_链表_05

 

合并操作

将list列表中除了list本身插入到prev和next之间

【数据结构】linux 内核的list_链表_06

 

将一个列表插入到另一个列表的头部

【数据结构】linux 内核的list_头插法_07

 

将一个列表插入到另一个列表的尾部

【数据结构】linux 内核的list_链表_08

list_entry宏

按之前说的, 这个list_head都有要嵌入到用户定义的struct中,这个宏就是由这个list_head ptr来获取当前所处的struct对象的指针, 用了linux的经典宏定义 container_of

【数据结构】linux 内核的list_初始化_09

一堆宏定义, 用来各种遍历, 获取entry

( container_of介绍:​​javascript:void(0)​​)

4  注意事项

只说一个,就是多线程操作同一个list, 还是需要加锁

 

5  使用实例

【数据结构】linux 内核的list_#define_10

使用

连接:​​http://blog.chinaunix.net/uid-22566367-id-2182866.html​

(二)结构体初始化


结构体初始化函数:


  1. static inline void INIT_LIST_HEAD(struct list_head *list)
  2. {
  3.     list->next = list;
  4.     list->prev = list;
  5. }



初始化结构体list指向自己本身。


对于(一)结构体定义和(二)结构体初始化来说,最终的效果是一样的,都是将一个


struct list_head变量指向自己本身。


可以写一个小的程序测试一下


  1. /*test.c*/
  2. #include <stdio.h>
  3. struct list_head {
  4.     struct list_head *next, *prev;
  5. };
  6. #define LIST_HEAD_INIT(name) { &(name), &(name) }
  7. #define LIST_HEAD(name) \
  8.     struct list_head name = LIST_HEAD_INIT(name)
  9. static inline void INIT_LIST_HEAD(struct list_head *list)
  10. {
  11.     list->next = list;
  12.     list->prev = list;
  13. }
  14. int main()
  15. {
  16.     LIST_HEAD(temp);
  17.     printf("%p %p %p\n", (&temp)->prev, (&temp)->next, &temp);
  18.     INIT_LIST_HEAD(&temp);
  19.     printf("%p %p %p\n", (&temp)->prev, (&temp)->next, &temp);
  20.     return 0;
  21. }
  22. 运行结果:
  23. ^_^[sunny@sunny-laptop ~/DS]11$ ./a.out
  24. 0xbf8191a8 0xbf8191a8 0xbf8191a8
  25. 0xbf8191a8 0xbf8191a8 0xbf8191a8
  26. ^_^[sunny@sunny-laptop ~/DS]12$



可以看出他们完毕之后的地址都是一样的。


 


(三)增加结点


 


附带知识:


  1. 内联函数 inline
  2. 在c 中,为了解决一些频繁调用的小函数而大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。
  3. 内联函数使用inline关键字定义,
  4. 并且函数体和声明必须结合在一起,
  5. 否则编译器将他作为普通函数对待。
  6. inline void function(int x); //仅仅是声明函数,没有任何效果
  7. inline void function(int x) //正确
  8. {
  9. return x;
  10. }



增加结点的话,有两种方式:头插法和尾插法。


我们调用的话就调用


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


static inline void list_add_tail(struct list_head *new, struct list_head *head);


这两个接口。


头插法:


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


{


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


}


尾插法:


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


{


__list_add(new, head->prev, head);


}


真正的实现插入:


static inline void __list_add(struct list_head *new,


      struct list_head *prev,


      struct list_head *next)


{


next->prev = new;


new->next = next;


new->prev = prev;


prev->next = new;


}


__list_add(new, prev, next):表示在prev和next之间添加一个新的节点new


所以对于list_add()中的__list_add(new, head, head->next)表示的在head和


head->next之间加入一个新的节点,是头插法。


对于list_add_tail()中的__list_add(new, head->prev, head)表示在head->prev(双向循环链表的最后一个结点)和head之间添加一个新的结点。



 


 



 


(四)删除结点


//LIST_POISON1和LIST_POISON2这两个变量在poison.h中定义的:

#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA)

#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA)

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;
}


/*prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问(对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障)。
还有一个删除操作的接口函数*/

static inline void list_del_init(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
INIT_LIST_HEAD(entry);
}

/*这个函数首先将entry从双向链表中删除之后,并且将entry初始化为一个空链表。
list_del(entry)和list_del_init(entry)唯一不同的是对entry的处理,前者是将entry设置为不可用,后者是将其设置为一个空的链表的开始。*/



指定一个结点,删除这个结点


我们调用的话,调用


static inline void list_del(struct list_head *entry)


这个函数接口就可以了。


  1. static inline void __list_del(struct list_head * prev, struct list_head * next)
  2. {
  3.     next->prev = prev;
  4.     prev->next = next;
  5. }
  6. static inline void list_del(struct list_head *entry)
  7. {
  8.     __list_del(entry->prev, entry->next);
  9.     entry->next = LIST_POISON1;
  10.     entry->prev = LIST_POISON2;
  11. }



__list_del(entry->prev, entry->next)表示将entry的前一个和后一个之间建立关联。


至于让,


entry->next = LIST_POISON1;


entry->prev = LIST_POISON2;


LIST_POISON1和LIST_POISON2这两个变量在poison.h中定义的:


#define LIST_POISON1  ((void *) 0x00100100 + POISON_POINTER_DELTA)


#define LIST_POISON2  ((void *) 0x00200200 + POISON_POINTER_DELTA)


prev、next指针分别被设为LIST_POSITION2和LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问(对LIST_POSITION1和LIST_POSITION2的访问都将引起页故障)。


还有一个删除操作的接口函数


  1. static inline void list_del_init(struct list_head *entry)
  2. {
  3.     __list_del(entry->prev, entry->next);
  4.     INIT_LIST_HEAD(entry);
  5. }



这个函数首先将entry从双向链表中删除之后,并且将entry初始化为一个空链表。


list_del(entry)和list_del_init(entry)唯一不同的是对entry的处理,前者是将entry设置为不可用,后者是将其设置为一个空的链表的开始。


 


(五)替换结点


 


结点的替换操作是将old的结点替换成new,提供的接口是


static inline void list_replace_init(struct list_head *old,


struct list_head *new);


下面是替换的有关代码


  1. static inline void list_replace(struct list_head *old,
  2.                 struct list_head *new)
  3. {
  4.     new->next = old->next;
  5.     new->next->prev = new;
  6.     new->prev = old->prev;
  7.     new->prev->next = new;
  8. }
  9. static inline void list_replace_init(struct list_head *old,
  10.                     struct list_head *new)
  11. {
  12.     list_replace(old, new);
  13.     INIT_LIST_HEAD(old);
  14. }



List_replace_init首先调用list_replace改变new和old的指针关系,然后调用INIT_LIST_HEAD(old)将其设置为一个指向自己的结点(这个操作和前面的删除操作是一样的——初始化)。


 


(六)结点搬移


 


搬移就是将一个结点从一个链表但终删除之后,加入到其他的一新的链表当中。这里提供了两个接口:


static inline void list_move(struct list_head *list, struct list_head *head)


static inline void list_move_tail(struct list_head *list,struct list_head *head)


前者是加入的时候使用头插法,后者使用的是尾插法。


static inline void list_move(struct list_head *list, struct list_head *head)


{


__list_del(list->prev, list->next);


list_add(list, head);


}


首先调用__list_del(list->prev, list->next),将list的前一个结点和后一个结点建立联系,之后调用list_add(list, head)将list添加到head的链表。下面的和这个类似,不同的是使用的是尾插法。


static inline void list_move_tail(struct list_head *list,


  struct list_head *head)


{


__list_del(list->prev, list->next);


list_add_tail(list, head);


}


(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点


 


判断list这个结点是不是链表head的最后一个节点。


static inline int list_is_last(const struct list_head *list,


const struct list_head *head)


{


return list->next == head;


}


下面两个接口都是判断head这个链表是不是为一个空链表(也就是只进行过初始化操作或者是刚申请的一个变量)。


  1. static inline int list_empty(const struct list_head *head)
  2. {
  3.     return head->next == head;
  4. }
  5. static inline int list_empty_careful(const struct list_head *head)
  6. {
  7.     struct list_head *next = head->next;
  8.     return (next == head) && (next == head->prev);
  9. }



list_empty()函数和list_empty_careful()函数都是用来检测链表是否为空的。但是稍有区别的就是第一个链表使用的检测方法是判断表头的结点的下一个结点是否为其本身,如果是则返回为1,否则返回0。第二个函数使用的检测方法是判断表头的前一个结点和后一个结点是否为其本身,如果同时满足则返回0,否则返回值为1。


这主要是为了应付另一个cpu正在处理同一个链表而造成next、prev不一致的情况。但代码注释也承认,这一安全保障能力有限:除非其他cpu的链表操作只有list_del_init(),否则仍然不能保证安全,也就是说,还是需要加锁保护。


下面的这个函数是用来判断head这个链表是不是只有一个成员结点(不算带头结点的那个head)。


  1. static inline int list_is_singular(const struct list_head *head)
  2. {
  3.     return !list_empty(head) && (head->next == head->prev);
  4. }


 


 

 

READ_ONCE 和WRITE_ONCE说明

READ_ONCE

#define READ_ONCE(x) __READ_ONCE(x, 1)

#define __READ_ONCE(x, check) \
({ \
union { typeof(x) __val; char __c[1]; } __u; \
if (check) \
__read_once_size(&(x), __u.__c, sizeof(x)); \
else \
__read_once_size_nocheck(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})

static __always_inline
void __read_once_size(const volatile void *p, void *res, int size)
{
__READ_ONCE_SIZE;
}

static __always_inline
void __read_once_size_nocheck(const volatile void *p, void *res, int size)
{
__READ_ONCE_SIZE;
}

#define __READ_ONCE_SIZE \
({ \
switch (size) { \
case 1: *(__u8 *)res = *(volatile __u8 *)p; break; \
case 2: *(__u16 *)res = *(volatile __u16 *)p; break; \
case 4: *(__u32 *)res = *(volatile __u32 *)p; break; \
case 8: *(__u64 *)res = *(volatile __u64 *)p; break; \
default: \
barrier(); \
__builtin_memcpy((void *)res, (const void *)p, size); \
barrier(); \
} \
})

barrier() __asm__ __volatile__("": : :"memory")

WRITE_ONCE

#define WRITE_ONCE(x, val) \
({ \
union { typeof(x) __val; char __c[1]; } __u = \
{ .__val = (__force typeof(x)) (val) }; \
__write_once_size(&(x), __u.__c, sizeof(x)); \
__u.__val; \
})

static __always_inline void __write_once_size(volatile void *p, void *res, int size)
{
switch (size) {
case 1: *(volatile __u8 *)p = *(__u8 *)res; break;
case 2: *(volatile __u16 *)p = *(__u16 *)res; break;
case 4: *(volatile __u32 *)p = *(__u32 *)res; break;
case 8: *(volatile __u64 *)p = *(__u64 *)res; break;
default:
barrier();
__builtin_memcpy((void *)p, (const void *)res, size);
barrier();
}
}

说明

在某些情况下CPU对内存中变量读写并不是一次完成的,这可能会出现竞争。而READ_ONCE和WRITE_ONCE实现对变量一次性读取和一次性写入。