>시스템 튜토리얼 >리눅스 >리눅스 디바이스 모델(9)_디바이스 리소스 관리

리눅스 디바이스 모델(9)_디바이스 리소스 관리

PHPz
PHPz앞으로
2024-02-10 21:15:411242검색

1. 소개

모든 Linux 드라이버 엔지니어는 시간을 내어 이 문서를 읽어 보는 것이 좋습니다.

이 기사에서 소개된 주제는 매우 실용적이고 일부 혼란에 답할 수 있으며 코드를 간단하고 간결하게 만들 수도 있습니다. 먼저 예를 살펴보겠습니다.

으아아아

Linux 드라이버를 작성한 모든 엔지니어는 프로브 기능에서 비슷한 문제에 직면했다고 생각합니다. 여러 리소스(IRQ, 시계, 메모리, 영역, ioremap, DMA 등)를 특정 순서로 적용해야 합니다. 그 중 하나 리소스 적용이 실패하면 이전에 적용한 모든 리소스를 롤백하고 해제해야 합니다. 따라서 일반적으로 함수 끝에는 많은 goto 레이블(예:exit_free_irq,exit_free_dma 등)이 있으므로 리소스 적용이 실패할 경우 올바른 레이블로 조심스럽게 점프하여 적용된 리소스를 해제할 수 있습니다.

그러나 이러한 코드는 반복되는 코드의 큰 섹션으로 가득 차 있으며 "if (조건) { err = xxx; goto xxx; }" 논리를 많이 포함하고 있습니다. 이는 시간 낭비일 뿐만 아니라 오류가 발생하기 쉬울 뿐만 아니라 보기 흉한. 그러나 혼란스럽다면 개선의 여지가 있을 것입니다. 궁극적으로 Linux 장치 모델은 장치 리소스 관리를 통해 이 문제를 해결합니다. 드라이버는 리소스를 신청하기만 하면 되며 리소스를 해제하는 방법에 대해 걱정할 필요가 없습니다. 궁극적으로 다음을 통해 드라이버 코드를 단순화할 수 있습니다.

으아아아

어떻게 하나요? 위에서 "devm_"으로 시작하는 인터페이스에 주목하세요. 답이 거기에 있습니다. 기존 리소스 애플리케이션 인터페이스를 사용하지 말고 대신 devm_xxx 인터페이스를 사용하세요. 호환성을 유지하기 위해 이러한 새 인터페이스의 매개변수는 이름 앞에 "devm_"이 추가되고 추가적인 구조체 장치 포인터가 추가된다는 점을 제외하면 이전 인터페이스의 매개변수와 동일하게 유지됩니다.

2.devm_xxx

다음은 장치 리소스 관리를 기반으로 하는 다양한 프레임워크(예: 시계, 조정기, gpio 등)로 구현되는 일반적으로 사용되는 리소스 응용 프로그램 인터페이스 목록입니다. 사용할 때 "devm_" 접두사를 무시하면 나머지 부분은 드라이버 엔지니어에게 친숙할 것입니다. 한 가지만 기억하세요. 드라이버는 적용만 가능하고 릴리스할 수는 없으며 장치 모델이 드라이버를 릴리스하는 데 도움이 됩니다. 그러나 엄격하게 하고 싶다면 드라이버가 제거될 때 적극적으로 해제할 수 있습니다(여기에 나열되지 않은 해당 인터페이스도 있습니다).

으아아아

3. "장치 리소스"란 무엇입니까

장치가 작동하려면 전원 공급 장치, 시계 등 다양한 외부 조건에 따라 달라집니다. 이러한 외부 조건을 장치 리소스라고 합니다. 최신 컴퓨터 아키텍처의 경우 가능한 리소스는 다음과 같습니다.

a) 전원, 전원 공급 장치.
b) 시계, 시계.
c) 메모리, 메모리는 일반적으로 커널에서 kzalloc을 사용하여 할당됩니다.
d) GPIO, 사용자 및 CPU는 간단한 제어, 상태 및 기타 정보를 교환합니다.
e) IRQ는 인터럽트를 트리거합니다.
f) DMA, CPU 참여 없이 데이터 전송.
g) 가상 주소 공간은 일반적으로 ioremap, request_region 등을 사용하여 할당됩니다.
h) 잠깐

