Linux内核链表浅析及简单运用

来源:互联网 发布:ubuntu安装deepin桌面 编辑:程序博客网 时间:2024/06/04 19:46

        作为一个c++新手,我也开始接触底层的东西鸟。

        Linux内核中有很多的数据结构,链表就是其中之一,但是这个链表和普通我们用的链表是有很大区别的,区别就在于,它只有指针域,却没有数据域,这样就有很大的一个好处,很方面我们来扩展它,我们只要自己定义一种数据结构,指定自己的数据域,使用它作为指针域就OK了。而且它还实现了各种底层的方法,方面我们运用。

1.链表的定义

        Linux内核链表定义在include/linux/list.h头文件中,是用的一个结构体:

struct list_head {struct list_head *next, *prev;}

        可以看出Linux内核链表是一个双向链表。

2.链表的初始化

        提供了一个方法和两个宏来初始化:

#define LIST_HEAD_INIT(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){list->next = list;list->prev = list;}

        都是将节点的前后指针指向自己。

3.链表是否为空

static inline int list_empty(const struct list_head *head){return head->next == head;}

        由上面看出初始化是将指针都指向自己,如果链表为空的话,那么头结点就是指向自己,头结点是没有任何意义的,只是一个标识。

4.链表添加结点

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;}

        这是一个很底层的方法,将new这个结点添加到prev和next之间。我们经常要将一个结点添加到另一个结点之前或者之后,只要将该结点的前结点或者后结点带进去:

static inline void list_add(struct list_head *new, struct list_head *head){_list_add(new, head, head->next);}static inline void list_add_tail(struct list_head *new, struct list_head *head){_list_add(new, head->prev, head);}

        head为头结点,所以list_add_tail()方法就是将结点new添加到链表的尾端。

5.链表删除结点

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;}static inline void list_del_init(struct list_head *entry){_list_del(entry->prev, entry->next);INIT_LIST_HEAD(entry);}

        方法list_del调用后,将entry的前后指针指向不可访问的的位置,作用也就相当于free(),list_del_init()方法调用后,将entry这个结点又初始化一次,指向自己。

6.链表的遍历

        Linux内核提供了很多的遍历链表的宏,其实基础的就2个,也就是向两个方向遍历:

#define list_for_each(pos, head) \for(pos = (head)->next; prefetch(pos->next), pos != (head); pos = pos->next)#define list_for_each_prev(pos, head) \for(pos = (head)->prev; prefetch(pos->prev), pos != (head); pos = pos->prev)

        从中可以看出,是根据指针的移动来遍历整个链表,从头结点开始,到再次回到头结点为止。但是当我们遍历达到某个条件要删除结点的时候,就不能这么遍历了,不然会指针会出错。那么可以用下面提供的宏来实现:

#define list_for_each_safe(pos, n, head) \for(pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next)#define list_for_each_prev_safe(pos, n, head) \for(pos = (head)->prev, n = pos->prev; pos != (head); pos = n, n = n->prev)

        这里多出了一个局部临时指针n,它的作用就是当我们删除某个结点后,不至于找指针的时候会出错,有一个安全的保证。

7.得到链表结构的数据信息

        这个是最难理解的地方,也是最重要地方。说它难理解就是因为它很巧妙的使用了0地址,还有地址的偏移,说它重要是因为我们用链表的话必须存数据,而Linux内核提供的链表是没有数据域的,必须我们自己加上,但是指针指向的还是指针域地址而不是我们扩展后的对象地址,而提供的这个借口能让我们得到扩展后的对象地址。这里可能有点难理解,后面会解释到:

#define list_entry(ptr, type, member) \container_of(ptr, type, member)// 这个container_of定义在include/linux/kernel.h头文件中:#define container_of(ptr, type, member)({ \const typeof(((type *)0)->member) *_mptr = (ptr); \(type *)((char *)_mptr - offsetof(type, member));})//而offsetof定义为:#define offsetof(TYPE, MEMBER)((size_t)&((TYPE *)0)->MEMBER)

       这个看上去太复杂了,简化一下其实就是:

#define list_entry(ptr, type, member) \((type *)((char *)(ptr) - ((unsigned long)(&((type *)0)->member - 0)))

        这个看上去就简单多了,ptr为指向指针域的指针,type为我们扩展过后的数据结构类型,member就是指针域的属性,比如我们扩展了一个链表,增加一个int型的数据域:

