第零章:扯扯淡

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

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

参考答案:#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)structstudent
{int a;    //FIND(struct student,a) 等于0char b;    //FIND(struct student,b)等于4doublec;
};using namespacestd;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();
}

View Code

输出为 4 0 4 8

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

  第一章:普通链表

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

/*表示书的结构体*/
structBook
{intbkId;stringbkName;struct Book *prev;    //指向链表中前一个节点struct Book *next;    //指向链表中后一个节点
};

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

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

  第二章:Linux内核链表

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

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

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

structlist_head {struct list_head *next, *prev;
};/*表示书的结构体*/
structBook
{intbkId;stringbkName;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;/*链表节点*/
structlist_head {struct list_head *next, *prev;
};/*表示书的结构体*/
structBook
{intbkId;stringbkName;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 namespacestd;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 namespacestd;int main(void)
{struct list_head MyBkList;    //创建我的链表头INIT_LIST_HEAD(&MyBkList);   //初始化这个链表/*创建新书结构体*/structBook bk1;bk1.bkId= 1;bk1.bkName= "book1";list_add_tail(&bk1.list, &MyBkList);        //把新书1加到头结点MyBkList后面structBook 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的转换......

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

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

转载于:https://www.cnblogs.com/jiayith/p/3772908.html

Linux 内核 链表 的简单模拟(1)相关推荐

  1. linux内核链表以及list_entry--linux内核数据结构(一)

    传统的链表实现 之前我们前面提到的链表都是在我们原数据结构的基础上增加指针域next(或者prev),从而使各个节点能否链接在一起, 比如如下的结构信息 typedef struct fox { un ...

  2. linux内核链表分析

    一.常用的链表和内核链表的区别 1.1  常规链表结构        通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系.按照指针域的组织以及各个节 ...

  3. 深入分析 Linux 内核链表--转

    引用地址:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据 ...

  4. 第六天2017/04/11(2:Linux内核链表Demo、顺序表、链表的开发与设计)

    //结构体相关的高级话题#include "stdio.h" #include "stdlib.h" #include "string.h" ...

  5. Linux 内核链表 【转】

    原文:http://www.ibm.com/developerworks/cn/linux/kernel/l-chain/index.html 一. 链表数据结构简介 链表是一种常用的组织有序数据的数 ...

  6. Linux驱动编程 step-by-step (十) Linux 内核链表

    终于可以清闲下来打理一下我的blog了,台资企业真的事情很多很烦-- 前几篇文章对字符设备有个简单介绍,并以简单的一个字符设备驱动作结尾,其实linux上大部分驱动程序都是字符设备程序,Linux源码 ...

  7. 深入理解Linux内核链表

    之前写过的链表文章,再结合这篇,我觉得是一道硬菜. Linux内核链表 C语言,链表 大家五一节日快乐,我知道劳动节大家都辛苦了,吃点硬菜好顶住饿肚子~ #一. 链表数据结构简介 链表是一种常用的组织 ...

  8. 如下为利用Linux内核链表创建,Linux内核中链表的实现与应用

    链表(循环双向链表)是Linux内核中最简单.最常用的一种数据结构. 1.链表的定义 struct list_head { struct list_head *next, *prev; } 这个不含数 ...

  9. 【C++】链表反转逆序|建立、删除、修改、插入|linux内核链表与普通链表

    目录 C++实现链表逆序 链表的建立.删除.修改.插入 linux内核链表与普通链表 C++实现链表逆序 实现链表逆序,首先要有一个链表,下面是链表的结构体: typedef struct listn ...

最新文章

  1. 学习实例.文章管理.数据库操作类.DBUtil.java
  2. 欲从事服务端工作不懂seata?一篇小demo零基础带你快速掌握分布式事务框架seata的基本使用!
  3. python __reduce__魔法方法_Python魔法方法指南
  4. mysql体系结构和存储引擎
  5. Jmeter新手频犯错误之一(登录)
  6. 上传Text文档并转换为PDF(解决乱码)
  7. 「镁客·请讲」归墟电子王景阳:以桌面小型机器人切入市场,沿着“机器人+教育”的方向前进...
  8. 这15个网站,为设计师提供用不完的免费素材
  9. C# panel控件实现鼠标滚轮滚动拖动滚动条
  10. css 实现一个尖角_(转载) css实现小三角(尖角)
  11. springboot整合jett实现模板excel数据导出
  12. java 通用权限管理_通用权限管理设计篇(一)
  13. 小数取整 四舍五入
  14. 关于Win10的powerdesigner16的缩放问题,字体和图标
  15. POI批量导出Excel ZIP打包下载
  16. 如何利用Python动态模拟太阳系运转
  17. 组学数据上传(五):代谢组学数据
  18. Base64解密算法、AES解密
  19. Java Heap堆分析
  20. socket的延时技巧

热门文章

  1. mysql 宽字符注入_5. 宽字符注入详解与实战
  2. java 最简单一个家庭支付收入项目(实用于刚入门的小伙伴)
  3. 开发这么久你真知道for循环内部执行顺序吗?
  4. 但是,使用Navicat for MySQL软件连接失败,报错1862
  5. java序列化和反序列化对象_java中的序列化与反序列化,还包括将多个对象序列化到一个文件中...
  6. python单词统计、给定一个段落()_自己动手Python写一个词频统计小项目
  7. python能做机器人吗_最火的Python语言也能做机器人仿真,你会不?
  8. ggplot2作图详解:图层语法和图形组合
  9. python pandas series加速原理_python pandas中对Series数据进行轴向连接的实例
  10. 数据表 高水位 mysql_Oracle中的高水位(HWM)