【数据结构】linux 内核的list
目录
(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点
原理
前言
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提供的操作方法
初始化
//静态初始化
#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);
}
在尾部插入,在最后一个元素和头指针间插入, 因为是循环链表嘛~
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介绍:https://blog.csdn.net/rosetta/article/details/90751028)
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;
- }
- /*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;
- }
(四)删除结点
//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 * 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;
- }
- static inline void list_del_init(struct list_head *entry)
- {
- __list_del(entry->prev, entry->next);
- INIT_LIST_HEAD(entry);
- }
(五)替换结点
- 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_replace_init(struct list_head *old,
- struct list_head *new)
- {
- list_replace(old, new);
- INIT_LIST_HEAD(old);
- }
(六)结点搬移
(七)检测是否为最后节点、检测链表是否为空、检测链表是不是有一个成员结点
- static inline int list_empty(const struct list_head *head)
- {
- return head->next == head;
- }
- static inline int list_empty_careful(const struct list_head *head)
- {
- struct list_head *next = head->next;
- return (next == head) && (next == head->prev);
- }
- static inline int list_is_singular(const struct list_head *head)
- {
- return !list_empty(head) && (head->next == head->prev);
- }
本文大部分摘自:http://blog.itpub.net/31555491/viewspace-2216449/
推荐文章:https://blog.csdn.net/wanshilun/article/details/79747710
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实现对变量一次性读取和一次性写入。
原文链接:https://blog.csdn.net/cloudblaze/article/details/51676139
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!