Linux 커널의 관점에서 "리소스"의 정의는 PWM, RTC, Reset과 같이 더 광범위하며, 이는 모두 드라이버에서 사용할 리소스로 추상화될 수 있습니다.

이전 커널에서는 시스템이 특별히 복잡하지 않았고 각 프레임워크가 아직 구체화되지 않았기 때문에 대부분의 리소스는 드라이버 자체에서 유지 관리되었습니다. 그러나 시스템의 복잡성이 증가함에 따라 드라이버 간에 점점 더 많은 리소스가 공유되고, 전력 관리의 필요성이 더욱 시급해집니다. 따라서 커널은 "장치 리소스 관리" 프레임워크를 기반으로 각 리소스의 관리 권한을 되찾아 할당 및 재활용을 포함하여 이를 균일하게 관리합니다.

4. 장치 리소스 관리를 위한 소프트웨어 프레임워크

Linux设备模型(9)_device resource management장치 리소스 관리는 "drivers/base/devres.c"에 있으며 구현이 매우 간단합니다. 이유는 무엇입니까? 자원의 종류와 표현의 형태가 다양하고, 개발자가 이를 모두 알 수 없기 때문에 구체적인 할당과 재활용을 수행할 수 없습니다. 따라서 개발자가 할 수 있는 작업(및 유일한 기능)은 다음과 같습니다.

提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在driver detach的时候,自动释放。

而更为具体的事情,如怎么抽象某一种设备,则由上层的framework负责。这些framework包括:regulator framework(管理power资源),clock framework(管理clock资源),interrupt framework(管理中断资源)、gpio framework(管理gpio资源),pwm framework(管理PWM),等等。

其它的driver,位于这些framework之上,使用它们提供的机制和接口,开发起来就非常方便了。

5. 代码分析

5.1 数据结构

先从struct device开始吧!该结构中有一个名称为“devres_head”的链表头,用于保存该设备申请的所有资源,如下:

   1: struct device {
   2:         ...
   3:         spinlock_t              devres_lock;
   4:         struct list_head        devres_head;
   5:         ...
   6: }
   7:  

那资源的数据结构呢?在“drivers/base/devres.c”中,名称为struct devres,如下:

   1: struct devres {
   2:         struct devres_node              node;
   3:         /* -- 3 pointers */
   4:         unsigned long long              data[]; /* guarantee ull alignment */
   5: };

咋一看非常简单,一个struct devres_node的变量node,一个零长度数组data,但其中有无穷奥妙,让我们继续分析。

node用于将devres组织起来,方便插入到device结构的devres_head链表中,因此一定也有一个list_head(见下面的entry)。另外,资源的存在形式到底是什么,device resource management并不知情,因此需要上层模块提供一个release的回调函数,用于release资源,如下:

   1: struct devres_node {
   2:         struct list_head                entry;
   3:         dr_release_t                    release;
   4: #ifdef CONFIG_DEBUG_DEVRES
   5:         const char                      *name;
   6:         size_t                          size;
   7: #endif
   8: };

抛开用于debug的变量不说,也很简单,一个entry list_head,一个release回调函数。看不出怎么抽象资源啊!别急,奥妙都在data这个零长度数组上面呢。

注1:不知道您是否注意到,devres有关的数据结构,是在devres.c中定义的(是C文件哦!)。换句话说,是对其它模块透明的。这真是优雅的设计(尽量屏蔽细节)!

5.2 一个无关话题:零长度数组

零长度数组的英文原名为Arrays of Length Zero,是GNU C的规范,主要用途是用来作为结构体的最后一个成员,然后用它来访问此结构体对象之后的一段内存(通常是动态分配的内存)。什么意思呢?

以struct devres为例,node变量的长度为3个指针的长度,而struct devres的长度也是3个指针的长度。而data只是一个标记,当有人分配了大于3个指针长度的空间并把它转换为struct devres类型的变量后,我们就可以通过data来访问多出来的memory。也就是说,有了零长度数组data,struct devres结构的长度可以不定,完全依赖于你分配的空间的大小。有什么用呢?

