在Linux内核中,许多数据结构是通过指针形式连在一起的,每个结构都有一个特定的目的,它们之间的相互关系在代码中很明显。然而,在某些情况下,需要更复杂的它们之间的关系才能实现特定的行为。这时就可以使用container_of来实现从指针获取宿主数据的神器。
什么是container_of?
container_of是Linux内核中一个非常有用的宏,它允许我们从一个结构成员的指针中得到该结构的基地址。该宏的语法如下:
```
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
```
这个宏接受三个参数:第一个参数是结构体成员的指针,第二个参数是结构体类型,第三个参数是要获取的成员名。
container_of工作原理
container_of的工作原理是通过偏移量找到宿主结构体的内存地址。宏展开后,会产生如下的代码:
```
const typeof(&((type *)0->member)) *__mptr = (ptr);
(type *)((char *)__mptr - offsetof(type, member));
```
首先看一下第一行代码:
```
const typeof(&((type *)0->member)) *__mptr = (ptr);
```
这一行代码中使用了typeof运算符,它的作用是获取成员变量的类型。这里我们从0地址获取member变量的指针,然后将typeof运算符应用于该指针,就能得到一个指向member变量的指针的类型。由于要声明的是指针,因此需要在其前面加上“&”符号。最后,使用指针“ptr”的值将该指针初始化。
第二行代码是相对简单的计算:
```
(type *)((char *)__mptr - offsetof(type, member));
```
它使用offsetof宏计算出member变量在type结构体中的偏移量,再将这个偏移量应用到__mptr指针上,以获取宿主type结构体的地址。
container_of的使用场景
1. 容器
容器形式经常用于实现列表或树这样的数据结构。在一个典型的容器内,每个元素都有一个指向相邻元素的指针,例如链表或树节点。对于任何一个元素,从其指针中找到相邻元素就容易了。但是,有时候必须从子元素(例如,一个树节点)找到它所属的容器(例如,一个树)。
这就是使用container_of宏的情况。假设有一个包含多个树节点的树,每个树节点都有一个指向相邻元素的指针和一个指向它的父节点的指针。现在需要查找一个树节点的父节点。可以使用以下代码来实现:
```
struct tree_node {
struct tree_node *parent;
struct tree_node *next;
int value;
};
struct tree_node *get_parent(struct tree_node *node)
{
return container_of(node->parent, struct tree_node, next);
}
```
在这种情况下,node的parent指针是指向它的父节点的,但我们需要的是它所在的父节点结构体的地址。因此,使用container_of宏可以得到它所在的父节点的地址。
2. 用户数据
在Linux内核中,container_of广泛用于获取用户数据的地址。内核中的许多结构体包含指针,指向与该结构体相关的用户数据。虽然用户数据的大小和类型通常是未知的,但container_of使内核程序员不必为每个用户数据类型都编写专门的地址计算程序。
例如,以struct file为例,它代表了进程打开的文件。在内核中使用struct file结构体表示文件句柄,但在用户空间中会将文件描述符映射到struct file结构体的内存地址。如果在内核中已经具有指向struct file结构体的指针,可以使用container_of宏找到与该结构体相关的用户数据的地址。它的典型用法如下:
```
struct file_operations *fops = file->f_op;
struct my_data *data = container_of(fops, struct my_data, f);
```
在这种情况下,file->f_op指向用于在内核中打开文件时指定的操作集,可以从中找到与该结构相关联的数据。因此,可以使用container_of来检索该数据的地址。
总结
container_of是Linux内核中一个非常有用的宏,它使内核程序员能够轻松获取到宿主结构的地址,而无需记住它们之间的详细关系。除了在容器和用户数据的情况下,它也可以用于许多其他数据结构。只需注意指针成员的位置和名称即可。使用container_of宏可以使Linux内核中的代码更加可读,更容易维护。