在Linux下链表使用介绍一:list_head_咸鱼弟的博客-CSDN博客_linux链表带数据链表头
在看这个知识点的时候我相信大家对数据结构已经有所了解,尤其是对链表的了解,因此在这里不过多讲解传统的链表基本知识,这里只给出通常双向链表的数据结构。
struct list_node{
struct list_node *next,*prev;
type1 m1;
type2 m2;
};
在Linux内核编程中为什么不使用通常的链表呢?因为它的缺点是:对每种类型串起来的链表,我们需要编写一系列的函数实现对链表的操作,在复杂的项目中,结构体会很多,对它们实现链表功能,那需要我们为每种类型的链表编写一套函数,我们时间会浪费,可维护性差。在Linux中内核中链表是如何使用的呢?
Linux中使用了侵入式链表“list_head”
struct list_head {
struct list_head *next, *prev;
};
在我们需要某种数据结构的双向链表时,可以这样定义:
struct kobject {
const char *name;
unsigned int age;
struct list_head entry;
};
这个结构体类型生成的双向链表结构如下图所示:

那我们如何构建起这个链表,如何对链表中的数据访问,如何遍历链表呢?
1、必须专门定义一个专有链表头,并初始化:
struct list_head ListHead;
INIT_LIST_HEAD(&ListHead);
其中INIT_LIST_HEAD的定义为
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); \
(ptr)->prev = (ptr); \
} while (0)
2、用list_add/list_add_tail 来添加链表节点
list_add(&entry, &ListHead);
其中list_add,list_add_tail定义为
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
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;
}
void list_add_tail(struct list_head *new, struct list_head *head);
static __inline__ void list_add_tail(struct list_head *_new, struct list_head *head)
{
__list_add(_new, head->prev, head);
}
/*
* 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)
{
next->prev = _new;
_new->next = next;
_new->prev = prev;
prev->next = _new;
}
3、遍历链表list_for_each/list_for_each_safe
我们知道list_for_each_entry会用到list_entry,而list_entry用到container_of,而container_of中有offsetof,所以从offsetof讲起。
offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
理解offsetof的关键在于&((TYPE *)0)->MEMBER,几乎可以说只要理解了这一部分,后面的几个函数都能够解决,那么我们看看这一部分究竟完成了怎样的工作。根据优先级的顺序,最里面的小括号优先级最高,TYPE *将整型常量0强制转换为TYPE型的指针,且这个指针指向的地址为0,也就是将地址0开始的一块存储空间映射为TYPE型的对象,接下来再对结构体中MEMBER成员进行取址,而整个TYPE结构体的首地址是0, 这里获得的地址就是MEMBER成员在TYPE中的相对偏移量 。再将这个偏移量强制转换成size_t型数据(无符号整型)。
接下来讲讲container_of
/*
* container_of - cast a member of a structure out to the containing structure
* @ptr: the pointer to the member.
* @type: the type of the container struct this is embedded in.
* @member: the name of the member within the struct.
*
*/
#define container_of(ptr, type, member) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );})
首先可以看出container_of被预定义成一个函数,函数的第一句话,通过((type *)0)->member定义一个MEMBER型的指针__mptr,这个指针指向ptr,所以第一句话获取到了我们要求的结构体,它的成员member的地址,接下来我们用这个地址减去成员member在结构体中的相对偏移量,就可以获取到所求结构体的地址, (char *)__mptr - offsetof(type,member)就实现了这个过程,最后再把这个地址强制转换成type型指针, 就获取到了所求结构体指针, define预定义返回最后一句话的值, 将所求结构体指针返回。
所以整个container_of的功能 就是通过指向结构体成员member的指针ptr获取指向整个结构体的指针 。container_of清楚了,那list_entry就更是一目了然了。
/*list_entry /**
* list_entry - get the struct for this entry
* @ptr: the &;struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) /
container_of/(ptr,type,member)
功能等同于container_of。接下来分析我们最终想要知道的list_for_each_entry的实现过程。
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry(pos, head, member) /
for (pos = list_entry((head)->next, typeof(*pos), member); /
&pos->member != (head); /
pos = list_entry(pos->member.next, typeof(*pos), member))
在理解了list_entry的基础上分析list_for_each_entry本来是一件比较轻松的事情,但在这里还是要强调一下双向链表及链表头的概念,否则对list_for_each_entry的理解还是一知半解。建立一个双向链表通常有一个独立的用于管理链表的链表头,链表头一般是不含有实体数据的,必须用INIT_LIST_HEAD()进行初始化,表头建立以后,就可以将带有数据结构的实体链表成员加入到链表中,链表头和链表的关系如图所示:
链表头和链表的关系清楚了,我们才能完全理解list_for_each_entry。list_for_each_entry被预定义成一个for循环语句,for循环的第一句话获取(head)->next指向的member成员的数据结构指针,也就是将pos初始化为除链表头之外的第一个实体链表成员,for的第三句话通过pos->member.next指针遍历整个实体链表,当pos->member.next再次指向我们的链表头的时候跳出for循环。整个过程没有对链表头进行遍历(不需要被遍历),所以使用list_for_each_entry遍历链表必须从链表头开始。 因此可以看出, list_for_each_entry的功能就是遍历以head为链表头的实体链表,对实体链表中的数据结构进行处理 。
接下来看看list_for_each_entry_safe
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_struct within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) /
for (pos = list_entry((head)->next, typeof(*pos), member), /
n = list_entry(pos->member.next, typeof(*pos), member); /
&pos->member != (head); /
pos = n, n = list_entry(n->member.next, typeof(*n), member))
相比于list_for_each_entry,list_for_each_entry_safe用指针n对链表的下一个数据结构进行了临时存储,所以如果在遍历链表的时候可能要删除链表中的当前项,用list_for_each_entry_safe可以安全的删除,而不会影响接下来的遍历过程(用n指针可以继续完成接下来的遍历, 而list_for_each_entry则无法继续遍历)。
1.1 container_of
/* 原型 */
#define container_of(ptr, type, member)
/* 功能 */
已知某一个成员变量的名字、指针和结构体类型的情况下,计算结构体的指针,也就是计算结构体的起始地址。
/* 参数 */
ptr: 某一个成员的指针
type: 结构体类型
member:成员变量名字
1.2 offsetof
/* 原型 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* 功能 */
在已知某一个成员变量的名字和结构体类型的情况下,计算该成员相对于结构体的起始地址的偏移量
/* 参数 */
TYPE: 结构体类型名称
MEMBER:结构体中成员
/* 返回 */
该成员相对于结构体的起始地址的偏移量
1.3 LIST_HEAD_INIT
/* 原型 */
#define LIST_HEAD_INIT(name) { &(name), &(name); }
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
name:结点名字
1.4 LIST_HEAD
/* 原型 */
#define LIST_HEAD(name)
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
name:结点名字
1.5 INIT_LIST_HEAD
/* 原型 */
static inline void INIT_LIST_HEAD(struct list_head *list)
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
list:结点名字
1.6 list_add
//原型
static inline void list_add(struct list_head *new, struct list_head *head)
//功能
添加结点new到链表头
//参数
new:新的结点
head:链表头
1.7 list_add_tail
//原型
static inline void list_add_tail(struct list_head *new, struct list_head *head)
//功能
添加结点new到链表尾
//参数
new:新的结点
head:链表头
1.8 list_del
//原型
static inline void list_del(struct list_head *entry)
//功能
删除结点entry,将entry的prev指向LIST_POISON1,next指向LIST_POISON2.
//参数
entry:被删除结点
//说明
指向LIST_POISON不会引起缺页错误。
1.9 list_move
//原型
static inline void list_move(struct list_head *list, struct list_head *head)
//功能
先将list节点从原链表中删除,然后将其添加到head链表的表头
//参数
list:移除结点
head:链表表头。
1.10 list_empty
//原型
static inline int list_empty(const struct list_head *head)
//功能
判断head链表是否为空链表,是返回1,否则返回为0;
//参数
head:链表表头。
//返回
0或1
1.11 list_entry
//原型
#define list_entry(ptr, type, member)
//功能
获取type类型结构体的起始指针
//参数
ptr:type类型的结构体中member成员的指针
type:结构体类型
member:结构体中成员
//返回
结构体的起始指针
1.12 list_for_each
//原型
#define list_for_each(pos, head)
//功能
从head节点开始(不包括head节点!)遍历它的每一个节点!
//参数
pos:循环指针
head:链表头
1.13 list_for_each_safe
//原型
#define list_for_each_safe(pos, n, head)
//功能
从head节点开始(不包括head节点!)遍历它的每一个节点!它用n先将下一个要遍历的节点保存起来,
防止删除本节点后,无法找到下一个节点,而出现错误!
//参数
pos:循环指针
n:缓存节点
head:链表头
1.14 list_for_each_entry
//原型
#define list_for_each_entry(pos, head, member)
//功能
已知指向某个结构体的指针pos,以及指向它中member成员的指针head,从下一个结构体开始向后遍历这个结构体链
//参数
pos:结构体指针
head:链表头
member:结构体成员
1.15 list_for_each_entry_safe
//原型
#define list_for_each_entry_safe(pos, n, head, member)
//功能
先保存下一个要遍历的节点!从head下一个节点向后遍历链表。
//参数
pos:结构体指针
n:缓冲节点
head:链表头
member:结构体成员
以上列出了常用的一些链表操作接口,更多接口定义请参考内核代码 /include/linux/list.h
1.1 container_of
/* 原型 */
#define container_of(ptr, type, member)
/* 功能 */
已知某一个成员变量的名字、指针和结构体类型的情况下,计算结构体的指针,也就是计算结构体的起始地址。
/* 参数 */
ptr: 某一个成员的指针
type: 结构体类型
member:成员变量名字
1.2 offsetof
/* 原型 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
/* 功能 */
在已知某一个成员变量的名字和结构体类型的情况下,计算该成员相对于结构体的起始地址的偏移量
/* 参数 */
TYPE: 结构体类型名称
MEMBER:结构体中成员
/* 返回 */
该成员相对于结构体的起始地址的偏移量
1.3 LIST_HEAD_INIT
/* 原型 */
#define LIST_HEAD_INIT(name) { &(name), &(name); }
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
name:结点名字
1.4 LIST_HEAD
/* 原型 */
#define LIST_HEAD(name)
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
name:结点名字
1.5 INIT_LIST_HEAD
/* 原型 */
static inline void INIT_LIST_HEAD(struct list_head *list)
/* 功能 */
初始化一个结点名字为name的双向循环链表的头结点
/* 参数 */
list:结点名字
1.6 list_add
//原型
static inline void list_add(struct list_head *new, struct list_head *head)
//功能
添加结点new到链表头
//参数
new:新的结点
head:链表头
1.7 list_add_tail
//原型
static inline void list_add_tail(struct list_head *new, struct list_head *head)
//功能
添加结点new到链表尾
//参数
new:新的结点
head:链表头
1.8 list_del
//原型
static inline void list_del(struct list_head *entry)
//功能
删除结点entry,将entry的prev指向LIST_POISON1,next指向LIST_POISON2.
//参数
entry:被删除结点
//说明
指向LIST_POISON不会引起缺页错误。
1.9 list_move
//原型
static inline void list_move(struct list_head *list, struct list_head *head)
//功能
先将list节点从原链表中删除,然后将其添加到head链表的表头
//参数
list:移除结点
head:链表表头。
1.10 list_empty
//原型
static inline int list_empty(const struct list_head *head)
//功能
判断head链表是否为空链表,是返回1,否则返回为0;
//参数
head:链表表头。
//返回
0或1
1.11 list_entry
//原型
#define list_entry(ptr, type, member)
//功能
获取type类型结构体的起始指针
//参数
ptr:type类型的结构体中member成员的指针
type:结构体类型
member:结构体中成员
//返回
结构体的起始指针
1.12 list_for_each
//原型
#define list_for_each(pos, head)
//功能
从head节点开始(不包括head节点!)遍历它的每一个节点!
//参数
pos:循环指针
head:链表头
1.13 list_for_each_safe
//原型
#define list_for_each_safe(pos, n, head)
//功能
从head节点开始(不包括head节点!)遍历它的每一个节点!它用n先将下一个要遍历的节点保存起来,
防止删除本节点后,无法找到下一个节点,而出现错误!
//参数
pos:循环指针
n:缓存节点
head:链表头
1.14 list_for_each_entry
//原型
#define list_for_each_entry(pos, head, member)
//功能
已知指向某个结构体的指针pos,以及指向它中member成员的指针head,从下一个结构体开始向后遍历这个结构体链
//参数
pos:结构体指针
head:链表头
member:结构体成员
1.15 list_for_each_entry_safe
//原型
#define list_for_each_entry_safe(pos, n, head, member)
//功能
先保存下一个要遍历的节点!从head下一个节点向后遍历链表。
//参数
pos:结构体指针
n:缓冲节点
head:链表头
member:结构体成员
以上列出了常用的一些链表操作接口,更多接口定义请参考内核代码 /include/linux/list.h
例子:
typedef struct {
int id;
char *name;
int age;
int score;
struct list_head node;
} student_info_t;
struct list_head* student_info_queue = NULL;
初始化
if (student_info_queue == NULL) {
student_info_queue = (struct list_head *) malloc(sizeof(struct list_head));
if (student_info_queue == NULL; return;
INIT_LIST_HEAD(student_info_queue);
}
加入队列
student_info_t * one_student = calloc(1, sizeof(student_info_t));
if (one_student == NULL) return;
one_student->id = 20200101;
one_student->name = malloc(20);
if (one_student->name == NULL) {
free(one_student);
return;
}
strcpy(one_student->name, "zhangsan");
one_student->age = 20;
one_student->score = 80;
list_add_tail(&one_student->node, student_info_queue);
删除节点
student_info_t *entry *next;
list_for_each_entry_safe(entry, next, student_info_queue, node) {
if (entry->id == 20200101)
list_del(&entry->node);
}
本文详细介绍了Linux内核中如何使用侵入式链表`list_head`,包括如何定义、初始化链表,以及如何通过`list_add`、`list_add_tail`添加节点,`list_for_each_entry`、`list_for_each_entry_safe`遍历链表。此外,还解释了`container_of`、`offsetof`等关键宏的作用,帮助理解链表操作的实现细节。
1592

被折叠的 条评论
为什么被折叠?