以本文的应用场景为例,多出来的、可通过data访问的空间,正是具体的device resource所占的空间。资源的类型不同,占用的空间的多少也不同,但devres模块的主要功能又是释放资源所占的资源。这是就是零长度数组的功能之一,因为整个memory空间是连续的,因此可以通过释devres指针,释放所有的空间,包括data所指的那片不定长度的、具体资源所用的空间。

零长度数组(data[0]),在不同的C版本中,有不同的实现方案,包括1长度数组(data[1])和不定长度数组(data[],本文所描述就是这一种),具体可参考GCC的规范:

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

5.3 向上层framework提供的接口:devres_alloc/devres_free、devres_add/devres_remove

先看一个使用device resource management的例子(IRQ模块):

   1: /* include/linux/interrupt.h */
   2: static inline int __must_check
   3: devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
   4:                  unsigned long irqflags, const char *devname, void *dev_id)
   5: {
   6:         return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
   7:                                          devname, dev_id);
   8: }
   9:  
  10:  
  11: /* kernel/irq/devres.c */
  12: int devm_request_threaded_irq(struct device *dev, unsigned int irq,
  13:                               irq_handler_t handler, irq_handler_t thread_fn,
  14:                               unsigned long irqflags, const char *devname,
  15:                               void *dev_id)
  16: {
  17:         struct irq_devres *dr;
  18:         int rc;
  19:  
  20:         dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
  21:                           GFP_KERNEL);
  22:         if (!dr)
  23:                 return -ENOMEM;
  24:  
  25:         rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
  26:                                   dev_id);
  27:         if (rc) {
  28:                 devres_free(dr);
  29:                 return rc;
  30:         }
  31:  
  32:         dr->irq = irq;
  33:         dr->dev_id = dev_id;
  34:         devres_add(dev, dr);
  35:  
  36:         return 0;
  37: }
  38: EXPORT_SYMBOL(devm_request_threaded_irq);
  39:  
  40: void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
  41: {
  42:         struct irq_devres match_data = { irq, dev_id };
  43:  
  44:         WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
  45:                                &match_data));
  46:         free_irq(irq, dev_id);
  47: }
  48: EXPORT_SYMBOL(devm_free_irq);

前面我们提过,上层的IRQ framework,会提供两个和request_irq/free_irq基本兼容的接口,这两个接口的实现非常简单,就是在原有的实现之上,封装一层devres的操作,如要包括:

1)一个自定义的数据结构(struct irq_devres),用于保存和resource有关的信息(对中断来说,就是IRQ num),如下:

   1: /*
   2:  * Device resource management aware IRQ request/free implementation.
   3:  */
   4: struct irq_devres {
   5:         unsigned int irq;
   6:         void *dev_id;
   7: };

2)一个用于release resource的回调函数(这里的release,和memory无关,例如free IRQ),如下:

   1: static void devm_irq_release(struct device *dev, void *res)
   2: {
   3:         struct irq_devres *this = res;
   4:  
   5:         free_irq(this->irq, this->dev_id);
   6: }

因为回调函数是由devres模块调用的,由它的参数可知,struct irq_devres变量就是实际的“资源”,但对devres而言,它并不知道该资源的实际形态,因而是void类型指针。也只有这样,devres模块才可以统一的处理所有类型的资源。

3)以回调函数、resource的size为参数,调用devres_alloc接口,为resource分配空间。该接口的定义如下:

   1: void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
   2: {
   3:         struct devres *dr;
   4:  
   5:         dr = alloc_dr(release, size, gfp);
   6:         if (unlikely(!dr))
   7:                 return NULL;
   8:         return dr->data;
   9: }

调用alloc_dr,分配一个struct devres类型的变量,并返回其中的data指针(5.2小节讲过了,data变量实际上是资源的代表)。alloc_dr的定义如下:

   1: static __always_inline struct devres * alloc_dr(dr_release_t release,
   2:                                                 size_t size, gfp_t gfp)
   3: {
   4:         size_t tot_size = sizeof(struct devres) + size;
   5:         struct devres *dr;
   6:  
   7:         dr = kmalloc_track_caller(tot_size, gfp);
   8:         if (unlikely(!dr))
   9:                 return NULL;
  10:  
  11:         memset(dr, 0, tot_size);
  12:         INIT_LIST_HEAD(&dr->node.entry);
  13:         dr->node.release = release;
  14:         return dr;
  15: }

