devm设备资源管理分析

说明

本文将详细介绍devm的机制,包括怎么向设备添加各种资源,以及在设备卸载时,驱动是怎么自动释放相关资源的?

数据结构

drivers/base/devres.c:
struct devres_node {
	struct list_head		entry;		//用于链表操作
	dr_release_t			release;	//用于保存释放函数的函数指针
#ifdef CONFIG_DEBUG_DEVRES
	const char			*name;
	size_t				size;
#endif
};

struct devres {
	struct devres_node		node;	//见上面
	/* -- 3 pointers */
	/* 0长度数组,C99是下面的定义,不需要data[0] */
	unsigned long long		data[];	/* guarantee ull alignment */
};

struct device {
...
	/* 这个是跟设备资源管理相关的,设备资源都会添加到这个链表上 */
	struct list_head	devres_head;
}

核心机制

添加资源

在看代码之前一个非常重要的问题:这种机制是怎么样将各种的资源挂载这个链表上,进行统一管理的?当我们把这个问题搞明白了,那么设备资源管理devm相关的知识基本上也就明白了。
以devm_clk为例:

drivers/clk/clk-devres.c:
/* clk的释放函数
 * 两个参数分别为对应的设备dev和资源res,这两个参数对所有的资源都是一样的,可以从链表释放函数中看出来,因为调用了统一的函数指针来释放资源。
 */
static void devm_clk_release(struct device *dev, void *res)
{
/* clk自己的释放操作函数 */
	clk_put(*(struct clk **)res);
}

struct clk *devm_clk_get(struct device *dev, const char *id)
{
	struct clk **ptr, *clk;

	/* 1.调用devm中核心的分配函数,并注册devm_clk_release */
	ptr = devres_alloc(devm_clk_release, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	/* 2.clk自己的相关操作 */
	clk = clk_get(dev, id);
	if (!IS_ERR(clk)) {
		*ptr = clk;
	/* 3.devm中第二个核心操作,添加到设备资源管理中,这里的函数实际上有点小技巧 */
		devres_add(dev, ptr);
	} else {
		devres_free(ptr);
	}

	return clk;
}
EXPORT_SYMBOL(devm_clk_get);

先来看下devres_alloc_node这个函数:

/**
 * devres_alloc - Allocate device resource data
 * @release: Release function devres will be associated with
 * @size: Allocation size
 * @gfp: Allocation flags
 * @nid: NUMA node
 *
 * Allocate devres of @size bytes.  The allocated area is zeroed, then
 * associated with @release.  The returned pointer can be passed to
 * other devres_*() functions.
 *
 * RETURNS:
 * Pointer to allocated devres on success, NULL on failure.
 */
void * devres_alloc_node(dr_release_t release, size_t size, gfp_t gfp, int nid)
{
	struct devres *dr;

	dr = alloc_dr(release, size, gfp | __GFP_ZERO, nid);
	if (unlikely(!dr))
		return NULL;
	/* 注意这里的返回值,代表的是各种资源自己的内容 */
	return dr->data;
}
EXPORT_SYMBOL_GPL(devres_alloc_node);

再深入看下:

static __always_inline struct devres * alloc_dr(dr_release_t release,
						size_t size, gfp_t gfp, int nid)
{
	/* 计算分配内存总长度,包括devres结构体的长度 */
	size_t tot_size = sizeof(struct devres) + size;
	struct devres *dr;

	dr = kmalloc_node_track_caller(tot_size, gfp, nid);
	if (unlikely(!dr))
		return NULL;

	memset(dr, 0, offsetof(struct devres, data));

	/* 初始化链表 */
	INIT_LIST_HEAD(&dr->node.entry);
	/* 关联释放函数 */
	dr->node.release = release;
	return dr;
}

到这里的时候只是分配了对象内存、并将release填充到了dr的node.release中。但是还没有和设备关联起来?
在devm_clk_get()中的第二步,是和资源相关的操作,不用太关心。第三步就能回答我们上面的疑问。

/**
 * devres_add - Register device resource
 * @dev: Device to add resource to
 * @res: Resource to register
 *
 * Register devres @res to @dev.  @res should have been allocated
 * using devres_alloc().  On driver detach, the associated release
 * function will be invoked and devres will be freed automatically.
 */
void devres_add(struct device *dev, void *res)
{
	/* 注意下这里,通过资源res来得到devm的通用的核心结构体 */
	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);
}
EXPORT_SYMBOL_GPL(devres_add);

在注释中有说明,小技巧也是在这里,利用container_of()函数来将不同的资源数据得到devm通用的核心结构体数据。此外还涉及到一个零字节数组,devm就是利用它来实现对不同的资源进行统一的管理,即提供一套通用的devm的机制。

static void add_dr(struct device *dev, struct devres_node *node)
{
	devres_log(dev, node, "ADD");
	BUG_ON(!list_empty(&node->entry));
	/* 将node添加到dev的资源链表上 */
	list_add_tail(&node->entry, &dev->devres_head);
}

上面的这个函数很简单,就是将资源添加到dev的资源管理链表上。
到此,设备不同资源添加到设备上过程就清晰了,devm最重要的作用是在设备卸载时能自动释放所有的资源,添加资源和释放资源是一个整体,而释放资源又是怎么操作的呢?

释放资源

在设备注销阶段会调用到device_release_driver()和driver_detach()函数(具体是怎么调用到的,请读者自己去分析下),而这两个函数会调用下面的函数:

drivers/base/dd.c
/* 释放dev资源的核心函数 */
void device_release_driver_internal(struct device *dev,
				    struct device_driver *drv,
				    struct device *parent)
{
...
	if (!drv || drv == dev->driver)
		__device_release_driver(dev, parent);

...
}

依次调用:

/*
 * __device_release_driver() must be called with @dev lock held.
 * When called for a USB interface, @dev->parent lock must be held as well.
 */
static void __device_release_driver(struct device *dev, struct device *parent)
{
	struct device_driver *drv;

	drv = dev->driver;
	if (drv) {
...
		/* 释放所有的资源 */
		devres_release_all(dev);
...
	}
}

接着往下看:

/**
 * devres_release_all - Release all managed resources
 * @dev: Device to release resources for
 *
 * Release all resources associated with @dev.  This function is
 * called on driver detach.
 */
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");
		/* 调用注册的释放函数去释放资源 */
		dr->node.release(dev, dr->data);
		kfree(dr);
	}

	return cnt;
}

到此,释放设备资源的过程也清晰了。

总结

这里只是以clk为例来分析,读者朋友可以自己去看下其它的函数,它们的原理基本上是一样的,比如添加资源:第一步调用devres_alloc()来分配对象,第二步是资源自己的相关操作,第三步调用devres_add()函数将资源添加到设备的资源链表上。释放资源时,遍历设备资源管理链表,然后调用资源注册的释放函数。总的来说,这种机制设计的还是挺好的,哈哈。。。

参考

需要知道的其它知识:零长度数组
参考:零长度数组

相关阅读

devm简介

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值