嵌入式驱动学习第三周——container_of()宏

前言

   Linux内核编程中,会经常看见一个宏函数container_of,那么这究竟是什么呢,本篇博客记录学习container_of的过程。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

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);

    // cdev
    chrdevTest.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevTest.cdev, &chrdevTest_fops);

    // 添加cdev
    cdev_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_modules

kernel_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“(二)

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值