Linux开发中内核通用链表的操作方法
我们在项目开发的时候要怎么封装双向链表库呢?下面就让我们带着这个疑问一起来学习这个结构,我们程序员只需要提供相关接口即可实现,有需要的朋友赶紧进入下文学习Linux开发中内核通用链表的操作方法吧!
传统的链表结构
struct node{ int key; int val; node* prev; node* next; }
linux 内核通用链表库结构
提供给我们的指针域结构体:
struct list_head { struct list_head *next, *prev; };
我们只需要包含它就可以:
struct node{ int val; int key; struct list_head* head; }
可以看到通过这个 list_head 结构就把我们的数据层跟驱动层分开了,而内核提供的各种操作方法接口也只关心 list_head 这个结构,也就是具体链接的时候也只链接这个list_head 结构,并不关心你数据层定义了什么类型.
一些接口宏定义
//初始化头指针 #define LIST_HEAD_INIT(name) { &(name), &(name) } #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) //遍历链表 #define __list_for_each(pos, head) \ for (pos = (head)->next; pos != (head); pos = pos->next) //获取节点首地址(不是list_head地址,是数据层节点首地址) #define list_entry(ptr, type, member) \ container_of(ptr, type, member) //container_of在Linux内核中是一个常用的宏,用于从包含在某个 //结构中的指针获得结构本身的指针,通俗地讲就是通过结构体变 //量中某个成员的首地址进而获得整个结构体变量的首地址 #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define offsetof(s,m) (size_t)&(((s *)0)->m)
使用方式
typedef struct node{ int val; int key; struct list_head* list; }node; //初始化头指针 LIST_HEAD(head); //创建节点 node* a = malloc(sizeof(node)); node* b = malloc(sizeof(node)); //插入链表 方式一 list_add(&a->list,&head); list_add(&b->list,&head); //插入链表 方式二 list_add_tail(&a->list,&head); list_add_tail(&b->list,&head); //遍历链表 struct list_head* p; struct node* n; __list_for_each(p,head){ //返回list_head地址,然后再通过list_head地址反推 //节点结构体首地址. n = list_entry(pos,struct node,list); }
list_add 接口,先入后出原则,有点类似于栈
list_add-先入后出模式
list_add_tail 接口,先入先出原则,有点类似于fifo
list_add-先入先出模式
我们的链表节点,实际在内存中的展示形态
节点描述
可以看到最终的形态是,通过指向每个结构体里面的 list_head 类型指针,然后把它们串联起来的
list_entry 接口,通过结构体变量某个成员的地址,反推结构体首地址,就像 __list_for_each 接口只返回 list_head 地址,所以我们要通过这个成员地址在去获取它本身的结构体首地址,底层实现方法 container_of 宏
反推结构体首地址
举个例子
这个例子包括简单的增、删、遍历
#include#include #include #include #include MODULE_LICENSE("GPL"); MODULE_AUTHOR("David Xie"); MODULE_DESCRIPTION("List Module"); MODULE_ALIAS("List module"); struct student //代表一个实际节点的结构 { char name[100]; int num; struct list_head list; //内核链表里的节点结构 }; struct student *pstudent; struct student *tmp_student; struct list_head student_list; struct list_head *pos; int mylist_init(void) { int i = 0; //初始化一个链表,其实就是把student_list的prev和next指向自身 INIT_LIST_HEAD(&student_list); pstudent = kmalloc(sizeof(struct student)*5,GFP_KERNEL);//向内核申请5个student结构空间 memset(pstudent,0,sizeof(struct student)*5); //清空,这两个函数可以由kzalloc单独做到 for(i=0;istudent %d name: %s/n",tmp_student->num,tmp_student->name); } return 0; } void mylist_exit(void) { int i ; /* 实验:将for换成list_for_each来遍历删除结点,观察要发生的现象,并考虑解决办法 */ for(i=0;i
上述就是爱站技术频道小编介绍的Linux开发中内核通用链表的操作方法,我们在网站建设的时候思维要活跃和广阔一些,也可以多多参考js.aizhan.com的文章。