linux里的devm函数

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

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值