ホームページ  >  記事  >  システムチュートリアル  >  Linuxデバイスモデル(9)_デバイスリソース管理

Linuxデバイスモデル(9)_デバイスリソース管理

PHPz
PHPz転載
2024-02-10 21:15:411188ブラウズ

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_」プレフィックスを無視するだけで、残りの部分はドライバー エンジニアにとって馴染みのあるものになります。 1 つだけ覚えておいてください。ドライバーは適用することしかできませんが、解放することはできません。デバイス モデルがドライバーの解放に役立ちます。ただし、厳密にしたい場合は、ドライバーが削除されたときに積極的にリリースすることもできます (ここには記載されていない対応するインターフェイスもあります)。

リーリー

3.「デバイスリソース」とは

デバイスが動作するには、電源、クロックなどの多くの外部条件に依存します。これらの外部条件はデバイス リソースと呼ばれます。最新のコンピュータのアーキテクチャの場合、考えられるリソースは次のとおりです。

#「

a) 電源、電源。

b) 時計、時計。

c) メモリ。メモリは通常、カーネル内の kzalloc を使用して割り当てられます。
d) GPIO、ユーザー、CPU は簡単な制御、ステータス、その他の情報を交換します。
e) IRQ、割り込みをトリガーします。
f) DMA、CPU の関与なしのデータ送信。
g) 仮想アドレス空間は通常、ioremap、request_region などを使用して割り当てられます。
h) 待ってください

#Linux カーネルの観点から見ると、「リソース」の定義は PWM、RTC、リセットなどのより広範なものであり、これらはすべてドライバーが使用するリソースに抽象化できます。

以前のカーネルでは、システムは特に複雑ではなく、各フレームワークもまだ形になっていなかったため、ほとんどのリソースはドライバー自体によって維持されていました。しかし、システムの複雑さが増すにつれて、ドライバー間で共有されるリソースがますます多くなり、電源管理の必要性がますます高まっています。そこでカーネルは各リソースの管理権を取り戻し、「デバイスリソース管理」フレームワークに基づいて、各フレームワークが割り当てやリサイクルなどを一元的に管理します。

4. デバイスリソース管理のためのソフトウェアフレームワーク

デバイス リソース管理は「drivers/base/devres.c」にあり、その実装は非常に簡単です。リソースにはさまざまな種類や表現があり、それらすべてを把握することはできませんので、具体的な配分やリサイクルを行うことはできません。したがって、devres ができること (そしてその唯一の機能) は次のとおりです:

提供一种机制,将系统中某个设备的所有资源,以链表的形式,组织起来,以便在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以及资源所占的空间。

以上がLinuxデバイスモデル(9)_デバイスリソース管理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlxlinux.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。