linux内核链表的实现和使用和详解


首先,内核链表的头文件,在linux内核的 /include/linux 下的 List.h ,把List.h 复制出来,黏贴到 工程下,就可以直接用内核链表的一些宏和函数。

以下介绍内核链表的一些宏和函数,已经他的实现方式和使用方法。

(1)什么是内核链表:

如图:内核链表一般就是 在一个结构体 有一个结构体成员变量,该结构体成员变量只有 next 和 prev两个指针,分别指向下一个结点和上一个结构,就像一条绳子串起所有的结构体,这样做的好处,就是可以用内核链表来串起各个不同类型的结构体。

linux内核链表的实现和使用和详解_第1张图片

(2)内核链表的初始化:

#include "kernel_list.h"	//我把内核中的 /include/linux/List.h 改名为 kernel_list.h
#include   


struct Person
{
	char name[20];
	int age;
	struct list_head mylist;	//其中 list_head 在kernel_list.h 中有定义,这个结构体就只有两个结构体指针(next和prev)	
};

int main()
{
	struct Person *p;
	INIT_LIST_HEAD(&(p->mylist));		//主要是使用 INIT_LIST_HEAD()这个宏初始化那个链表。
	return 0;
}

以下是宏INIT_LIST_HEAD()的实现方法,仅作了解就行,因为引入了 /include/linux/List.h 后,是可以直接使用 INIT_LIST_HEAD的

#define INIT_LIST_HEAD(ptr)    \           //ptr是指针
do {  						\
     (ptr)->next = (ptr); (ptr)->prev = (ptr);  \
   } while (0)	

作用是把 指针指向的结构体 中的next和prev都指向自己。


(3)插入内核链表结点:

在结点head之后插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

static inline void list_add(struct list_head *new, struct list_head *head)    //在结点之后插入新结点
{
	__list_add(new, head, head->next);	//__list_add()这个函数,等等就讲
}


在结点head之前插入新节点。(这是实现原理,仅作了解就行,后面会有怎么用的代码)

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}
linux内核链表的实现和使用和详解_第2张图片
访问顺序和新增插入顺序(如在head之前或之后插入)有相应的函数。

__list_add()的实现:
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;
}
struct list_head 在内核头文件 List.h中的实现:
struct list_head {
	struct list_head *next, *prev;
};

list_head()和list_head_tail()选一个使用就行,没必要两个都记住。

(4)遍历内核链表:
宏函数 list_entry()是重点,不过这个也了解一下就好,真正操作内核链表用不到。下面再解释这个宏。

两种遍历方法(对应两种插入结点的方法):

第一种:向前遍历:(下面有更好的方法)
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next)
/**
第二种:向后遍历:(下面有更好的方法)
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); \
pos = pos->prev)

linux内核链表的实现和使用和详解_第3张图片
注意:插入结点的方法要跟遍历方法匹对好,用head之后插入结点的方法的话,遍历方法就要选向后遍历。
           用head之前插入结点的方法的话,遍历方法就要宣向前遍历,这样才能 按插入顺序遍历所有结点。

上述的两种遍历方法,都是指向内核链表结构体的,这样设计起来不太简易,首先你的封装性就不够强,而下面介绍的方法则是 指向内核链表结构体 所在的结构体。
如:(推荐以下这种--->list_for_each_entry())

list_for_each_entry() 是                  向前遍历。
list_for_each_entry_reverse()是向后遍历。
list_for_each_entry() 和list_for_each_entry_reverse()选一个就好,当然也得匹配好插入结点是向前还是向后。
struct student
{
 int num;
 struct list_head mylist;
};
int show(struct student *head)
{
	struct student *pos;
	list_for_each_entry(pos,&(head->mylist),mylist)  //list_for_each_entry()是将要介绍的宏。
	{						 //用list_for_each_entry的话,pos可以写你定义的结构体类型(struct student)
		printf("pos->num is:%d\n",pos->num);     // 而不是struct list_head ,而用list_for_each的话,pos必须是 list_head类型的
	}
	
	return 0;
}

list_for_each_entry_reverse()的实现代码:(跟list_for_each_entry差不多)
#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))



list_for_each_entry()的实现:(实现原理仅作了解即可)(他只是一个 for 循环)

#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))   //一个for循环
list_for_each_entry()的作用是 :
           list_for_each_entry()的三个参数分别是,pos(指向member的指针) , head(数据类型结构体头结点),member(数据类型结构体的一个成员变量)
           这个宏可以通过 通过 一个指针指向 一个结构体中 一个成员,来返回这个结构体的起始地址。
 
其中实现这个功能最重要的是一个宏 list_entry() ,我们来看看他的实现:
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
这片代码看起来有点难懂,我们来画个图看看:

linux内核链表的实现和使用和详解_第4张图片
首先  &((type*)0)->member:把0地址转换成(type*)类型,然后就可以映射出 member(因为member是结构体x的一个成员),0地址上的结构体是不存在的,只是虚拟出来
然后 &((type*)0)->member 是上图A部分的大小,因为同是 type型结构体,所以A部分大小 = B部分大小。然后再用  指向member 的指针pos 减去B部分 就等于 结构体x的
起始地址。



(5)删除结点:
直接上代码,并不难理解:
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = (void *) 0;
	entry->prev = (void *) 0;
}
这个函数,包括上面的宏和函数,只是一个接口,后续的操作函数其实还需要自己设计,例如上面这个删除函数,其实还得free掉entry,否则会一直占着堆空间

宏__list_del()的实现原理:(这些都简单理解就好)
static inline void __list_del(struct list_head *prev, struct list_head *next)
{
	next->prev = prev;
	prev->next = next;
}


最后,一片代码:
#include 
#include 
#include "kernel_list.h"
struct student
{
	int num;
	struct list_head mylist;
};
// 内核链表的初始化
struct student * list_init()
{
	struct student *head=malloc(sizeof(struct student));
	INIT_LIST_HEAD(&(head->mylist));
	return head;
}
// 创建新的节点
struct student * new_node(int data)
{
	struct student *new=malloc(sizeof(struct student));
	new->num=data;
	INIT_LIST_HEAD(&(new->mylist));
	return new;
}
// 添加新的节点到内核链表中
int kernellist_add(struct student *new,struct student *head)
{
	list_add(&(new->mylist), &(head->mylist));
	return 0;
}
// 打印内核链表的数据
int show(struct student *head)
{
	struct student *pos;
	list_for_each_entry(pos,&(head->mylist),mylist) 
	{
		printf("pos->num is:%d\n",pos->num);
	}
	
	return 0;
}
int main()
{
	int n;
	int i;
	struct student *mynewnode;
	struct student *p=list_init();
	printf("please input num you want create node!\n");
	scanf("%d",&n);
	for(i=1; i<=n; i++)
	{
		mynewnode = new_node(i);
		kernellist_add(mynewnode,p);
	}
	show(p);
	return 0;
}

你可能感兴趣的:(linux内核链表的实现和使用和详解)