作为一个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减1
struct 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++新手代码,如有不足请指正。