在linux内核里,可以看到很多devm_*开头的函数; 它是在已有的函数基础上添加了前缀devm, 相对于已有的函数, 它肯定添加了功能; devm的作用是自动释放申请的资源,无需再手动释放;本文主要分析这种机制的实现;
设想以下,如果我们自己来实现这个功能,该如何操作呢?
a. 首先定义结构体,包含: 1). 资源, 2). 释放函数,3). 链表头;
b. 两个操作函数: 1). 结构体构造函数, 2). 将结构体添加到device的资源链表里;
c. 当device的引用计数为0时, 会触发结构体里的释放函数;
这些事情linux内核已经做好了,下面分析如何实现的;
devm相关代码一般在'devres.c'文件中:
核心: drivers/base/devres.c
gpio: drivers/gpio/devres.c
irq: kernel/irq/devres.c
spi: drivers/spi/spi.c
结构体定义, drivers/base/devres.c
struct devres {
struct devres_node node;
unsigned long long data[]; //资源
};
struct devres_node {
struct list_head entry; //链表头
dr_release_t release; //释放函数
#ifdef CONFIG_DEBUG_DEVRES
const char *name;
size_t size;
#endif
};
结构体构造函数:
void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
{
struct devres *dr;
//定义了释放函数,需要驱动自己去实现:
dr = alloc_dr(release, size, gfp | __GFP_ZERO);
if (unlikely(!dr))
return NULL;
//返回资源数组的指针, 各驱动自己填充要自动释放的资源到这里:
return dr->data;
}
将结构体添加进device链表:
void devres_add(struct device *dev, void *res)
{
struct devres *dr = container_of(res, struct devres, data);
unsigned long flags;
spin_lock_irqsave(&dev->devres_lock, flags);
add_dr(dev, &dr->node);
spin_unlock_irqrestore(&dev->devres_lock, flags);
}
static void add_dr(struct device *dev, struct devres_node *node)
{
devres_log(dev, node, "ADD");
BUG_ON(!list_empty(&node->entry));
//添加到device的资源链表头里:
list_add_tail(&node->entry, &dev->devres_head);
}
当device的引用计数为0时,会自动调用上面注册的release函数; 下面看下其实现原理; device或driver注册失败,或卸载时, 都会调用kobject_put(), 将引用计数减1, 当引用计数为0时,会自动触发内核里引用计数机制的release函数,引用计数的代码在 include/linux/kref.h 里:
static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref))
{
return kref_sub(kref, 1, release);
}
static inline int kref_sub(struct kref *kref, unsigned int count,
void (*release)(struct kref *kref))
{
WARN_ON(release == NULL);
if (atomic_sub_and_test((int) count, &kref->refcount)) {
//引用计数为0时, 调用release函数:
release(kref);
return 1;
}
return 0;
}
看下linux设备模型里kobject的引用计数:
//lib/kobject.c
void kobject_put(struct kobject *kobj)
{
if (kobj) {
if (!kobj->state_initialized)
WARN(1, KERN_WARNING "kobject: '%s' (%p): is not "
"initialized, yet kobject_put() is being "
"called.\n", kobject_name(kobj), kobj);
//这里设置了kobject的release函数:
kref_put(&kobj->kref, kobject_release);
}
}
static void kobject_release(struct kref *kref)
{
kobject_cleanup(container_of(kref, struct kobject, kref));
}
static void kobject_cleanup(struct kobject *kobj)
{
struct kobj_type *t = get_ktype(kobj);
const char *name = kobj->name;
pr_debug("kobject: '%s' (%p): %s\n",
kobject_name(kobj), kobj, __func__);
...
//调用kobj_type的release函数:
if (t && t->release) {
pr_debug("kobject: '%s' (%p): calling ktype release\n",
kobject_name(kobj), kobj);
t->release(kobj);
...
linux设备模型里kobj_type的定义:
//drivers/base/core.c
static struct kobj_type device_ktype = {
.release = device_release,
.sysfs_ops = &dev_sysfs_ops,
.namespace = device_namespace,
};
看下它的release函数:
//drivers/base/core.c
static void device_release(struct kobject *kobj)
{
struct device *dev = kobj_to_dev(kobj);
struct device_private *p = dev->p;
/*
* Some platform devices are driven without driver attached
* and managed resources may have been acquired. Make sure
* all resources are released.
*
* Drivers still can add resources into device after device
* is deleted but alive, so release devres here to avoid
* possible memory leak.
*/
//释放资源:
devres_release_all(dev);
...
//drivers/base/devres.c
int devres_release_all(struct device *dev)
{
unsigned long flags;
/* Looks like an uninitialized device structure */
if (WARN_ON(dev->devres_head.next == NULL))
return -ENODEV;
spin_lock_irqsave(&dev->devres_lock, flags);
return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
flags);
}
static int release_nodes(struct device *dev, struct list_head *first,
struct list_head *end, unsigned long flags)
__releases(&dev->devres_lock)
{
LIST_HEAD(todo);
int cnt;
struct devres *dr, *tmp;
cnt = remove_nodes(dev, first, end, &todo);
spin_unlock_irqrestore(&dev->devres_lock, flags);
/* Release. Note that both devres and devres_group are
* handled as devres in the following loop. This is safe.
*/
list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
devres_log(dev, &dr->node, "REL");
//调用设备资源的release函数:
dr->node.release(dev, dr->data);
kfree(dr);
}
return cnt;
}
另: kobject_put()函数的调用, 很多地方调用到, 这里仅举几例:
1). driver注册失败:
driver_register(struct device_driver *drv)
bus_remove_driver(drv)
kobject_put(&drv->p->kobj)
2). 卸载drver:
driver_unregister(struct device_driver *drv) //drivers/base/driver.c
bus_remove_driver(drv)
kobject_put(&drv->p->kobj)
3). device注册失败:
bus_remove_device(dev) //drivers/base/bus.c
bus_put(dev->bus)
kset_put(&bus->p->subsys)
kobject_put(&k->kobj)
4). 卸载device:
device_unregister(struct device *dev) //drivers/base/core.c
put_device(dev)
kobject_put(&dev->kobj)
以spi为例, 分析下具体的实现:
//drivers/spi/spi.c
int devm_spi_register_master(struct device *dev, struct spi_master *master)
{
struct spi_master **ptr;
int ret;
//1). 分配资源结构体:
ptr = devres_alloc(devm_spi_unregister, sizeof(*ptr), GFP_KERNEL);
if (!ptr)
return -ENOMEM;
ret = spi_register_master(master);
if (!ret) {
//2). 设置要释放的资源:
*ptr = master;
//3). 添加进设备的资源链表:
devres_add(dev, ptr);
} else {
devres_free(ptr);
}
return ret;
}
//这样当spi注册失败或卸载时, 会自动调用下面的函数, 无需再手动调用:
static void devm_spi_unregister(struct device *dev, void *res)
{
spi_unregister_master(*(struct spi_master **)res);
}
作业: 注册一个设备, 为其分配资源; 当卸载设备时, 检查资源有没有被释放?
//cnt_test.c
/* dts里添加:
* ttra: ttrb@0 {
* compatible = "cnty_test,ont";
* };
*
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/device.h>
#include <linux/slab.h>
static struct device *cnty_dev;
//release函数:
static void cnty_release(struct device *dev, void *res)
{
printk("%s\n", __func__);
WARN_ON(1);
}
ssize_t cnty_test_show(struct device *dev, struct device_attribute *attr, char *buf)
{
return 0;
}
ssize_t cnty_test_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int *res;
if(*buf == 'a'){
device_register(cnty_dev);
} else if (*buf == 'b'){
res = devres_alloc(cnty_release, sizeof(res), GFP_KERNEL);
*res = 15;
devres_add(cnty_dev, res);
} else if (*buf == 'c'){
device_unregister(cnty_dev);
}
return count;
}
static DEVICE_ATTR(cnty_test, S_IRUGO|S_IWUSR, cnty_test_show, cnty_test_store);
static int cnty_test_probe(struct platform_device *pdev)
{
struct device *dev;
dev = &pdev->dev;
device_create_file(dev, &dev_attr_cnty_test);
cnty_dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev_set_name(cnty_dev, "%s", "tadf");
return 0;
}
static const struct of_device_id cnty_test_ids[] = {
{.compatible = "cnty_test,ont",},
{},
};
MODULE_DEVICE_TABLE(of, cnty_test_ids);
static struct platform_driver cnty_test_driver = {
.driver = {
.name = "cnty_test-zrt",
.of_match_table = of_match_ptr(cnty_test_ids),
},
.probe = cnty_test_probe,
};
module_platform_driver(cnty_test_driver);
MODULE_LICENSE("GPL");
验证:
/sys/devices/ttrb.22 # echo a > cnty_test
/sys/devices/ttrb.22 # echo b > cnty_test
/sys/devices/ttrb.22 # echo c > cnty_test
发现’echo c > cnty_test’后,有调用到release函数: cnty_release();