内核链表实际上就是双向循环链表的升级版,因为双向循环链表的数据域与指针域是在同一个结构体中的,数据域与指针域始终是捆绑在一块的。由于不同的项目,数据域的结构可能是不同的,这样的话,由于数据域的不同,所以之前写好的链表操作就不能直接拿来使用,这样就不具备通用性。所以,大佬们就将数据域与指针域给分离出来,然后底层都实现封装好了。不管数据域是什么,我们只对指针域进行操作。这样的话,不管是什么项目,关于链表操作,我们拿内核链表直接用就行了,灰常的方便、通用。
双向链表的结构体是这样的:
struct link{
datatype data; //数据域
struct link *prev; //指针域 前面那个节点的地址
struct link *next; //指针域 后面那个节点的地址
};
内核链表的结构体是这样的:
struct list_head{ //指针域的结构体
struct list_head *next, *prev;
};
在 struct list_head 的基础上 加入 数据域 即可
struct kernel {
datatype data; //数据域
struct list_head list; //指针域的结构体
};
将数据域剥离,只留下指针域
将指针域打包成一个结构体struct list_head,我们只需要对指针域结构体进行操作就行了。
内核链表分析:
typedef int datatype;
struct list_head{
struct list_head *next,*prev;
};
typedef struct kernel{
datatype data;
struct list_head list;
} kernel_t,*pkernel_t;
节点初始化:
第一种:
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \ //这个 \ 表示语句太长,后面的语句写在下一行
struct list_head name = LIST_HEAD_INIT(name)
分析:
struct list_head name = { &(name), &(name) }; //这种方式是将链表定义在栈中,而不是定义在堆中,一般不用
第二种:
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
===============================================================================
节点的插入 :
//注意:开头带下划线的函数,都是系统内核函数,一般我们是不调用这样的函数的,这样的函数是给系统内部用的。
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;
}
解释:
new作为桥梁 将prev 和 next 链接起来了,new在中间,即将new节点插入到prev节点和next节点之间。其中new prev next 事先有没有关系 无所谓。
第一种:
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
解释:
new插在了head后面,即new节点插到head节点和head->next节点之间
第二种:
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
解释:
new插在了head前面,即new节点插到head节点和head->next节点之间。
==================================================================================
节点的删除:
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
解释:
将prev 和 next这两个节点直接链接起来 next 在prev 的后面
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
解释:
将entry剪切出来了
第一种:
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1; //NULL
entry->prev = LIST_POISON2; //NULL
}
/*虚拟内存中,地址0x0000 0000到0x0804 8000 之间为保留区,这块空间里面都为NULL,所以,下面的地址0x00100100、0x00200200都在保留区的区间内,所以它们都为NULL,所以宏定义LIST_POISON1、LIST_POISON2都为NULL指针*/
#define LIST_POISON1 ((void *) 0x00100100 + 0)
#define LIST_POISON2 ((void *) 0x00200200 + 0)
解释:
1》将entry剪切出来了
2》entry将prev和next指向NULL
第二种:
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
解释:
1》将entry剪切出来了
2》entry自己指向自己
================================================
节点的移动:
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
解释:
1》将list剪切出来了
2》list插在了head后面
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
解释:
1》将list剪切出来了
2》list插在了head前面
=========================================================================
难点:根据小结构体(即 struct list_head)寻找大结构体(即struct kernel):
struct kernel {
datatype data; //数据域
struct list_head list; //指针域的结构体
};
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
说明:
ptr : 小结构体地址 (从外部传入)
type :大结构体类型
member : 大结构体中小结构体的名字
代码分析:
container_of(ptr, type, member)
const typeof( ((type *)0)->member ) *__mptr = (ptr);
分析:
我们先来分析: const typeof( ((type *)0)->member ) *__mptr = (ptr); 首先typeof( ((type *)0)->member ) *是一个指针类型,其中typoof() 关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。它可以取得变量的类型,或者表达式的类型。所以,typeof( ((type *)0)->member ) 就表示取( (type *)0 )->member的类型。再来分析( (type *)0 )->membe,type是由我们手动填写的大结构体类型,其中0就是0x0000 0000地址,所以(type *)0,就是将0x0000 0000地址强制转换为type *类型,也就是说,从地址0x0000 0000开始扩展,扩展成type类型大小的空间。这就像:
int a;
char *p;
(int *)p = &a;
将a变量的地址赋值给p指针,此时p就是a变量的首地址,但是由于p指针为char类型,所以p指针指向的变量所在空间只能是char大小的空间,所以p = &a;只能指向a变量的前面char大小的空间,而不能指向整个int大小空间,所以我们使用强制转换(int *),这样就可以指向整个int大小空间。如果a变量的地址为0x0000 0000,即&a == 0x0000 0000,那么此时(int *)p = &a;就表示为:指针p 指向 以地址0x0000 0000为首地址,其空间为int大小,变量名字为a的变量。其中,由于p就是&a,所以p == 0x0000 0000,所以(int *)p = &a,我们可以改写为:(int *) 0x0000 0000,它等价于(int *)p = &a;
所以,(type *)0 就等价于:
type a; //假设a的地址等于0x0000 0000,
(type *)p = &a;
即:指针p 指向 以地址0x0000 0000为首地址,其空间为type类型大小,变量名字为a的变量。
由于type是大结构体类型,所以( (type *)0 )->member表示大结构体下面的member成员,即大结构体下面的小结构体的名字(这个member也是需要我们来手动填写)。 也就是(kernel_t *)0)->list。
所以 const typeof( ((type *)0)->member ) *__mptr = (ptr);表示:定义一个名字叫__mptr的指针,该指针类型为((type *)0)->member类型,即小结构体的类型。ptr为小结构体的地址,将小结构体的地址交给__mptr指针保存。其中(ptr)的括号可以不要。
所以,const typeof( ((type *)0)->member ) *__mptr = (ptr); 等价于 const struct list_head *__mptr = (ptr);
我们再来分析(type *)( (char *) __mptr - offsetof(type,member) );
首先我们来分析offsetof(type,member)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
分析:
(size_t) &((kernel_t *)0)->list
---> data所在区域的字节数 (对齐)
分析:
(TYPE *)0)->MEMBER,type :大结构体类型,member : 大结构体中小结构体的名字,即(kernel_t *)0)->list(前面已描述过), &(TYPE *)0)->MEMBER,即&(kernel_t *)0)->list,取小结构体的地址(注意:这里的小结构体地址并不是真正的小结构体首地址,而是在0x0000 0000基础上来计算的小结构体地址,它的作用是用来计算大结构体中数据域的大小,真正的小结构体首地址是ptr)
我们可以发现,从地址&list到地址0x0000 0000,这块空间长度就是大结构体的数据域的空间长度,所以数据域的空间长度为 &list - 0x0000 0000 = &list,所以&list为数据域空间的长度。由于地址在32位系统中是4个字节,而在64位系统中为8个字节,不同的操作系统,地址的长度为一个字长。所以需要在地址前面加上强制转换(unsinged long), unsinged long的长度为一个字长。
所以写成(size_t) &((kernel_t *)0)->list,其中size_t 就是 unsinged long。(size_t) &((kernel_t *)0)->list此时就表示数据域的字节数长度了(一个地址存储一个字节),如:地址0x0000 0008 - 0x0000 0000表示它们之间有8个字节数据。
我们再来看 (char *) __mptr - offsetof(type,member) :
首先对于(char *) __mptr ,__mptr这个指针保存的是真正的小结构体的首地址,我们将其强制转换为char *类型,目的如下:
int *p1;
char *p2
则p1 - 1表示的是p1地址减4,因为p1指针是int类型。而p2 - 1则是p2地址减1,因为p2指针是char类型。所以(char *) __mptr - offsetof(type,member),就表示真正的小结构体地址减去数据域的字节数长度,便可得到真正的数据域的首地址了,大结构体的数据域的首地址便是大结构体的首地址,这样我们就可以得到大结构体的首地址了。由于该地址是指向大结构体的,所以该地址形成的指针的指针类型为type *类型(即:kernel_t *类型),所以该地址需要强制转换为type *类型
(type *)( (char *) __mptr - offsetof(type,member) );
例:小结构体地址为0x0000 1234,即__mptr == 0x0000 ef36,而数据域的空间长度为0x0000 0016,即offsetof(type,member) == 0x0000 0016。那么大结构体的数据域首地址为:
(char *) __mptr - offsetof(type,member) == 0x0000 ef36 - 0x0000 0016 == 0x0000 ef20,所以,大结构体的地址就为0x0000 ef20,指针为(type *)0x0000 ef20。
例子:
list_entry: 通过小结构体地址pos ,得到其所在的大结构体地址node
例子:
struct list_head *pos = 0xabcdef;
pkernel_t node = list_entry(pos,kernel_t,list);
=============================================================
链表的遍历:
//这个宏定义是一个for循环
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
说明:
pos : 小结构体指针 中间变量
head : 头节点小结构体指针 变量
分析:
小结构体 正序 遍历 操作
===========================================================
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
说明:
pos : 小结构体指针 中间变量
head : 头节点小结构体指针 变量
分析:
小结构体 逆序 遍历 操作
=============================================================
/* head为头节点中小结构体的地址(因为head->next,只有小结构有next指针域),而head->next,则为头节点的下一个节点的小结构体地址,也就是第一个节点的小结构体地址。list_entry则取到第一个节点的大结构体地址*/
#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))
说明:
pos : 大结构体地址 中间变量
head : 头节点小结构体指针 变量
member : 大结构中小结构名字 list
分析:
大结构体 正序 遍历 操作
============================================================
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
说明:
pos : 大结构体地址 中间变量
head : 头节点小结构体指针 变量
member : 大结构中小结构名字 list
分析:
大结构体 逆序 遍历 操作
==========================================================================
内核链表的操作:
/* 内核链表的 增 删 改 查 */
/*
* @bref 创建新的节点
* @param d : 数据
* @retval 新节点地址
*/
static pkernel_t create_node(datatype d)
{
pkernel_t p = (pkernel_t)malloc(sizeof(kernel_t));
if(p==NULL){
perror("malloc error");
return NULL;
}
p->data = d;
INIT_LIST_HEAD(&p->list); //让节点自己指向自己
return p;
}
/*
* @bref 初始化
* @param None
* @retval 头节点地址
*/
pkernel_t kernel_init(void)
{
//在堆中开辟空间
pkernel_t p = (pkernel_t)malloc(sizeof(kernel_t));
if(p==NULL){
perror("malloc error");
return NULL;
}
INIT_LIST_HEAD(&p->list);
return p;
}
/*
* @bref 头插
* @param p : 头节点
* @param d : 数据
* @retval None
*/
void kernel_insert_head(pkernel_t p,datatype d)
{
//创建新的节点
pkernel_t node = create_node(d);
if(node == NULL)
return;
//将新的节点插入到头的后面
list_add(&node->list, &p->list);
}
/*
* @bref 尾插
* @param p : 头节点
* @param d : 数据
* @retval None
*/
void kernel_insert_tail(pkernel_t p,datatype d)
{
//创建新的节点
pkernel_t node = create_node(d);
if(node == NULL)
return;
//将新的节点插入到头的前面
list_add_tail(&node->list, &p->list);
}
/*
* @bref 删除
* @param p : 头节点
* @param d : 需要删除的数据
* @retval None
*/
void kernel_del(pkernel_t p,datatype d)
{
struct list_head *pos = NULL;
pkernel_t node = NULL;
// for (pos = (head)->next; pos != (head); pos = pos->next)
list_for_each(pos, &p->list)
{
//通过小结构体地址 得到 大结构体地址
node = list_entry(pos,typeof(*p),list);
// 判断
if(node->data == d){
//pos往前移动一个
pos = pos->prev;
//将node节点 截切 并删除
list_del_init(&node->list);
//释放
free(node);
}
}
}
/*
* @bref 修改(替换)
* @param p : 头节点
* @param old : 需要修改的数据
* @param new : 新数据
* @retval None
*/
void kernel_update(pkernel_t p,datatype old,datatype new)
{
pkernel_t pos = NULL;
pkernel_t node = NULL;
//找寻old所在节点
list_for_each_entry(pos, &p->list, list)
{
if(pos->data == old){
//创建新的节点
node = create_node(new);
if(node==NULL)
return;
//node 替换 pos节点
list_replace_init(&pos->list,&node->list);
// 释放pos 空间
free(pos);
//关键一步
pos = node; /* 为了让程序能够继续往下走,因为前面 free(pos);已经把pos节点给释放掉了,而pos是for循环的中间值,如果没有这一步,那么for无法继续循环下去。*/
}
}
}
/*
* @bref 正序遍历
* @param p : 头节点
* @retval None
*/
void display(pkernel_t p)
{
printf("内核链表正序遍历结果为:");
pkernel_t pos = NULL;
/* for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))*/
list_for_each_entry(pos,&p->list, list) //用大结构体遍历
printf("%d ",pos->data);
printf("\n");
}
/*
* @bref 逆序遍历
* @param p : 头节点
* @retval None
*/
void pre_display(pkernel_t p)
{
printf("内核链表逆序遍历结果为:");
pkernel_t pos = NULL;
list_for_each_entry_reverse(pos,&p->list, list) //用大结构体遍历
printf("%d ",pos->data);
printf("\n");
}
/************************************
*display : 内核链表的正序遍历
*@head : 头节点
*/
void display(pkernel_t head)
{
printf("正序遍历的结果为:");
struct list_head *pos = NULL; //for中的中间变量
pkernel_t node = NULL;
//for (pos = (head)->next; pos != (head); pos = pos->next)
list_for_each(pos,&head->list) //用小结构体遍历
{
node = list_entry(pos, typeof(*head), list);
printf("%d ",node->data);
}
printf("\n");
}