Linux 内核 链表 的简单模拟

  第零章:扯扯淡

  出一个有意思的题目:用一个宏定义FIND求一个结构体struct里某个变量相对struc的编移量,如


struct student
{
int a; //FIND(struct student,a) 等于0
char b; //FIND(struct student,b)等于4
double c;
};

参考答案:#define FIND(type,member) ((size_t)&((type*)0)->member)

我这样理解(可能不太正确):

(type*)0,0在编译过程中会用一个int临时变量装一下,现在把这个临时变量转化成了指针,指向假想的在地址0处存在的结构体,其实是没有的。

((type*)0)->member就访问这个结构体的变量了,

&((type*)0)->member就取得了这个变量的地址了,因为假想这个结构体放在0地址处嘛,所以这个变量的地址就是相对于结构体的偏移,

(size_t)&((type*)0)->member再把这个地址转化成size_t类型(typedef unsigned int
size_t),因为地址是0x00000004这样形式的,转化成无符号整形,即得偏移。

测试如下:

#include <iostream>

#define FIND(type,member) ((size_t)&((type*)0)->member)

struct student
{
int a; //FIND(struct student,a) 等于0
char b; //FIND(struct student,b)等于4
double c;
};

using namespace std;

int main(void)
{
cout << sizeof(size_t) << endl;
cout << FIND(struct student, a) << endl << FIND(struct student, b)<<endl<< FIND(struct student, c) << endl;
cin.get();
}

输出为 4 0 4 8

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  第一章:普通链表

  正常的的链表不是这样嘛:


/*表示书的结构体*/
struct Book
{
int bkId;
string bkName;

struct Book *prev; //指向链表中前一个节点
struct Book *next; //指向链表中后一个节点
};

然后再具体写对struct
Book结构体的具体操作,如添加一个节点啊,删除一个啊等等。设想:如果有几十个甚至上百个链表,那不是搞死了嘛!要为每一个特定类型的链表写一个具体的操作,程序员怎么会做这么愚蠢的事情呢?于是就有了帅帅的Linux内核链表管理法。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  第二章:Linux内核链表

  Linux内核链表数据结构如下:(在我的M:\linux-3.14.5\include\linux下types.h文件中)


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

正常的链表是把指向前后节点的指针放到链表节点中,但Linux内核不是这么干的,它把上面的struct
list_head塞到数据结构中,这样每一个struct list_head就成为了链表节点,其中next指向下一个struct
list_hand,prev指向前一个struct list_hand,这样是不是很帅?但关键是要看它怎么使用。现在来看我的struct
Book,如果要弄一个它的链表,只要这样:


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

/*表示书的结构体*/
struct Book
{
int bkId;
string bkName;

struct list_head list; //所有的Book结构体形成链表
};

我这是在抄袭哈!最关键的是要看到底怎么才能组成链表以及到底怎么样才能操作链表。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  第三章:Linux内核链表创建与初始化

  

  创建一个链表也就是创建一个链表头,这个头也是一个struct
list_head,源代码有些宏就省去了。(在我的M:\linux-3.14.5\include\linux下list.h文件中)


/*初始化链表*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}

其实就是让头的前后指针都指向自己啦。目前我的代码如下:

#include <string>

using std::string;

/*链表节点*/
struct list_head {
struct list_head *next, *prev;
};

/*表示书的结构体*/
struct Book
{
int bkId;
string bkName;
struct list_head list; //所有的Book结构体形成链表
};

