linux内核中有很多用的很经典的数据结构,链表就算其中之一,还有队列,
哈希查找,红黑树查找等等,链表的设计很经典,就连很多开发内核的黑客们
都觉得内核中链表的设计是他们引以自豪的一部分。我觉得内核链表的好主要
体现为两点,1是可扩展性,2是封装。可扩展性肯定是必须的,内核一直都是
在发展中的,所以代码都不能写成死代码,要方便修改和追加。将链表常见的
操作都进行封装,使用者只关注接口,不需关注实现。分析内核中的链表我们
可以做些什么呢?我觉得可以将其复用到用户态编程中,以后在用户态下编程
就不需要写一些关于链表的代码了,直接将内核中list.h中的代码拷贝过来用。
也可以整理出my_list.h,在以后的用户态编程中直接将其包含到C文件中。
-------------------------------------------------------------------------------------------
1,链表的创建和初始化。
内核的链表一般都是双向循环链表,学过数据结构的人都知道,双向循环
链表的效率是最高的,找头节点,尾节点,直接前驱,直接后继时间复杂度都是O(1),而使用单链表,单向循环链表或其他形式的链表是不能完成的。
其实内存中开辟出一个头节点然后将其初始化好就完成了链表的创建。
-
static inline void INIT_LIST_HEAD(struct list_head *list)
-
{
-
list->next = list;
-
list->prev = list;
-
}
开辟好头节点后,然后将结构体中的next域和prev域都指向自己即OK。
例如:(用户态下一个例子)
-
struct stu {
-
int num;
-
char name[20];
-
struct list_head list;
-
};
-
-
struct stu *head = (struct stu *)malloc(sizeof(struct stu));
-
if (head == NULL) {
-
printf("file,%s line,%d:malloc error!\n",__FILE__,__LINE__);
-
exit(1);
-
}
-
INIT_LIST_HEAD(&head->list);
上面的代码就完成了一个双向循环链表的创建。图示如下:
-------------------------------------------------------------------------------------------
2.链表的节点的插入。上面建立的只是一个空链表,肯定要向链表中插入一些
节点,插入的方式很多,下面主要分析list_add_tail函数,该函数实现的是
“尾插”。
-
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;
-
}
-
-
/**
-
* list_add_tail - add a new entry
-
* @new: new entry to be added
-
* @head: list head to add it before
-
*
-
* Insert a new entry before the specified head.
-
* This is useful for implementing queues.
-
*/
-
static inline void list_add_tail(struct list_head *new, struct list_head *head)
-
{
-
__list_add(new, head->prev, head);
-
}
其实,双向循环链表插入节点很简单,也不需要辅助指针,就几个指针变来变去的,画个图就清楚了
如果光靠脑子还不好想。示意图如下:
-------------------------------------------------------------------------------------------
3,链表的遍历。
遍历就是使用线性表的方式吧链表上的每个节点都走一遍,内核中定
义了一个宏来实现。
-
#define list_for_each(pos, head) \
-
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
-
pos = pos->next)
可以看出,使用了辅助指针pos,pos是从第一节点开始的,并没有访问头节点,
直到pos到达头节点指针head的时候结束。
-
/**
-
* list_entry - get the struct for this entry
-
* @ptr: the &struct list_head pointer.
-
* @type: the type of the struct this is embedded in.
-
* @member: the name of the list_struct within the struct.
-
*/
-
#define list_entry(ptr, type, member) \
-
container_of(ptr, type, member)
-
#ifndef container_of
-
/**
-
* container_of - cast a member of a structure out to the containing structure
-
*
-
* @ptr: the pointer to the member.
-
* @type: the type of the container struct this is embedded in.
-
* @member: the name of the member within the struct.
-
*
-
*/
-
#define container_of(ptr, type, member) ({ \
-
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
-
(type *)( (char *)__mptr - offsetof(type,member) );})
-
#endif
list_entry主要是实现从内层结构体返回到外层结构体,就拿上面的struct stu
结构体来说吧,现在假如知道struct stu内部list结构体的指针pos,如何来获得
该结构体对应的struct stu的结构体指针?这儿主要是使用container_of这个宏
来实现的,该宏主要是用了一个技巧,主要是求出内层结构体字段到外层的偏移量,计算的方式是,将外层结构体的地址设置为0,然后就有,该字段的地址 =
该字段到外层结构体的偏移量。
更详细的解释见:
示意图如下:
遍历的时候都是遍历pos,然后再由pos返回到pnode。然后就可以访问
strudent中的任何一个字段了。
------------------------------------------------------------------------------------------
4,链表的释放。
由于链表的节点都是动态开辟的空间,必须要释放,不然容易造成内存泄漏。
释放也是一个节点一个节点的释放,故也需要遍历链表。
-
/**
-
* list_for_each_safe - iterate over a list safe against removal of list entry
-
* @pos: the &struct list_head to use as a loop cursor.
-
* @n: another &struct list_head to use as temporary storage
-
* @head: the head for your list.
-
*/
-
#define list_for_each_safe(pos, n, head) \
-
for (pos = (head)->next, n = pos->next; pos != (head); \
-
pos = n, n = pos->next)
该遍历链表和一般的链表遍历不一样,这时候是释放整条链表,必须要重
新引入一个辅助指针n,如果使用list_for_each就会出错。
-
static inline void __list_del(struct list_head * prev, struct list_head * next)
-
{
-
next->prev = prev;
-
prev->next = next;
-
}
-
-
-
static inline void list_del(struct list_head *entry)
-
{
-
__list_del(entry->prev, entry->next);
-
entry->next = LIST_POISON1;
-
entry->prev = LIST_POISON2;
-
}
list_del将会将该节点与外界的“联系”切断,然后就可以使用free释放了
(如果是内核态就用kfree或vfree)。
最后将会只剩下头节点,再将头节点释放,将完成了整条链表的释放。
-------------------------------------------------------------------------------------------
5,用户态使用内核链表的实例:(完整代码)
kernel_list_for_user.rar
--------------------------------------------------------------------------------------------
6,参考:
阅读(672) | 评论(0) | 转发(0) |