【问题标题】:How to use container_of() when the variable is in a struct within a struct?当变量在结构中的结构中时如何使用container_of()?
【发布时间】:2021-10-26 07:32:37
【问题描述】:

我浏览了很多关于container_of() 的问题,但没有找到询问如何使用container_of() 在结构中检索结构的问题。如果这个问题是重复的,请指出。很抱歉给您带来不便。

我目前正在实现与另一个内核模块相结合的 PCI 设备驱动程序 remove() 函数,具有以下设置(我删除了一些其他字段以使这个问题易于阅读):

struct my_device {
    struct mutex lock;
    
    struct my_dev {
        struct cdev cdev;
    } my_dev;
    
    struct pci {
        struct cdev cdev;
        struct pci_dev *pdev;
    } pci;
};

问题来了:当我们在struct pci_dev * 类型的struct pci 中有一个指向pdev 的指针时,我们怎么能得到struct pci,然后得到struct my_device 注意在@987654329 @,我已经将dev分配给了之前分配的struct my_device *dev_ptr,即dev_ptr->pci.pdev = dev;

我尝试了以下方法:

struct pci *pci_t;
pci_t = container_of(ptr, struct pci, pdev)

但这没有用。

报错信息如下:

error: call to '__compiletime_assert_243' declared with attribute error: pointer type mismatch in container_of()

任何想法都将不胜感激。


更新:

我尝试了 cmets 中的方法。由于pci驱动的remove()函数有一个参数——struct pci_dev *dev,代表对应的pci设备,所以我做了如下操作:

struct my_device *mydev;
mydev = container_of(&dev, struct my_device, pci.pdev);

但它没有将指向正确结构的指针返回到mydev

【问题讨论】:

  • 请提供MCVE,说明您是如何使用该宏并显示确切的错误消息。
  • 可能不会导致问题,但“struct pci 内指向pdev 的指针”必须是struct pci_dev ** 类型,而不是struct pci_dev*
  • @EthanL。如果结构成员的类型为int,您将需要一个int * 指针,对吗?但如果它的类型为int *,则需要一个int ** 指针。在您的情况下,它的类型为struct pci_dev *,因此您需要一个struct pci_dev ** 指针。如果您想要一个指向类型的指针,则该指针需要比该类型增加一个间接级别。你真的需要明白这一点。这是 C 语言的基础。
  • 所有评论员都遗漏了一个重要的细节,即您在谈论 Linux 内核。因此,正确的解决方案是使用指向struct pci 或更好的struct my_device特定实例 的指针来填充驱动程序数据。不要用双指针丑化你的代码,所以,拜托!更多阅读:stackoverflow.com/questions/23800337/…
  • 您应该使用pci_set_drvdata 将指向您的struct my_device(或您的struct pci)的指针与PCI 设备(由struct pci_device * 指向)相关联,并使用pci_get_drvdata从 PCI 设备中检索该指针。或者,使用嵌入在struct pci_device 中的struct device dev 的地址将pci_set_drvdatapci_get_drvdata 替换为dev_set_drvdatadev_get_drvdata

标签: c linux-kernel linux-device-driver


【解决方案1】:

这是container_of()宏的简化定义:

#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member)))

offsetof(type, member) 宏在 C 标准中进行了描述,它给出了从某个结构类型 type 的开头到该类型的某个成员 member 的开头的字节数。 ptr 应该与指向成员类型的指针兼容(但上面的简化版本不关心)。 (char *)(ptr) 将指针转换为指向成员的字节指针,以便指针减法(char *)(ptr) - offsetof(type, member) 生成指向包含结构类型开头的字节指针。最后,演员 (type *) 将该指针转换为正确的类型。

使用示例。给定:

struct my_device {
    struct mutex lock;
    
    struct my_dev {
        struct cdev cdev;
    } my_dev;
    
    struct pci {
        struct cdev cdev;
        struct pci_dev *pdev;
    } pci;
};

还有一个指向已分配struct my_device的指针:

    struct my_device *mydev = kzalloc(sizeof(*mydev), GFP_KERNEL);

以及指向pci.pdev 成员的指针:

    struct pci_dev **ppdev = &mydev->pci.pdev;

然后可以使用container_of() 将其转换回指向包含struct my_device 的指针:

    struct my_device *myd = container_of(ppdev, struct my_device, pci.pdev);

OP 有这样的东西:

    struct my_device *mydev = kzalloc(sizeof(*mydev), GFP_KERNEL);
    mydev->pci.pdev = pcidev;

并试图从指针pcidev 回到struct my_device,如下所示:

    struct my_device *mydev = container_of(&pcidev, struct my_device, pci.pdev); /* wrong! */

这不起作用,因为&pcidevpcidev 变量的地址,而不是原始mydev->pci.pdev 成员的地址。


Linux 内核允许 PCI 设备驱动程序将任意 void * 值与 PCI 设备相关联。这通常在 PCI 设备驱动程序的 probe() 处理程序中完成,如下所示:

    pci_set_drvdata(pcidev, mydev);

然后,直到 PCI 设备从驱动程序中删除,或者驱动程序使用其他值调用 pci_set_drvdata,指针可以通过调用 pci_get_drvdata() 来检索:

    struct my_device *mydev = pci_get_drvdata(pcidev);

更一般地说,pci_set_drvdata()pci_get_drvdata() 只是 dev_set_drvdata()dev_get_drvdata() 的包装器,它们将任意 void * 值与 struct device 相关联。 struct pci_device 结构定义包含成员 struct device dev;pci_set_drvdata(pcidev, ptr) 等价于dev_set_drvdata(&pcidev->dev, ptr)pci_get_drvdata(pcidev) 等价于dev_get_drvdata(&pcidev->dev)

通常,驱动程序代码有一个指向 struct device 的指针,它知道它嵌入在 struct pci_dev 中,它可以使用 container_of() 获取指向包含 struct pci_dev 的指针:

/* dptr is pointing the the struct device inside a struct pci_dev. */
static void foo(struct device *dptr)
{
    struct pci_dev *pcidev = container_of(dptr, struct pci_dev, dev);
    /* ... */
}

如果驱动程序之前使用过pci_set_drvdata()dev_set_drvdata(),则可以使用pci_get_drvdata()dev_get_drvdata()检索设置值:

static void foo(struct device *dptr)
{
    struct pci_dev *pcidev = container_of(dptr, struct pci_dev, dev);
    struct my_device *mydev = pci_get_drvdata(pcidev);
    struct my_device *mydev1 = dev_get_drvdata(dptr);
    /* (mydev == mydev1) is true */
    struct pci_dev *pcidev1 = mydev->pci.pdev;
    /* (pcidev == pcidev1) is true assuming mydev->pci.pdev was previously set to pcidev */
    /* ... */
}

虽然它有效,但将pci_set_drvdata() 与先前对dev_get_drvdata() 的调用混合或将pci_get_drvdata() 与对dev_set_drvdata() 的先前调用混合可能是不好的风格。

【讨论】:

  • 虽然不允许在评论中仅仅表达感谢,但我想在这里破例,因为这是我得到的最不可思议和最有帮助的答案。非常感谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多