看第一句就可以了,在资源size之前,加一个struct devres的size,就是total分配的空间。除去struct devres的,就是资源的(由data指针访问)。之后是初始化struct devres变量的node。

4)调用原来的中断注册接口(这里是request_threaded_irq),注册中断。该步骤和device resource management无关。

5)注册成功后,以设备指针(dev)和资源指针(dr)为参数,调用devres_add,将资源添加到设备的资源链表头(devres_head)中,该接口定义如下:

   1: void devres_add(struct device *dev, void *res)
   2: {
   3:         struct devres *dr = container_of(res, struct devres, data);
   4:         unsigned long flags;
   5:  
   6:         spin_lock_irqsave(&dev->devres_lock, flags);
   7:         add_dr(dev, &dr->node);
   8:         spin_unlock_irqrestore(&dev->devres_lock, flags);
   9: }

从资源指针中,取出完整的struct devres指针,调用add_dr接口。add_dr也很简单,把struct devres指针挂到设备的devres_head中即可:

   1: static void add_dr(struct device *dev, struct devres_node *node)
   2: {
   3:         devres_log(dev, node, "ADD");
   4:         BUG_ON(!list_empty(&node->entry));
   5:         list_add_tail(&node->entry, &dev->devres_head);
   6: }

6)如果失败,可以通过devres_free接口释放资源占用的空间,devm_free_irq接口中,会调用devres_destroy接口,将devres从devres_head中移除,并释放资源。这里就不详细描述了。

5.4 向设备模型提供的接口:devres_release_all

这里是重点,用于自动释放资源。

先回忆一下设备模型中probe的流程(可参考“Linux设备模型(5)_device和device driver”),devres_release_all接口被调用的时机有两个:

1)probe失败时,调用过程为(就不详细的贴代码了):

__driver_attach/__device_attach–>driver_probe_device—>really_probe,really_probe调用driver或者bus的probe接口,如果失败(返回值非零,可参考本文开头的例子),则会调用devres_release_all。

2)deriver dettach时(就是driver remove时)

driver_detach/bus_remove_device–>__device_release_driver–>devres_release_all

devres_release_all的实现如下:

   1: int devres_release_all(struct device *dev)
   2: {
   3:         unsigned long flags;
   4:  
   5:         /* Looks like an uninitialized device structure */
   6:         if (WARN_ON(dev->devres_head.next == NULL))
   7:                 return -ENODEV;
   8:         spin_lock_irqsave(&dev->devres_lock, flags);
   9:         return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
  10:                              flags);
  11: }

以设备指针为参数,直接调用release_nodes:

   1: static int release_nodes(struct device *dev, struct list_head *first,
   2:                          struct list_head *end, unsigned long flags)
   3:         __releases(&dev->devres_lock)
   4: {
   5:         LIST_HEAD(todo);
   6:         int cnt;
   7:         struct devres *dr, *tmp;
   8:  
   9:         cnt = remove_nodes(dev, first, end, &todo);
  10:  
  11:         spin_unlock_irqrestore(&dev->devres_lock, flags);
  12:  
  13:         /* Release.  Note that both devres and devres_group are
  14:          * handled as devres in the following loop.  This is safe.
  15:          */
  16:         list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
  17:                 devres_log(dev, &dr->node, "REL");
  18:                 dr->node.release(dev, dr->data);
  19:                 kfree(dr);
  20:         }
  21:  
  22:         return cnt;
  23: }

release_nodes会先调用remove_nodes,将设备所有的struct devres指针从设备的devres_head中移除。然后,调用所有资源的release回调函数(如5.3小节描述的devm_irq_release),回调函数会回收具体的资源(如free_irq)。最后,调用free,释放devres以及资源所占的空间。

위 내용은 리눅스 디바이스 모델(9)_디바이스 리소스 관리의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 lxlinux.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제