struct list{int data;struct list_head pointer;};

        比如我们有一个list型对象my_list,那么my_list.next得到的是my_list指向的下一个结点的pointer,而不是list,那么我们怎么得到这个list的地址呢?如图:


      我们用链表就是要得到这个data,但是我们的next和prev指针得到的都是指针的地址,那么我们怎样才能得到整个对象的首地址呢,看下图:


        加入我们在0地址这里创建一个list对象,那么member这里的地址就为((list *)0)->pointer,那么这个差值也就是偏移量就为member这里的地址减去0地址有木有,那么当我们知道ptr这里的地址的时候,是不是拿ptr地址减去差值就得到了目标地址呢。也就是我们想要得到的list的首地址,得到过后->data是不是就是我们要的data呢。这样想想是不是还有点小激动呢。

        Linux内核链表最基本的东西大概就是这些,当然还提供了很多的接口,但是这些都是基于以上接口实现的,比如结点的move(),链表的合并,搞清楚以上这些接口并灵活运用,那么其他的接口都可以很简单的实现。

        下面是我基于Linux内核链表自己实现一个先进先出的队列,其中我还增加了一个int型属性id,用来统计队列的大小,这样就不用遍历了++来得到大小:

queue_3.h:

#ifndef _QUEUE_3_H_#define _QUEUE_3_H_#include "mylist.h" // mylist.h是我自己写的一个头文件,只截取了部分Linux内核链表的接口struct queue_node{int id; // 用来统计队列大小,就像数组索引一样,尾结点的id就是队列的大小int data; // 数据域struct list_head list; // 指针域};class queue{private:struct queue_node head; // 头结点public:queue();void init();void push(struct queue_node *node);int pop();int size();queue_node find(int nData);}#endif /*_QUEUE_3_H_*/
queue_3.cpp:

#include "queue_3.h"queue::queue(){head.id = 0;head.data = 0;init(); }void queue::init(){INIT_LIST_HEAD(&head.list);}void queue::push(struct queue_node *node){struct queue_node *rear = list_entry(head.list.prev, struct queue_node, list); // 尾结点node->id = rear->id + 1; // id加1,作为尾结点list_add(&node->list, &rear->list);}int queue::pop(){if(list_empty(&head.list)) // 如果队列是空的,就返回0{return 0;}struct queue_node *rear = list_entry(head.list.prev, struct queue_node, list); // 尾结点rear->id = rear->id - 1; // 尾结点id减1struct queue_node *result = list_entry(head.list.next, struct queue_node, list);list_del_init(&result->list);return result->data;}int queue::size(){struct queue_node *rear = list_entry(head.list.prev, struct queue_node, list); // 尾结点return rear->id;}queue_node queue::find(int nData) // 没做元素数据重复判定(待完善){struct list_head *pos;struct queue_node *result;list_for_each(pos, &head.list) // 遍历,查找{result = list_entry(pos, struct queue_node, list);if(result->data == nData){return *result;}}return head; // 没查找到就返回头结点}
queuetest_3.cpp:

#include <iostream>#include "queue_3.h"using namespace std;int main(){cout << "queue!" << endl;queue q;cout << "size of the queue : " << q.size() << endl;cout << "get NO.1 of queue : " << q.pop() << endl << endl;struct queue_node node1, node2, node3, node4, node5;node1.data = 1;node2.data = 2;node3.data = 3;node4.data = 4;node5.data = 5;INIT_LIST_HEAD(&node1.list);INIT_LIST_HEAD(&node2.list);INIT_LIST_HEAD(&node3.list);INIT_LIST_HEAD(&node4.list);INIT_LIST_HEAD(&node5.list);q.push(&node1);q.push(&node2);q.push(&node3);q.push(&node4);q.push(&node5);struct queue_node result = q.find(3);cout << "find 3, (3 exist) the result : " << result.data << endl;result = q.find(10);cout << "find 10, (10 not exist) the result : " << result.data << endl;cout << "size of the queue : " << q.size() << end;cout << "get NO.1 of the queue : " << q.pop() << endl;cout << "after pop NO.1 size of the queue : " << q.size() << endl;cout << "get NO.2 of the queue : " << q.pop() << endl;cout << "after pop NO.2 size of the queue : " << q.size() << endl;cout << "get NO.3 of the queue : " << q.pop() << endl;cout << "after pop NO.3 size of the queue : " << q.size() << endl;cout << "get NO.4 of the queue : " << q.pop() << endl;cout << "after pop NO.4 size of the queue : " << q.size() << endl;cout << "get NO.5 of the queue : " << q.pop() << endl;cout << "after pop NO.5 size of the queue : " << q.size() << endl;cout << "get NO.6 of the queue : " << q.pop() << endl;cout << "after pop all element size of the queue : " << q.size() << endl << endl;return 0;}
测试结果:


        c++新手代码,如有不足请指正。

0 0
原创粉丝点击