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

来源:互联网 发布:有向图最长路径算法 编辑:程序博客网 时间:2024/06/09 08:41


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

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

(1)什么是内核链表:

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

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

#include "kernel_list.h"//我把内核中的 /include/linux/List.h 改名为 kernel_list.h#include <stdio.h>  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);}
访问顺序和新增插入顺序(如在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)


注意:插入结点的方法要跟遍历方法匹对好,用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)))
这片代码看起来有点难懂,我们来画个图看看:

首先  &((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 <stdio.h>#include <stdlib.h>#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;}

0 0
原创粉丝点击