您的位置:首页 > 运维架构 > Linux

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

2014-06-06 16:36 260 查看
  第零章:扯扯淡

  出一个有意思的题目:用一个宏定义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>
#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的转换......

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

下接:Linux 内核 链表 的简单模拟(2)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: