container_of宏定义分析---linux内核

时间:2022-06-19 02:42:32

问题:如何通过结构中的某个变量获取结构本身的指针???

关于container_of宏定义在[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) );})
container_of在Linux Kernel中的应用非常广泛,它用于获得某结构中某成员的入口地址.

关于offsetof宏定义在[include/linux/stddef.h]中:
#define offsetof(type, member) ((size_t) &((type *)0)->member)
type是某struct的类型 0是一个假想type类型struct,member是该struct中的一个成员. 由于该struct的基地址为0, member的地址就是该成员相对与struct头地址的偏移量.
关于typeof,这是gcc的C语言扩展保留字,用于声明变量类型.
const typeof( ((type *)0->member ) *__mptr = (ptr);意思是声明一个与member同一个类型的指针常量 *__mptr,并初始化为ptr.
(type *)( (char *)__mptr - offsetof(type,member) );意思是__mptr的地址减去member在该struct中的偏移量得到的地址, 再转换成type型指针. 该指针就是member的入口地址了.

例一:

用一个测试程序test.c来说明

#include<stdio.h>
struct student{
    char name[20]; 
    char sex;
}stu={"zhangsan",'m'};

main()
{
    struct student *stu_ptr;    //存储container_of宏的返回值
    int offset;                        //存储offsetof宏的返回值

//下面三行代码等同于 container_of(&stu.sex,struct student, sex )参数带入的情形
//首先定义一个_mptr指针,类型为struct student 结构体中sex成员的类型
    const typeof(((struct student*)0)->sex) *_mptr = &stu.sex;
//typeof 为获取(((struct student*)0)->sex)的类型,此处此类型为char
//((struct student*)0)在offsetof处讲解

offset = (int)(&((struct student *)0)->sex);
/**

*((struct student*)0)为 把 0地址 强制转化为指向student结构体类型的指针
*该指针从地址 0 开始的 21个字节用来存放name 与 sex,(char name[20] 与 char sex 共 21 字节)
*sex存放在第20个字节处(从0字节开始)
*&((struct student *)0)->sex 取出sex地址(此处即为20) 并强制转化为整形
*所以offset为20,后面的printf结果将证明这一点

**/
    stu_ptr = (struct student *)((char*)_mptr - offset);
/**

*((char*)_mptr - offset)此处先把_mptr指针转化为字符形指针
*(为什么这么做呢? 如果_mptr为整形指针 _mptr - offset 相当于减去 sizeof(int)*offset个字节)
*减去 offset值 相当于 得到_mptr所在结构体的首地址(即stu的地址)
*然后我们把 该地址 强制转化为 struct student类型即可正常使用了

**/
    printf("offsetof stu.sex = %d/n",offset); 
    printf("stu_ptr->name:%s/tstu_ptr->sex:%c/n", stu_ptr->name, stu_ptr->sex);
    return 0;
}

例二:

container_of作用显而易见,那就是根据结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

比如,有一个结构体变量,其定义如下:

1. struct demo_struct {
   2.     type1 member1;
   3.     type2 member2;
   4.     type3 member3;
   5.     type4 member4;
   6. };
   7.
   8. struct demo_struct demo;

同时,在另一个地方,获得了变量demo中的某一个域成员变量的指针,比如:

1. type3 *memp = get_member_pointer_from_somewhere();

此时,如果需要获取指向整个结构体变量的指针,而不仅仅只是其某一个域成员变量的指针,我们就可以这么做:

1. struct demo_struct *demop = container_of(memp, struct demo_struct, member3);
首先,我们将container_of(memp, struct demo_struct, type3)根据宏的定义进行展开如下:

1. struct demo_struct *demop = ({                      /
   2.     const typeof( ((struct demo_struct *)0)->member3 ) *__mptr = (memp);    /
   3.     (struct demo_struct *)( (char *)__mptr - offsetof(struct demo_struct, member3) );})

其中,typeof是GNU C对标准C的扩展,它的作用是根据变量获取变量的类型。因此,上述代码中的第2行的作用是首先使用typeof获取结构体域变量member3的类型为 type3,然后定义了一个type3指针类型的临时变量__mptr,并将实际结构体变量中的域变量的指针memp的值赋给临时变量__mptr。

假设结构体变量demo在实际内存中的位置如下图所示:
     demo
+-------------+ 0xA000
|   member1              |
+-------------+ 0xA004
|   member2             |
|                                |
+-------------+ 0xA010
|   member3             |
|                                |
+-------------+ 0xA018
|   member4             |
+-------------+

则,在执行了上述代码的第2行之后__mptr的值即为0xA010。

再看上述代码的第3行,其中需要说明的是offsetof,它定义在include/linux/stddef.h中,其定义如下:

1. 24#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

同样,我们将上述的offsetof调用展开,即为:

1. (struct demo_struct *)( (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) );

可见,offsetof的实现原理就是取结构体中的域成员相对于地址0的偏移地址,也就是域成员变量相对于结构体变量首地址的偏移。

因此,offsetof(struct demo_struct, member3)调用返回的值就是member3相对于demo变量的偏移。结合上述给出的变量地址分布图可知,offsetof(struct demo_struct, member3)将返回0x10。

于是,由上述分析可知,此时,__mptr==0xA010,offsetof(struct demo_struct, member3)==0x10。
因此, (char *)__mptr - ((size_t) &((struct demo_struct *)0)->member3) == 0xA010 - 0x10 == 0xA000,也就是结构体变量demo的首地址(如上图所示)。

由此,container_of实现了根据结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针的功能。

参考链接:

http://blog.csdn.net/yinkaizhong/article/details/4093795

http://www.cnitblog.com/puppypyb/archive/2008/08/20/48172.aspx

http://bbs.ednchina.com/BLOG_ARTICLE_216992.HTM

http://blog.chinaunix.net/uid-26510579-id-3198843.html

http://blog.sina.com.cn/s/blog_55a7eb8201009elt.html

container_of宏定义分析---linux内核的更多相关文章

  1. linux内核container&lowbar;of宏定义分析

    看见一个哥们分析container_of很好,转来留给自己看 一.#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMB ...

  2. 《Linux内核--分析Linux内核创建一个新进程的过程 》 20135311傅冬菁

    20135311傅冬菁 分析Linux内核创建一个新进程的过程 一.学习内容 进程控制块——PCB  task_struct数据结构 PCB task_struct中包含: 进程状态.进程打开的文件. ...

  3. 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程

    结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 目录 结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程 一. 实验准备 二. 实验过程 I 分析中断上下文的切换 ...

  4. linux内核分析作业6:分析Linux内核创建一个新进程的过程

    task_struct结构: struct task_struct {   volatile long state;进程状态  void *stack; 堆栈  pid_t pid; 进程标识符  u ...

  5. 分析Linux内核创建一个新进程的过程【转】

    转自:http://www.cnblogs.com/MarkWoo/p/4420588.html 前言说明 本篇为网易云课堂Linux内核分析课程的第六周作业,本次作业我们将具体来分析fork系统调用 ...

  6. 通过从代码层面分析Linux内核启动来探知操作系统的启动过程

    通过从代码层面分析Linux内核启动来探知操作系统的启动过程 前言说明 本篇为网易云课堂Linux内核分析课程的第三周作业,我将围绕Linux 3.18的内核中的start_kernel到init进程 ...

  7. 第六周分析Linux内核创建一个新进程的过程

    潘恒 原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 task_struct结构: ...

  8. 跟踪分析Linux内核的启动过程--实验报告 分析 及知识重点

    跟踪分析Linux内核的启动过程 攥写人:杨光  学号:20135233 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.stud ...

  9. 实验 六:分析linux内核创建一个新进程的过程

    实验六:分析Linux内核创建一个新进程的过程 作者:王朝宪  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029 ...

随机推荐

  1. Java中StringBuilder的清空方法比较

    StringBuilder 没有提供clear或empty方法.清空有3种方法:1)使用delete2)新生成一个,旧的由系统自动回收3)使用setLength 将三种方法循环1000万次,代码: p ...

  2. 7zip ubuntu使用

    http://www.cnblogs.com/yiwd/p/3649094.html 安装: sudo apt-get install p7zip 解压: 7zr x file -r -o./path ...

  3. nodejs高大上的部署方式-PM2

    1.最常用的属nohup了,其实就是在后台执行进程,末尾加个&   [zhoujie@ops-dev ~]$ nohup node /home/zhoujie/ops/app.js & ...

  4. hibernate 问题

    如果hibernate中反转的表中没有主键的话,会生产三个文件. table.java tableADO.java tableId.java 并且在执行findByProperty时,会提示:coul ...

  5. SpringMVC学习总结&lpar;四&rpar;——基于注解的SpringMVC简单介绍

    SpringMVC是一个基于DispatcherServlet的MVC框架,每一个请求最先访问的都是 DispatcherServlet,DispatcherServlet负责转发每一个Request ...

  6. &lbrack;cocos2d&rsqb; 显示状态与文字

    前言: 对于显示数值与文字一般有三种方式可以使用: CCLabelTTF .CCLabelBMFont .CCLabelAtlas 详细区别可参考:cocos2d 中添加显示文字的三种方式(CCLab ...

  7. 服务器搭建纪录linux&plus;mysql&plus;nginx&plus;php

    新的项目启动 第一版 首先买了阿里云,选好环境镜像包,一键安装. 第一版php打算不用框架,完全手写,主要的功能点 数据交互和图片传输. 后台搭建好后,使用PHP的Laravel, web端还是选定b ...

  8. 小练习,判断X的奇偶性

    package lianxi1; public class text { public static void main(String[] args) { ; ==) { System.out.pri ...

  9. DAX&sol;PowerBI系列 - 库存总价值(Inventory Value)

    DAX/PowerBI系列 - 库存总价值(Inventory Value) 欢迎交流与骚扰 难度: ★★☆☆☆(2星) 适用: ★★☆☆☆(2星) 概况: 有多少货(库存)当然重要(对于运营人员), ...

  10. location的三种连接方式和区别

    location.href是一个属性,要这样使用:location.href='http://www.example.com'而location.assign('http://www.example. ...