/*初始化链表*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}

myHead.h

#include <iostream>
#include "myList.h"

using namespace std;

int main(void)
{
struct list_head MyBkList; //创建我的链表头
INIT_LIST_HEAD(&MyBkList); //初始化这个链表

cin.get();
}

main.cpp

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  第四章:Linux内核链表插入

  (1)插入节点

  上面只是建立了一个空链表,现在要插入数据了。

  内核提供了把一个新struct list_head节点插入到两个节点之间的方法:


/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new1,struct list_head * prev,struct list_head * next)
{
next->prev = new1;
new1->next = next;
new1->prev = prev;
prev->next = new1;
}

这里很抱歉啊,我的是C++项目,new是语言的关键词,所以把new换成了new1,内核源代码中上面的都是new。这个函数是其他插入函数都要调用的,其实就是提取了前插、后插的共同点啦,这个真美!

  (2)尾插节点


/**
* 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);
}

尾插这个方法其实就是在head节点之前插入一个节点啦,最终调用__list_add(new, head->prev,
head);表明是在head前一个节点和head节点之间插入一个节点。

  设想:刚开始只有一个链表头结点struct list_head head,head的前后都是指向自己的,现在调用 list_add_tail(struct
list_head *new, struct list_head *head),即__list_add(new, head->prev,
head);链表成了两个节点,并且都是各自的相互指向对方:

因为这是个双向链表,可以看做new1加到了head后面。再调用一个这个函数 list_add_tail(struct list_head
*new, struct list_head *head):

所以可以看做:把head当队头,每次修改的都是head的prev指针,每次都在队尾添加,相当于队列啦!现在代码是:


#include <iostream>
#include "myList.h"

using namespace std;

int main(void)
{
struct list_head MyBkList; //创建我的链表头
INIT_LIST_HEAD(&MyBkList); //初始化这个链表

/*创建新书结构体*/
struct Book bk1;
bk1.bkId = 1;
bk1.bkName = "book1";

list_add_tail(&bk1.list, &MyBkList); //把新书1加到头结点MyBkList后面

struct Book bk2;
bk2.bkId = 2;
bk2.bkName = "book2";

list_add_tail(&bk2.list,&MyBkList); //把书2加到bk1与MyBkList之间,把MyBkList看做头,则为MyBkList->bk1->bk2(按照节点next指针,MyBkList的next指针是没有变的,MyBkList的prev指针变了)

cin.get();
}

链表添加了2节点

  (3)头插

  内核还有个头插,相当于每次都插在了head的后面了,即每次都修改了head的next指针,每次都在前面插,相当于栈啦!和上面的基本差不多,不多写了。


/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}

其实这个更好理解,每次都在head节点和head节点后面一个节点之前插入一个节点。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  第四章:从list_head结构体到包含它的给定数据结构

  上面的都是针对struct list_head操作的,插入也只是把给定结构体的的struct
list_head成员传递给函数,但如何才能获得包含struct
list_head的结构体呢?毕竟真正要访问的时候我们想访问到我们真正关心的数据。好了,下面一步一步来

  container_of()(在我的M:\linux-3.14.5\include\linux下kernel.h文件中)


