为什么要用linux内核链表,Linux内核链表浅析及简单运用

作为一个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的地址呢?如图:

0818b9ca8b590ca3270a3433284dd417.png

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

0818b9ca8b590ca3270a3433284dd417.png

加入我们在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

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

}测试结果:

0818b9ca8b590ca3270a3433284dd417.png

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值