数据结构linux 内核的list
Posted liangchaoxi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构linux 内核的list相关的知识,希望对你有一定的参考价值。
目录
原理
前言
2 定义
使用
(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点
原理
前言
linux kernel里的很多数据结构都很经典, list链表就是其中之一,本文将从以下几方面介绍list链表:list的定义、list提供的操作方法、注意事项、使用实例
linux kernel里的很多数据结构都很经典, list链表就是其中之一
本篇要介绍的内容:
- list的定义
- list提供的操作方法
- 注意事项
- 使用实例
list链表
1 List 所在文件
List的所有操作可以在 include/linux/list.h找到;
List head的定义可以在 include/linux/types.h找到;
2 定义
实际上这就是一个双向循环链表, 且有一个头指针
list head的定义:
这个定义中只有前向和后向指针,没任何的数据部分, 那我们基本上就知道了, 它不是被单独使用的,而是把它嵌入到用户定义的struct中, 将用户定义的数据结构串起来,作成list;
思想很巧妙, 对用户定义的数据结构侵入性很小, 实现了c++中std::List模板的功能;
虽然这个定义是叫head, 但其实嵌入到用户定义的数据结构中的也是这个.(需要注意的一点是,头结点head是不使用的,这点需要注意。使用list_head组织的链表的结构如下图所示:)
3 list提供的操作方法
初始化
//静态初始化
//调用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);
}
在尾部插入,在最后一个元素和头指针间插入, 因为是循环链表嘛~
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
删除操作
删除两个元素之间的元素
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队列仅包括余下的元素
合并操作
将list列表中除了list本身插入到prev和next之间
将一个列表插入到另一个列表的头部
将一个列表插入到另一个列表的尾部
list_entry宏
按之前说的, 这个list_head都有要嵌入到用户定义的struct中,这个宏就是由这个list_head ptr来获取当前所处的struct对象的指针, 用了linux的经典宏定义 container_of
一堆宏定义, 用来各种遍历, 获取entry
( container_of介绍)
4 注意事项
只说一个,就是多线程操作同一个list, 还是需要加锁
5 使用实例
使用
连接:http://blog.chinaunix.net/uid-22566367-id-2182866.html
(二)结构体初始化
结构体初始化函数:
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
初始化结构体list指向自己本身。
对于(一)结构体定义和(二)结构体初始化来说,最终的效果是一样的,都是将一个
struct list_head变量指向自己本身。
可以写一个小的程序测试一下
- /*test.c*/
- #include <stdio.h>
- struct list_head {
- struct list_head *next, *prev;
- };
- #define LIST_HEAD_INIT(name) { &(name), &(name) }
- #define LIST_HEAD(name) \\
- struct list_head name = LIST_HEAD_INIT(name)
- static inline void INIT_LIST_HEAD(struct list_head *list)
- {
- list->next = list;
- list->prev = list;
- }
- int main()
- {
- LIST_HEAD(temp);
- printf("%p %p %p\\n", (&temp)->prev, (&temp)->next, &temp);
- INIT_LIST_HEAD(&temp);
- printf("%p %p %p\\n", (&temp)->prev, (&temp)->next, &temp);
- return 0;
- }
- 运行结果:
- ^_^[sunny@sunny-laptop ~/DS]11$ ./a.out
- 0xbf8191a8 0xbf8191a8 0xbf8191a8
- 0xbf8191a8 0xbf8191a8 0xbf8191a8
- ^_^[sunny@sunny-laptop ~/DS]12$
可以看出他们完毕之后的地址都是一样的。
(三)增加结点
附带知识:
- 内联函数 inline
- 在c 中,为了解决一些频繁调用的小函数而大量消耗栈空间或者是叫栈内存的问题,特别的引入了inline修饰符,表示为内联函数。
- 内联函数使用inline关键字定义,
- 并且函数体和声明必须结合在一起,
- 否则编译器将他作为普通函数对待。
- inline void function(int x); //仅仅是声明函数,没有任何效果
- inline void function(int x) //正确
- {
- return x;
- }
增加结点的话,有两种方式:头插法和尾插法。
我们调用的话就调用
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中定义的:
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next以上是关于数据结构linux 内核的list的主要内容,如果未能解决你的问题,请参考以下文章