我们都知道,链表是数据结构中用得最广泛的一种数据结构,对于数据结构,有顺序存储,数组就是一种。有链式存储,链表算一种。当然还有索引式的,散列式的,各种风格的说法,叫法层出不穷,但是万变不离其中,只要知道什么场合用什么样的数据结构,那就行了。
那么,标题说的内核万能链表,其实就是内核链表,它到底和我们平常大学学的数据结构的链表有什么不同呢??内核链表,是在linux内核里的一种普遍存在的数据结构,比如内核调度算法中有这样的结构,摄像头驱动程序,wifi模块,G_sensor等等,用到链表的东西实在实在是太多了,看内核的人务必要懂得这样的数据结构的用法,否则你根本不知道它是怎么产生的。
内核链表,在我们学完数据结构的人看来,它很奇怪,为什么说它奇怪?因为它没有数据域,这就是内核链表和普通链表的最大区别,那有人会问,没有数据域那怎么插入数据到链表呢?还有其它的什么遍历啊,反序啊等等的操作。。。别急,内核链表最大的优势就是内核已经提供了对链表进行常见操作的函数接口,也就是说给你接口你就会用,但是会用也不是那么简单,其中就有两个非常重要的宏,这两个宏能够理解,内核链表不过如此而已,没什么难度。
废话不多说,我们来看看,到底是怎么回事?
上代码...
以下的代码经过我本人亲自裁剪的常用接口,其实还有一些的,但是都大同小异,会用一个,其它也一样!
在代码里面,我做了详细的剖析,我做成了一个.h文件,当然写了一个使用的main函数:
最重要的,我对container_of和offsetof两个宏进行了分析:
<span style="font-size:18px;">#ifndef __list_H #define __list_H //在linux内核中提供了一个用来创建双向循环链表的结构list_head。 //list_head只有指针域,没有数据域,不是拿来单独使用,一般被嵌入到其它的结构中 struct list_head { struct list_head *next, *prev; }; //需要注意的是,头结点的head是不使用的 #define LIST_HEAD_INIT(name) { &(name), &(name) } //我们知道0地址内容时不允许被访问的,但是0地址我们还是可以访问的 //这里用一个取址运算符(TYPE *)0表示将0地址强制转换为TYPE *类型 //((TYPE *)0) -> MEMBER 也就是从0地址找到TYPE的成员MEMBER //将main.c中的结构体代入 //offset(struct list , list);----->展开后((size_t) & (struct list *)0 -> list) //写清楚一点时这样: //struct list *p = NULL ; &p->list ; 即是求p这个结构体指针的成员list的地址,只不过p是0 //地址,从0地址开始计算list成员的地址,也就是成员list在结构体struct list中的偏移量 //这个宏的作用就是求解MEMBER成员在TYPE中的偏移量 //编译器不报错的原因是在编译阶段就已经知道结构体里每个成员的属性的相对偏移量了 , //在代码中对结构体成员的访问其实最终 会被编译器转化为对其相对地址的访问 //在代码运行期间,其实根本就没有变量名还有属性成员,有的也就只有地址。 #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) //container_of最终结果返回的是第二个表达式的值,这里所谓的__mptr是一个中间的指针变量,其实就是list_head指针类型,被初始化为ptr //而我所说的这个ptr,就是我们要求的list_head中的节点地址,定义这样一个中间的指针变量其实考虑了很多因素 //如果传参进来的是ptr++,会有副作用,就类似于(p++)+(p++)这样 //而(char *)__mptr之所以要强制转换为char实质上是因为地址是以字节为单位的,而char的长度是一个字节 //所以contain_of实质是两个地址相减的结果 //__mptr是结构体中list_head节点的地址,offset宏求的是list_head节点在MEMBER在结构体中TYPE中的偏移量,那么 //__mptr减去它所在结构体中的偏移量,就是结构体的地址了。 #define container_of(ptr, type, member) ({ \ const typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) #define LIST_HEAD_INIT(name) { &(name), &(name) } //生成名字为name的双向链表的头节点 #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) //初始化链表头 static inline void INIT_LIST_HEAD(struct list_head *list) {</span>
<span style="font-size:18px;"><span style="white-space:pre"> </span>//链表的前一个节点和后一个节点都指向它自己 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; } //双向链表插入节点的操作---在头节点head的下一个节点,即是头插 ,即使将new这个节点插入 static inline void list_add(struct list_head *new, struct list_head *head) { __list_add(new, head, head->next); } //双向链表插入节点的操作---向尾部插入new节点 static inline void list_add_tail(struct list_head *new, struct list_head *head) { __list_add(new, head->prev, head); } static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } //双向链表中节点删除 ,并把被删除的位置清0 static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = NULL; entry->prev = NULL; } //将上面entry被删除的节点重新初始化 static inline void list_del_init(struct list_head *entry) { __list_del_entry(entry); INIT_LIST_HEAD(entry); } //将老的节点old换成新的节点new static inline void list_replace(struct list_head *old, struct list_head *new) { new->next = old->next; new->next->prev = new; new->prev = old->prev; new->prev->next = new; } //将上面的过程进行重新初始化 static inline void list_replace_init(struct list_head *old, struct list_head *new) { list_replace(old, new); INIT_LIST_HEAD(old); } //将链表中的list节点移动到链表的头部 static inline void list_move(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add(list, head); } //判断当前链表list节点是不是链表的最后一个节点 static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; } //判断链表是不是空的,如果空返回真,否则返回假 //也就是判断表头的节点的下一个节点是不是它本身 static inline int list_empty(const struct list_head *head) { return head->next == head; } //判断链表是不是空的,如果空返回真,否则返回假 ,稍微和上面有区别的是 //判断表头的前一个节点和后一个节点是否为本身,是返回真,不是,返回假 //也就是prev和next static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); } //将链表进行翻转,也就是将head的next和head的本身做交换 static inline void list_rotate_left(struct list_head *head) { struct list_head *first; if (!list_empty(head)) { first = head->next; list_move_tail(first, head); } } //判断链表中是否只有一个节点 static inline int list_is_singular(const struct list_head *head) { return !list_empty(head) && (head->next == head->prev); } static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { struct list_head *new_first = entry->next; list->next = head->next; list->next->prev = list; list->prev = entry; entry->next = list; head->next = new_first; new_first->prev = head; } //分割链表,以entry为界限进行分割,分割完后的另一半的那条链表被list所指 static inline void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { if (list_empty(head)) return; if (list_is_singular(head) && (head->next != entry && head != entry)) return; if (entry == head) INIT_LIST_HEAD(list); else __list_cut_position(list, head, entry); } static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } //将list和head这两条链表合并,注意,链表不能为空 static inline void list_splice(const struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } //将list和head这两条链表合并后并初始化 static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } #define prefetch(x) __builtin_prefetch(x) //双向链表的正向遍历,从前往后 #define list_for_each(pos, head) \ for (pos = (head)->next; prefetch(pos->next), pos != (head); \ pos = pos->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开始, 从当前的位置往后遍历,注意和头的情况区分 #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_entry(pos->member.next, typeof(*pos), member); \ &pos->member != (head); \ pos = list_entry(pos->member.next, typeof(*pos), member)) //双向链表的反向遍历,从后往前 #define list_for_each_prev(pos, head) \ for (pos = (head)->prev; pos != (head); pos = pos->prev) //双向链表遍历的时候可以同时删除pos节点 #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); \ pos = n, n = pos->next) //提供一个和pos一样的指针n,在for循环中暂时保留pos的下一个节点的地址,避免因pos节点被释放而造成断链的结果 #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_head的地址时,可以通过list_entry这个宏获取它的父结构的地址。 //所以泽火革宏的功能就是由结构体成员的地址求结构体的地址。其中ptr是所求结构体中 //list_head成员的指针,type是所求结构体的类型,member是结构体list_head成员名 #define list_entry(ptr, type, member) \ container_of(ptr, type, member) //哈希链表相关的数据结构 //单指针双向循环链表 struct hlist_head { struct hlist_node *first; }; struct hlist_node { struct hlist_node *next, **pprev; }; #ifdef CONFIG_ILLEGAL_POINTER_VALUE # define POISON_POINTER_DELTA _AC(CONFIG_ILLEGAL_POINTER_VALUE, UL) #else # define POISON_POINTER_DELTA 0 #endif #define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA) #define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA) //对定义的struct hlist_head 哈希链表变量进行初始化为空 #define HLIST_HEAD_INIT { .first = NULL } //初始化哈希链表头 #define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } //这个宏用在删除节点之后对节点的操作当中 #define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) static inline void INIT_HLIST_NODE(struct hlist_node *h) { h->next = NULL; h->pprev = NULL; } //判断h->prev是不是为空,如果pprev的指向是空的话,表示这个节点没有添加到这个链表当中来,如果是空,返回true,否则返回false*/ static inline int hlist_unhashed(const struct hlist_node *h) { return !h->pprev; } //判断哈希链表是否为空,空返回true,否则返回false static inline int hlist_empty(const struct hlist_head *h) { return !h->first; } static inline void __hlist_del(struct hlist_node *n) { struct hlist_node *next = n->next; struct hlist_node **pprev = n->pprev; *pprev = next; if (next) next->pprev = pprev; } //对哈希链表的节点进行删除 //对于删除操作的话,要注意n是不是末尾节点,如果是末尾节点的话, //next就是NULL,所以就没有指向的pprev,就更不能进行相应的修改了,否则进行修改。 static inline void hlist_del(struct hlist_node *n) { __hlist_del(n); n->next = LIST_POISON1; n->pprev = LIST_POISON2; } //删除哈希链表的节点并对其进行初始化 static inline void hlist_del_init(struct hlist_node *n) { if (!hlist_unhashed(n)) { __hlist_del(n); INIT_HLIST_NODE(n); } } //为哈希链表添加一个新的节点 //n:要添加的新的节点。 //h:hlist链表的表头节点。 //这个函数是给h的下一个和first节点中添加一个新的hlist_node节点,类似于上面的双向链表的头插 static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) { struct hlist_node *first = h->first; n->next = first; if (first) first->pprev = &n->next; h->first = n; n->pprev = &h->first; } //在next的前面添加一个新节点n static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) { n->pprev = next->pprev; n->next = next; next->pprev = &n->next; *(n->pprev) = n; } //在next的后面添加一个新节点n static inline void hlist_add_after(struct hlist_node *n, struct hlist_node *next) { next->next = n->next; n->next = next; next->pprev = &n->next; if(next->next) next->next->pprev = &next->next; } //将新的节点new替换老的节点old static inline void hlist_move_list(struct hlist_head *old, struct hlist_head *new) { new->first = old->first; if (new->first) new->first->pprev = &new->first; old->first = NULL; } //哈希链表得到ptr所指地址的这个结构体的首地址 #define hlist_entry(ptr, type, member) container_of(ptr,type,member) //哈希链表的遍历操作 //pos:struct hlist_node类型的一个指针; //head:struct hlist_head类型的一个指针,表示hlist链表的头结点。 //这个实际上就是一个for循环,从头到尾遍历链表。 #define hlist_for_each(pos, head) \ for (pos = (head)->first; pos ; pos = pos->next) //以下的遍历于双向循环链表遍历操作大同小异,在此不做注释 #define hlist_for_each_safe(pos, n, head) \ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \ pos = n) #define hlist_for_each_entry(tpos, pos, head, member) \ for (pos = (head)->first; \ pos && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_continue(tpos, pos, member) \ for (pos = (pos)->next; \ pos && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_from(tpos, pos, member) \ for (; pos && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = pos->next) #define hlist_for_each_entry_safe(tpos, pos, n, head, member) \ for (pos = (head)->first; \ pos && ({ n = pos->next; 1; }) && \ ({ tpos = hlist_entry(pos, typeof(*tpos), member); 1;}); \ pos = n) #endif</span>
以上就是裁剪过后的list.h的分析,下面我们来看看如何使用内核链表:同样的,我写成了两个函数接口,一个是双向链表,一个是哈希链表,其实都差不多。
以下是main.c文件:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "list.h" #define SIZE 100 #define hname "yangyuanxin" #define true 1 #define false 0 //创建一个双向链表结构 struct list { char name[11] ; char sex[5] ; char company_name[10]; char seq_number[32]; //嵌套在struct list这个自己创建的结构体里面 //list_head就是它的父结构的一个成员, struct list_head list; }; //创建一个哈希链表结构 struct hlist { int id ; char name[20] ; struct hlist_node hlist ; }; //内核链表中哈希链表的使用 int hlist_fuction(void) ; //内核链表中基本的双向链表的使用 int double_list(void) ; int main(int argc, char *argv[]) { hlist_fuction(); double_list(); return 0; } //定义一个和哈希链表操作的相关函数 int hlist_fuction(void) { printf("欢迎进入哈希链表模块!\n"); //初始化哈希链表头 HLIST_HEAD(hhead); //定义一个哈希链表成员 struct hlist people ; //定义一个哈希链表节点 struct hlist *entry ; //定义一个已知的某个哈希链表的节点 struct hlist_node *p ; printf("请输入成员id:\n"); scanf("%d",&people.id); printf("请输入成员name:\n"); scanf("%s",&people.name); //将成员添加到哈希链表的头部 hlist_add_head(&people.hlist , &hhead); //遍历哈希链表 hlist_for_each(p,&hhead) { //获取哈希链表的地址 entry = hlist_entry(p , struct hlist , hlist); printf("成员id为:\n"); printf("%d\n",entry->id); printf("成员name:\n"); printf("%s\n",entry->name); } int errcount = 0 ; repest : printf("需要删除链表吗?需要y,否则n\n"); //刷新输入缓冲区 fflush(stdin); char ch ; scanf("%c",&ch) ; switch(ch) { case 'y' : printf("正在准备删除链表节点!\n"); hlist_del(&people.hlist); printf("链表节点已删除!\n"); break ; case 'n' : printf("链表节点没有被删除!\n"); break ; default: printf("你的输入有误请重新输入!\n"); errcount ++ ; if(errcount == 3){ printf("输入错误次数太多,已退出程序!\n"); exit(1); } system("cls"); goto repest ; } switch(hlist_empty(&hhead)) { case true : printf("哈希链表为空\n"); break ; case false :printf("哈希链表不为空\n"); break ; } sleep(2); system("cls"); return 0 ; } int double_list(void) { printf("欢迎进入双向链表模块!\n"); //链表头初始化的两种方式 //1. // struct list_head head; // INIT_LIST_HEAD(&head); //2. LIST_HEAD(head); //创建一个结构体成员变量 struct list people; //创建一个链表节点 struct list *entry; //已知的某个链表的节点 struct list_head *p; printf("请输入员工姓名:\n"); scanf("%s",&people.name); printf("请输入公司名称:\n"); scanf("%s",&people.company_name); printf("请输入工号:\n"); scanf("%s",&people.seq_number); list_add(&people.list,&head); #if 0 printf("\n------------正向遍历-------------------\n\n"); printf("遍历的第一种方式:\n"); list_for_each(p,&head) { entry=list_entry(p,struct list,list); printf("姓名: %s\n",entry->name); printf("公司名: %s\n",entry->company_name); printf("工号: %s\n",entry->seq_number); } #endif printf("遍历的第二种方式:\n"); list_for_each_entry(entry,&head,list) { printf("name:%s\n",entry->name); printf("company_name:%s\n",entry->company_name); printf("seq_number:%s\n",entry->seq_number); } #if 1 printf("\n------------反向遍历------------------\n\n"); list_for_each_prev(p,&head) { entry=list_entry(p,struct list , list); printf("name:%s\n",entry->name); printf("company_name:%s\n",entry->company_name); printf("seq_number:%s\n",entry->seq_number); } #endif static int errcount ; fflush(stdin); char ch ; respest: printf("要删除双向链表节点吗?要输入y,否则输入n\n"); scanf("%c",&ch); switch(ch) { case 'y' : printf("准备删除双向链表所有节点!\n"); sleep(2); list_del(&people.list); printf("删除成功!\n"); switch(list_empty(&head)) { case true : printf("双向链表为空!\n"); break ; case false : printf("双向链表不为空!\n"); break ; } break ; case 'n' : printf("双向链表节点没有被删除!\n"); switch(list_empty(&head)) { case true : printf("双向链表为空!\n"); break ; case false : printf("双向链表不为空!\n"); break ; } break ; default : if(errcount == 3){ printf("你的输入错误次数太多,程序退出!"); exit(1); } printf("你的输入有误,请重新输入!\n"); errcount ++ ; system("cls"); goto respest ; } return 0 ; }
以上就是内核链表的使用方法。
我将内核链表裁剪后到win32的dev C++的gcc上进行了编译,没有错误也没有警告。
看看运行结果:
这个是哈希链表模块的运行结果:
来看看不为空的情况:
接下来是双向链表的运行结果:
接下来看看不为空的情况:
出错的情况:
至此,内核链表的剖析到此结束,下一次,我会继续剖析内核中红黑树的使用,以及哈夫曼树等等,还有图的遍历,只要是有的,我都尽量给挖出来。
花了一天时间进行整理,真心不容易...