/**
* 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) );})

它已经描述很清楚,由结构体包含的成员获得包含这个成员的结构体,ptr是指向结构体成员的指针,type是包含给定成员的结构体名称,member是结构体所含指定成员的的在结构体中的名字。

typeof:是g++对c /
c++语法的一个扩展,用来静态获取参数类型,在windows下面一直报错,我就迁徙到linux,用g++就ok比如:

int a = 3;

typeof(a) b = 4; // 相当于 int b = 4;

offsetof:如下,是内核源代码定义的宏(在我的M:\linux-3.14.5\include\linux下stddef.h文件中)


#undef offsetof
#ifdef __compiler_offsetof
#define offsetof(TYPE,MEMBER) __compiler_offsetof(TYPE,MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif

offsetoff(struct_t,
member)宏的作用就是获得成员member在类型struct_t中的偏移量。是不是第零章扯扯淡差不多???

好了,其实理解了第零章的扯扯淡就大概知道怎么做了:获得member相对于type的偏移,再拿给定的member地址减去这个便宜不就是这个结构体的地址吗?这里主要注意一些参数类型,因为这个宏针对各种结构体类型都可以的:

typeof(((type *)0)->member):获得type结构体中member成员名所对应的具体类型,是int呢还是struct
list_head呢等等,如果对应我的程序,type是struct Book,member是struct
list_head类型的list,则这里获得的是struct list_head;

const typeof(((type *)0)->member) *__mptr = (ptr);看看对应我的程序就懂了,即 const
struct list_head  *__mptr = ptr
,就是定义了一个结构体中member所对应的类型的指针,并把ptr赋给它,因为在使用这个宏的时候传递的ptr只是一个大概0xffffffff形式的member的地址,不知道具体类型,member也只是自己起的名字。现在
__mptr是有身份的地址啦!

(type *)((char *)__mptr - offsetof(type,
member)):对应我的程序就是那struct Book里面的list变量地址减去struct Book里面list的偏移,得到的就是这个结构体的地址,再强制转换成struct
Book类型。搞定!但还是不明白为什么要进行ptr到__mptr的转换......

这里利用编译器技术的小技巧,即先求得结构成员在与结构中的偏移量,然后根据成员变量的地址反过来得出属主结构变量的地址。

时间: 2024-10-16 06:10:26

Linux 内核 链表 的简单模拟的相关文章

Linux 内核 链表 的简单模拟(2)

接上一篇Linux 内核 链表 的简单模拟(1) 第五章:Linux内核链表的遍历 /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) for (pos = (head)->next; pos != (head);

例说Linux内核链表(二)

链表使用 我认为熟悉内核链表功能最好的方法就是看一些简单的实例,实例是一个非常好的素材去更好的理解链表. 下面是一个例子,包含创建,添加,删除和遍历链表. <span style="font-size:14px;"><span style="color:#330099;">#include <stdio.h> #include <stdlib.h> #include "list.h" struct

Linux 内核链表使用举例

链表数据结构的定义很简洁: struct list_head { struct list_head *next, *prev; }; list_head结构包含两个指向list_head结构的指针prev和next,该内核链表具备双链表功能,通常它都组织成双循环链表,这里的list_head没有数据域.在Linux内核链表中,不是在链表结构中包含数据,而是在数据结构中包含链表节点.下面是一个简单的内核模块的例子,包含了对链表进行插入.删除.遍历的一些函数: list.c: #include <l

Linux内核网络报文简单流程

转:http://blog.csdn.net/adamska0104/article/details/45397177 Linux内核网络报文简单流程2014-08-12 10:05:09 分类: Linux linux下的网卡驱动中通常会提供类似XXX_rx的接收函数 该函数处理与具体硬件相关的寄存器操作 包括中断检查,数据状态检查,错误检查等 在确认有数据抵达后读取数据或从DMA的接收环中获取数据地址 XXX_rx函数以skb为元数据结构组织报文数据 随后调用内核接口函数netif_rx或n

链表的艺术——Linux内核链表分析

引言: 链表是数据结构中的重要成员之中的一个.因为其结构简单且动态插入.删除节点用时少的长处,链表在开发中的应用场景许多.仅次于数组(越简单应用越广). 可是.正如其长处一样,链表的缺点也是显而易见的.这里当然不是指随机存取那些东西,而是因为链表的构造方法(在一个结构体中套入其同类型指针)使得链表本身的逻辑操作(如添加结点,删除结点,查询结点等),往往与其应用场景中的业务数据相互混杂.这导致我们每次使用链表都要进行手工打造,做过链表的人肯定对此深有了解. 是否能将链表从变换莫測的业务数据中抽象出

C语言 Linux内核链表(企业级链表)

//Linux内核链表(企业级链表) #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #define offscfof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER) #define container_of(ptr,type,member) (type *)((char *)ptr-off

Linux内核链表复用实现队列

有了前面Linux内核复用实现栈的基础,使用相同的思想实现队列,也是非常简单的.普通单链表复用实现队列,总会在出队或入队的时候有一个O(n)复杂度的操作,大多数采用增加两个变量,一个head,一个tail来将O(n)降成O(1).但是在内核链表中,天然的双向循环链表,复用实现队列,无论出队还是入队,都是O(1)时间复杂度. /* main.c */ #include <stdio.h> #include <stdlib.h> #include "queue.h"

Linux 内核链表

一 . Linux内核链表 1 . 内核链表函数 1.INIT_LIST_HEAD:创建链表 2.list_add:在链表头插入节点 3.list_add_tail:在链表尾插入节点 4.list_del:删除节点 5.list_entry:取出节点 6.list_for_each:遍历链表 2.程序代码

例说Linux内核链表(一)

介绍 众所周知,Linux内核大部分是使用GNU C语言写的.C不同于其他的语言,它不具备一个好的数据结构对象或者标准对象库的支持.所以可以借用Linux内核源码树的循环双链表是一件很值得让人高兴的事. 在include/linux/list.h文件中用C实现了一个好用的循环链表.它是有效而且易于操作的,否则它也不会被内核使用(译者注:在kernel中大量的使用了循环双链表结构,比如在在进程描述符实体中我们就可以看到很多struct list_head的身影).不管何时,依靠这种结构,在内核中都