数据结构linux 内核的list

Posted liangchaoxi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构linux 内核的list相关的知识,希望对你有一定的参考价值。

目录

​原理​

​前言​

​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

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

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

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

\'【数据结构】linux

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

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

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

 

 

\'【数据结构】linux

删除操作 

删除两个元素之间的元素

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列表中除了list本身插入到prev和next之间

\'【数据结构】linux

 

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

\'【数据结构】linux

 

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

\'【数据结构】linux

list_entry宏

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

\'【数据结构】linux

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

( container_of介绍​)

4  注意事项

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

 

5  使用实例

\'【数据结构】linux

使用

连接:​​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以上是关于数据结构linux 内核的list的主要内容,如果未能解决你的问题,请参考以下文章

数据结构linux 内核的list

Linux内核中的list_head

20179209《Linux内核原理与分析》第十二周作

Linux(内核剖析):14---内核数据结构之链表(struct list_head)

《Linux内核 核心知识全解析(完)》

linux驱动开发要知道的那些知识------list内核链表