• 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    核心机制

    添加资源

    在看代码之前一个非常重要的问题:这种机制是怎么样将各种的资源挂载这个链表上,进行统一管理的?当我们把这个问题搞明白了,那么设备资源管理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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    先来看下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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    再深入看下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    到这里的时候只是分配了对象内存、并将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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在注释中有说明,小技巧也是在这里,利用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的这个函数很简答,就是将资源添加到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);
    
    ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    依次调用:

    /*
     * __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);
    ...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    接着往下看:

    /**
     * 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    最终会调用到下面的函数:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

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

    总结

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

    参考

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

    相关阅读

    devm简介

  • 相关阅读:
    海滩的海鸥
    5G定位系统,实现通信服务和定位功能一体化
    【嵌入式基础】内存(Cache,RAM,ROM,Flash)
    交换机与路由器技术-04-远程管理交换机
    常见问题解答:同时运行 ELEMENTOR FREE 和 ELEMENTOR PRO 插件
    前房角镜的
    关于HTML5表单验证的方法教程
    ppt太大怎么压缩变小?ppt压缩方法和步骤
    linux之perf(2)list事件
    Matlab PolySpace安装破解
  • 原文地址:https://blog.csdn.net/cqxcool66/article/details/126000455