前言
Linux内核编程中,会经常看见一个宏函数container_of,那么这究竟是什么呢,本篇博客记录学习container_of的过程。
嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!
目录
- 前言
- 1. 简介
- 1.1 用途
- 1.2 举例说明
- 2. 代码尝试
- 2.1 结构体定义
- 2.2 container_of使用
- 2.3 完整代码
- 3. linux中的定义
- 参考资料
1. 简介
1.1 用途
在代码管理多个数据结构时,几乎总是需要将一个结构嵌入到另一个结构中,并随时检索它们,而不关心内存偏移或边界的问题。
container_of(ptr, type, member)
是一个宏函数,可以通过结构体成员的地址找到结构体的地址。
ptr——结构体成员地址
type——结构体类型
member——结构体成员在结构体里的名字
1.2 举例说明
虽然其定义看着比较吓人,但是使用起来是比较简单的。
假设有一个结构体存储了一些成员:
typedef struct _animal {double age;double weight;
}animal;
然后我们使用container_of就可以根据变量获取结构体:
animal cat = {2, 20};
animal *p_cat = NULL;
...
p_cat = container_of(&cat.age, animal, age);
就是如此简单,有一个结构体,然后一个结构体指针,那么就可以根据其中的变量来反求出结构体并赋给结构体指针。
2. 代码尝试
现在我们来用一个完整的例子来看看container的使用,既然出发点是嵌套结构体,那么举的例子就嵌套结构体成员进去。而事实上,内核开发都是结构体套结构体的。
2.1 结构体定义
首先先定义一个结构体表示工程师:
// 一个员工类
typedef struct engineer_Struct {int age; // 年龄char* name; // 姓名
}Engineer;
接下来定义一个公司类,公司类中包含了各种工程师和员工人数,假设现在有c++工程师和java工程师:
// 一个公司类
typedef struct company_Struct {Engineer cpp; // c艹工程师Engineer java; // java工程师int employee_count; // 员工人数
} company;
2.2 container_of使用
现在我们在入口函数中使用container_of宏,通过成员获取结构体。我们先定义一个c++工程师,年龄为23,名字叫wp1,再定义一个java工程师,年林为32,名字叫wp2。如果container_of宏根据c++找到的结构体能找到java,那么就说明是成功的:
Engineer wp1 = {23, "wp1"};Engineer wp2 = {32, "wp2"};Company company = {wp1, wp2, 10};Company *com_ptr;com_ptr = container_of(&company.cpp, Company, cpp); // 使用container_of查找结构体printk("the java engineer's age is %d and name is %s\r\n", com_ptr->java.age, com_ptr->java.name);
最后就是写下完整的驱动代码,并到板子上实验了
2.3 完整代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/of_device.h>#define chrdevTest_CNT 1
#define chrdevTest_NAME "chrdevTest"// 一个员工类
typedef struct engineer_Struct {int age; // 年龄char* name; // 姓名
}Engineer;// 一个公司类
typedef struct company_Struct {Engineer cpp; // c艹工程师Engineer java; // java工程师int employee_count; // 员工人数
} Company;// 设备结构体
struct chrdevTest_dev {dev_t devid;struct cdev cdev;struct class *class;struct device *device;int major;int minor;struct device_node *nd;
};struct chrdevTest_dev chrdevTest; // 字符设备static int chrdevTest_open(struct inode *inode, struct file *filp) {filp->private_data = &chrdevTest;return 0;
}static ssize_t chrdevTest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {return 0;
}static ssize_t chrdevTest_write(struct file *filp, const char __user* buf, size_t cnt, loff_t* offt) {return 0;
}static ssize_t chrdevTest_release(struct inode* inode, struct file* filp) {return 0;
}static struct file_operations chrdevTest_fops = {.owner = THIS_MODULE,.open = chrdevTest_open,.read = chrdevTest_read,.write = chrdevTest_write,.release = chrdevTest_release,
};// 入口函数
static int __init containerTest_init(void)
{Engineer wp1 = {23, "wp1"};Engineer wp2 = {32, "wp2"};Company company = {wp1, wp2, 10};Company *com_ptr;printk("this is init function\r\n");com_ptr = container_of(&company.cpp, Company, cpp);printk("the java engineer's age is %d and name is %s\r\n", com_ptr->java.age, com_ptr->java.name);if (chrdevTest.major) {chrdevTest.devid = MKDEV(chrdevTest.major, 0);register_chrdev_region(chrdevTest.devid, chrdevTest_CNT, chrdevTest_NAME);} else {alloc_chrdev_region(&chrdevTest.devid, 0, chrdevTest_CNT, chrdevTest_NAME); // 申请设备号chrdevTest.major = MAJOR(chrdevTest.devid); // 获取主设备号chrdevTest.minor = MINOR(chrdevTest.devid); // 获取次设备号}printk("chrdevTest major=%d, minor=%d\r\n", chrdevTest.major, chrdevTest.minor);// cdevchrdevTest.cdev.owner = THIS_MODULE;cdev_init(&chrdevTest.cdev, &chrdevTest_fops);// 添加cdevcdev_add(&chrdevTest.cdev, chrdevTest.devid, chrdevTest_CNT);// 创建类chrdevTest.class = class_create(THIS_MODULE, chrdevTest_NAME);if (IS_ERR(chrdevTest.class)) {return PTR_ERR(chrdevTest.class);}// 创建设备chrdevTest.device = device_create(chrdevTest.class, NULL, chrdevTest.devid, NULL, chrdevTest_NAME);if (IS_ERR(chrdevTest.device)) {return PTR_ERR(chrdevTest.device);}return 0;
}// 出口函数
static void __exit containerTest_exit(void)
{printk("this is exit function\r\n");cdev_del(&chrdevTest.cdev);unregister_chrdev_region(chrdevTest.devid, chrdevTest_CNT);device_destroy(chrdevTest.class, chrdevTest.devid);class_destroy(chrdevTest.class);
}module_init(containerTest_init);
module_exit(containerTest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wp");
编写makefile文件,makefile内容如下所示。同时将生成的模块文件.ko
放入到设备对应的文件夹下。
KERNELDIR := /home/wp/Linux/IMX6ULL/alientek_linux 此处是你的linux内核地址(要修改)
CURRENT_PATH := $(shell pwd)
obj-m := chrdevTest.o 此处是你要生成的文件(要修改)build: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
最后输入以下指令加载驱动
depmod
insmod chrdevTest.ko
加载完驱动后,可以看到我们之前用printk
想要显示的内容如下所示:
根据输出的信息,可以发现,输出了java工程师的年龄是32,名字叫wp2,通过container_of宏成功根据cpp工程师找到了结构体,并重新根据结构体找到了java工程师的信息。
最后可以卸载驱动:
rmmod chrdevTest.ko
3. linux中的定义
在知晓了其怎么使用后,下面可以来探究一下其在linux中的源码是如何实现的
其在linux源码中的定义如下,定义在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) );})
我们来拆分一下每一句,首先是第一句:
const typeof( ((type *)0)->member ) *__mptr = (ptr);
首先来看"0"指针。假设结构体变量的起始地址为"0",那么具体成员的地址其实就是相对于结构体的偏移量。(type *)0
是把"0"强制转换成结构体类型的地址;((type *)0)->member
是以0为起始地址的结构体成员变量member。那么用((type *)0)->member
定义出来的变量就是成员变量的地址。
由前面的铺垫,我们知道了ptr
是一个结构体成员变量member
的地址,所以ptr
的类型得是一个指向member
数据类型的指针。通过typeof( ((type *)0)->member )
就可以获取到结构体成员member
的数据类型,所以这句话就是把临时变量__mptr
定义为结构体成员的类型。
但是有个缺点,那就是如果ptr和成员变量类型不一致,就会报warning,这一点在后续的内核版本中得到了解决。
后续版本中,拆分为了两句话,第一句是先定义了一个void指针 __mptr
,然后用了一个断言BUILD_BUG_ON_MSG
,断言判断就是__same_type
,检查如果ptr
与实际类型不一致,就参数warning,直接中断编译。
void *__mptr = (void *)(ptr); \
BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) && \!__same_type(*(ptr), void), \"pointer type mismatch in container_of()"); \
下面来看第二句:
(type *)( (char *)__mptr - offsetof(type,member) );
拿结构体某个成员 member
的地址,减去这个成员在结构体 type
中的偏移,结果就是结构体 type
的首地址。container_of
最后就会返回这个地址值给宏的调用者。
这个offset宏定义如下所示:
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
这个宏中,将0强制转换为一个指向TYPE的结构体常来那个指针,然后通过这个常量指针访问成员获取member地址,其大小在数值上等于member在结构体中的偏移。
参考资料
[1] Linux container_of() 函数详解
[2] Linux 内核 container_of 宏详解
[3] container of()函数用法简介
[4] 话说Linux内核链表之“container_of“(二)