哪里可以学酷家乐设计,昆明网站建设优化企业,武进建设银行网站首页,产品开发流程介绍前言 Linux内核编程中#xff0c;会经常看见一个宏函数container_of#xff0c;那么这究竟是什么呢#xff0c;本篇博客记录学习container_of的过程。 嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程#xff0c;未来预计四个月将高强度更新本专栏#xff0c;喜欢的可…前言 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 engineers 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 engineers 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.ko3. 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“二