什么是container_of?

首先,container_of的本质是一个宏;
它可以使用某个结构体中的一个元素,反推得到整个结构体对象;
(听起来是不是很像是反射)

为什么需要container_of?

首先作为一个操作系统,linux是需要管理很多的数据的,这些数据大多都是以结构体对象的形式存在的(如果我没有记错的话,大学的C语言课本里,称之为结构体变量,but whatever,我们就暂且称之为对象吧);

这些对象经常需要被遍历,例如有一些结构体存着磁盘使用情况的数据,现在你想要知道每块磁盘的使用状况;那就不得不去一个一个的查询(也就是遍历);能够被遍历的,除了数组就是链表,而数组需要一块连续的内存空间,可是我们都知道内存空间是很难经常性保证能够拿出一大块的,尤其是随着内存的不断申请和释放,内存碎片化会越来越严重,那么就只剩下一个选择了:链表;

linux使用链表表示一些需要被聚合起来的对象;可是这里有一个问题:
假设当前整个linux总共只用到了3个结构体:struct A;structB;struct C;如何实现链表?
或许你会这样想

  1. struct A{
  2. struct A * next;
  3. struct A * prev;
  4. //....一些其他的数据
  5. };
  6. struct B{
  7. struct B * next;
  8. struct B * prev;
  9. //....一些其他的数据
  10. };
  11. struct C{
  12. struct C * next;
  13. struct C * prev;
  14. //....一些其他的数据
  15. };

这样可以吗?
当然可以,他们也完成了这个任务;
!!!但是,linux怎么可能只用到了3个struct呢?面对浩如烟含的struct,难道要像上面这样每一个对象都定义前后指针 以表达链表?
这根本不可能;

linux的实现方式是这样的:

  1. struct list_head{
  2. struct list_head * next, prev;
  3. };

所有的链表都是这样,你没有看错,linux的标准链表中不存在任何其他数据,只有前后指针

现在你或许充满了疑惑,这种链表看起来根本就不能用,毕竟它没办法包含任何具体数据,这正是container_of的作用:假设一个结构体,包含了一个list_head;那么只需要这个结构体的类型,和具体的list_head对象,我们就能用container_of反推出整个结构体;

如何实现?

  1. /**
  2. * container_of - cast a member of a structure out to the containing structure
  3. * @ptr: the pointer to the member.
  4. * @type: the type of the container struct this is embedded in.
  5. * @member: the name of the member within the struct.
  6. *
  7. */
  8. #define container_of(ptr, type, member) ({ \
  9. const typeof( ((type *)0)->member ) *__mptr = (ptr); \
  10. (type *)( (char *)__mptr - offsetof(type,member) );})
  11. #define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

举个例子:
linux中用于描述文件系统的结构:super_block如下:

struct super_block
| … |
| list_head s_file |
| … |

s_file作为一个链表头使用,而真正被连接的是struct file

这个结构如下所示
struct super_block struct file
| … | | … |
| list_head s_file | -> | list_head f_file | -> 下一个file -> 下一个file -> ……
| … | | … |

如果只给了一个super_block对象,如何遍历整个链表?并且处理每个file对象?
首先要理解const typeof( ((type )0)->member ) __mptr = (ptr); \

  1. struct super_block * sb = get_some_sb();
  2. struct file * f;
  3. //翻译下面参数我会替换一下,方便理解
  4. //
  5. #define container_of(sb->s_file->next, typeof(f), f_file) ({ \
  6. const typeof( (((typeof(f)) *)0)->f_file ) *__mptr = (sb->s_file->next); \
  7. //上面这一行(line7)做了如下几件事:
  8. //1 :将一个空指针,也就是0;转换了一个file指针,那么此时,我们可以通过这个file指针访问一切file成员,但是请记住,这个file指针本质上还是一个空指针,地址是0;
  9. //2 :获取了file中的f_file,这是允许的,因为我们只需要使用这个f_file获取类型,并不需要使用它进行运算
  10. //3 :获取到了file->f_file的类型(f_file的类型是list_head,这看起来有一些多次一举,但是其实很有必要,因为container_of并不只用来处理链表,所以这里的类型是任意的,因此必须要获取一下),并且声明一个该类型的指针,安放了sb->s_file->next;
  11. (type *)( (char *)__mptr - offsetof(type,member) );})
  12. //line12的作用和line13密切相关
  13. #define offsetof(file, f_file) ((size_t) &((file *)0)->f_file)
  14. //line13:
  15. //line13是container_of的精髓,它使用了一个空指针,将空指针强制转换类型,转换成了file指针;但是这并不能改变它是0的事实;因此,这个指针指向的f_file,在数值上,等于f_file在struct file中的偏移量;line13巧妙的使用了空指针去获取结构体中某个元素相对与整个结构体的偏移量;
  16. //现在我们知道了f_file相对于struct的偏移量,同时还知道一个file对象中的f_file的地址;相减即可获取到这个file对象的地址,也就是获取到了这个file对象