ホームページ  >  記事  >  システムチュートリアル  >  Linuxデバイスモデルの詳細説明(6)_Bus

Linuxデバイスモデルの詳細説明(6)_Bus

WBOY
WBOY転載
2024-02-12 13:42:131015ブラウズ

######1。概要######

Linux デバイス モデルでは、バスは特別なタイプのデバイスであり、プロセッサと他のデバイスを接続するチャネルです。デバイス モデルの実装を容易にするために、カーネルはシステム内の各デバイスをバスに接続する必要があると規定します。このバスは内部バス、仮想バス、またはプラットフォーム バスのいずれかになります。

カーネルは、include/linux/device.h で定義されている構造体 structbus_type を通じてバスを抽象化します。この記事では、この構造に焦点を当て、Linux カーネルのバスの機能と関連する実装ロジックについて説明します。最後に、いくつかの標準的なバス (プラットフォームなど) を簡単に紹介し、その用途と使用シナリオを紹介します。

Linuxデバイスモデルの詳細説明(6)_Bus

2. 機能の説明

いつものように、関数を紹介する前に、いくつかのコアデータ構造を紹介します。 Bus モジュールの場合、コアとなるデータ構造は構造体 structbus_type ですが、サブシステムに関連する構造体もありますので、それについても紹介します。

2.1 構造体バスタイプ

リーリー
#「
name (バスの名前) は、sysfs 内のディレクトリの形式で存在します。たとえば、プラットフォーム バスは、sysfs では「/sys/bus/platform」として表示されます。

dev_name、この名前は、「Linux デバイス モデル (5)_デバイスとデバイス ドライバー」で説明されている構造体デバイス構造の init_name に関連しています。一部のデバイス (大容量 USB デバイスなど) では、設計者は単に名前を付けることを気にせず、カーネルもこの遅延をサポートし、デバイス名を空白のままにすることを許可します。このようにして、デバイスがカーネルに登録されると、デバイス モデルのコア ロジックは「bus->dev_name device ID」の形式を使用して、そのようなデバイスの名前を生成します。

bus_attrs、dev_attrs、drv_attrs、一部のデフォルト属性は、カーネルに追加されるときに、対応する属性をバス、デバイス、またはデバイスドライバーに自動的に追加できます。

dev_root、カーネルのコメントによると、dev_root デバイスはバスのデフォルトの親デバイス (親として使用するデフォルトのデバイス) ですが、カーネルの実際の実装では、これは sub と呼ばれる関数にのみ関連します。システムは後ほど紹介します。

match、特定のバス ドライバーによって実装されるコールバック関数。バスに属するデバイスまたはデバイス ドライバーがカーネルに追加されると、カーネルはこのインターフェイスを呼び出します。新しく追加されたデバイスまたはデバイス ドライバーがもう一方と一致する場合、インターフェイスはゼロ以外の値を返します。このとき、コアはBus モジュールのロジックは後続の処理を実行します。

uevent、特定のバス ドライバーによって実装されるコールバック関数。バスに属するデバイスが追加、削除、またはその他の方法で動作すると、バス モジュールのコア ロジックがこのインターフェイスを呼び出し、バス ドライバーが環境変数を変更できるようにします。

probe と Remove、これら 2 つのコールバック関数は device_driver の関数と非常によく似ていますが、これらの存在は非常に意味があります。プローブで指定されたデバイス (実際には初期化) が必要な場合は、デバイスが配置されているバスが初期化されていて、正しく動作できることを確認する必要があると想像できます。これには、device_driver のプローブを実行する前にバス プローブを実行する必要があります。削除のプロセスは逆になります。

注 1: 一部のバス (プラットフォーム バスなど) では、仮想バス自体が仮想バスであるため、すべてのバスでプローブ アンド リムーブ インターフェイスが必要なわけではありません。初期化する必要がなく、直接使用できます。したがって、これらのバスのドライバーどちらのコールバック関数も空のままにすることができます。

シャットダウン、サスペンド、およびレジュームには、プローブと削除と同様の原則がありますが、電源管理の実装についてはまだ説明されていません。

午後、電源管理に関するロジックはまだ説明しません。

iommu_ops、まだ説明されていません。

p、struct subsys_private 型のポインター。これについては後のセクションで説明します。

#2.2 構造体 subsys_private

この構造は、device_driver の struct driver_private と似ており、「Linux デバイス モデル (5)_デバイスとデバイス ドライバー」の章で言及されていますが、詳細な説明はありません。

要说明subsys_private的功能,让我们先看一下该结构的定义:

 1: /* drivers/base/base.h, line 28 */
 2: struct subsys_private {
 3:     struct kset subsys;
 4:     struct kset *devices_kset;
 5:     struct list_head interfaces;
 6:     struct mutex mutex;
 7:  
 8:     struct kset *drivers_kset;
 9:     struct klist klist_devices;
 10:    struct klist klist_drivers;
 11:    struct blocking_notifier_head bus_notifier;
 12:    unsigned int drivers_autoprobe:1;
 13:    struct bus_type *bus;
 14:  
 15:    struct kset glue_dirs;
 16:    struct class *class;
 17: };

看到结构内部的字段,就清晰多了,没事不要乱起名字嘛!什么subsys啊,看的晕晕的!不过还是试着先理解一下为什么起名为subsys吧:

按理说,这个结构就是集合了一些bus模块需要使用的私有数据,例如kset啦、klist啦等等,命名为bus_private会好点(就像device_driver模块一样)。不过为什么内核没这么做呢?看看include/linux/device.h中的struct class结构(我们会在下一篇文章中介绍class)就知道了,因为class结构中也包含了一个一模一样的struct subsys_private指针,看来class和bus很相似啊。

想到这里,就好理解了,无论是bus,还是class,还是我们会在后面看到的一些虚拟的子系统,它都构成了一个“子系统(sub-system)”,该子系统会包含形形色色的device或device_driver,就像一个独立的王国一样,存在于内核中。而这些子系统的表现形式,就是/sys/bus(或/sys/class,或其它)目录下面的子目录,每一个子目录,都是一个子系统(如/sys/bus/spi/)。

好了,我们回过头来看一下struct subsys_private中各个字段的解释:

subsys、devices_kset、drivers_kset是三个kset,由”Linux设备模型(2)_Kobject”中对kset的描述可知,kset是一个特殊的kobject,用来集合相似的kobject,它在sysfs中也会以目录的形式体现。其中subsys,代表了本bus(如/sys/bus/spi),它下面可以包含其它的kset或者其它的kobject;devices_kset和drivers_kset则是bus下面的两个kset(如/sys/bus/spi/devices和/sys/bus/spi/drivers),分别包括本bus下所有的device和device_driver。

interface是一个list head,用于保存该bus下所有的interface。有关interface的概念后面会详细介绍。

klist_devices和klist_drivers是两个链表,分别保存了本bus下所有的device和device_driver的指针,以方便查找。

drivers_autoprobe,用于控制该bus下的drivers或者device是否自动probe,”Linux设备模型(5)_device和device driver”中有提到。

bus和class指针,分别保存上层的bus或者class指针。

2.3 功能总结

根据上面的核心数据结构,可以总结出bus模块的功能包括:

  • bus的注册和注销
  • 本bus下有device或者device_driver注册到内核时的处理
  • 本bus下有device或者device_driver从内核注销时的处理
  • device_drivers的probe处理
  • 管理bus下的所有device和device_driver

3. 内部执行逻辑分析

3.1 bus的注册

bus的注册是由bus_register接口实现的,该接口的原型是在include/linux/device.h中声明的,并在drivers/base/bus.c中实现,其原型如下:

 1: /* include/linux/device.h, line 118 */
 2: extern int __must_check bus_register(struct bus_type *bus);

该功能的执行逻辑如下:

  • 为bus_type中struct subsys_private类型的指针分配空间,并更新priv->bus和bus->p两个指针为正确的值
  • 初始化priv->subsys.kobj的name、kset、ktype等字段,启动name就是该bus的name(它会体现在sysfs中),kset和ktype由bus模块实现,分别为bus_kset和bus_ktype
  • 调用kset_register将priv->subsys注册到内核中,该接口同时会向sysfs中添加对应的目录(如/sys/bus/spi)
  • 调用bus_create_file向bus目录下添加一个uevent attribute(如/sys/bus/spi/uevent)
  • 调用kset_create_and_add分别向内核添加devices和device_drivers kset,同时会体现在sysfs中
  • 初始化priv指针中的mutex、klist_devices和klist_drivers等变量
  • 调用add_probe_files接口,在bus下添加drivers_probe和drivers_autoprobe两个attribute(如/sys/bus/spi/drivers_probe和/sys/bus/spi/drivers_autoprobe),其中drivers_probe允许用户空间程序主动出发指定bus下的device_driver的probe动作,而drivers_autoprobe控制是否在device或device_driver添加到内核时,自动执行probe
  • 调用bus_add_attrs,添加由bus_attrs指针定义的bus的默认attribute,这些attributes最终会体现在/sys/bus/xxx目录下

3.2 device和device_driver的添加

我们有在”Linux设备模型(5)_device和device driver”中讲过,内核提供了device_register和driver_register两个接口,供各个driver模块使用。而这两个接口的核心逻辑,是通过bus模块的bus_add_device和bus_add_driver实现的,下面我们看看这两个接口的处理逻辑。

这两个接口都是在drivers/base/base.h中声明,在drivers/base/bus.c中实现,其原型为:

 1: /* drivers/base/base.h, line 106 */
 2: extern int bus_add_device(struct device *dev);
 3:  
 4: /* drivers/base/base.h, line 110 */ 
 5: extern int bus_add_driver(struct device_driver *drv);

bus_add_device的处理逻辑:

  • 调用内部的device_add_attrs接口,将由bus->dev_attrs指针定义的默认attribute添加到内核中,它们会体现在/sys/devices/xxx/xxx_device/目录中

  • 调用sysfs_create_link接口,将该device在sysfs中的目录,链接到该bus的devices目录下,例如:

    xxx# ls /sys/bus/spi/devices/spi1.0 -l
    lrwxrwxrwx root root 2014-04-11 10:46 spi1.0 -> ../../../devices/platform/s3c64xx-spi.1/spi_master/spi1/spi1.0
    其中/sys/devices/…/spi1.0,为该device在sysfs中真正的位置,而为了方便管理,内核在该设备所在的bus的xxx_bus/devices目录中,创建了一个符号链接

  • 调用sysfs_create_link接口,在该设备的sysfs目录中(如/sys/devices/platform/alarm/)中,创建一个指向该设备所在bus目录的链接,取名为subsystem,例如:

    xxx # ls /sys/devices/platform/alarm/subsystem -l
    lrwxrwxrwx root root 2014-04-11 10:28 subsystem -> ../../../bus/platform

  • 最后,毫无疑问,要把该设备指针保存在bus->priv->klist_devices中

bus_add_driver的处理逻辑:

  • ドライバーの構造体 driver_private ポインター (priv) にスペースを割り当て、priv->klist_devices、priv->driver、priv->kobj.kset およびその他の変数を初期化し、device_driver# の p にポインターを保存します。
  • ドライバーの kset (priv->kobj.kset) をバスのドライバー kset (bus->p->drivers_kset) に設定します。これは、すべてのドライバー kobject が、bus->p->drivers_kset (send/sys/bus/) の下にあることを意味します。 xxx/drivers ディレクトリ)
  • ドライバーの名前をパラメーターとして使用して、kobject_init_and_add インターフェイスを呼び出してドライバーの kobject を sysfs に登録します。これは /sys/bus/xxx/drivers/ ディレクトリ (/sys/bus/spi/drivers/spidev
  • など) に反映されます。
  • ドライバーをバスの klist_drivers リンク リストに保存し、drivers_autoprobe の値に従って、プローブのために driver_attach を呼び出すかどうかを選択します
  • driver_create_file インターフェイスを呼び出し、sysfs のドライバーのディレクトリに uevent 属性
  • # を作成します。
  • driver_add_attrs インターフェイスを呼び出し、sysfs
  • のドライバーのディレクトリに Bus->drv_attrs ポインターで定義されたデフォルト属性を作成します。
  • 同時に、suppress_bind_attrs フラグに従って、sysfs 内のドライバーのディレクトリにバインド属性とアンバインド属性を作成するかどうかを決定します (詳細は、「Linux デバイス モデル (5)_デバイスとデバイス ドライバー」の紹介を参照してください)
#''

3.3 ドライバー プローブ
「Linux デバイス モデル (5)_デバイスとデバイス ドライバー」では、ドライバーのプローブのタイミングとプロセスを紹介しました。ロジックのほとんどは、バス モジュールの実装、主に Bus_probe_device インターフェイスと driver_attach インターフェイスに依存します。同様に、これら 2 つのインターフェイスは drivers/base/base.h で宣言され、drivers/base/bus.c で実装されます。

これら 2 つの構造体の動作は似ており、ロジックは非常に単純です: バスが存在する場所を検索し、同じ名前の device_driver (または device) があるかどうかを比較します。デバイスはドライバーにバインドされていません (注: これは非常に重要です。重要なのは、同じドライバーを通じて、同じ名前 (プラットフォーム デバイスの説明で後述します) とプローブ インターフェイスを持つ複数のデバイスを駆動できることです。 device_driver のが呼び出されます。

4.その他

4.1 サブシステムについてもう一度話しましょう
古い Linux カーネル バージョン (Wowo で使用されているカーネルの linux2.6.23 バージョンを例に挙げます) では、sysfs の下のすべてのトップレベル ディレクトリ (一部のセカンダリ ディレクトリを含む) が subsystem_register インターフェイスを呼び出し、サブシステム Registered を使用します。
の形式でカーネルに送信します。

#「

/sys/バス/

/sys/デバイス/

/sys/デバイス/システム/

/sys/ブロック

/sys/カーネル/

/sys/スラブ/

#…

#当時の subsystem_register の実装は非常に単純で、kset_register を呼び出して kset を作成するだけでした。 kset は kobject のコレクションであり、sysfs 内のディレクトリの形式で表示されることがわかっています。 新しいバージョンのカーネル (「Linux カーネル分析」シリーズの記事で参照されている linux3.10.29 など) では、サブシステムの実装が大幅に変更されました。たとえば、subsystem_register インターフェイスが削除されました (ただし、 /sys/device/system サブシステムと互換性を持たせるために、drivers/base/bus.c に対応する機能を実装するために subsys_register の内部インターフェイスが追加されます)。これらの変更により、サブシステムを登録するには 2 つの方法が存在します:

方法 1: それぞれの初期化関数で、 kset_create_and_add インターフェイスを呼び出して、次のような対応するサブシステムを作成します。

  • bus子系统,/sys/bus/,buses_init(drivers/base/bus.c)
  • class子系统,/sys/class
  • kernel子系统,/sys/kernel
  • firmware子系统,/sys/firmware
  • 等等

其中bus子系统就是本文所讲的Bus模块,而其它的,我们会在后续的文章中陆续讲述。这个方式和旧版本内核使用kset_register接口的方式基本一样。

方式二,在bus模块中,利用subsys_register接口,封装出两个API:subsys_system_register和subsys_virtual_register,分别用于注册system设备(/sys/devices/system/)和virtual设备(/sys/devices/virtual/)。 而该方式和方式一的区别是:它不仅仅创建了sysfs中的目录,同时会注册同名的bus和device。

4.2 system/virtual/platform

在Linux内核中,有三种比较特殊的bus(或者是子系统),分别是system bus、virtual bus和platform bus。它们并不是一个实际存在的bus(像USB、I2C等),而是为了方便设备模型的抽象,而虚构的。

system bus是旧版内核提出的概念,用于抽象系统设备(如CPU、Timer等等)。而新版内核认为它是个坏点子,因为任何设备都应归属于一个普通的子系统(New subsystems should use plain subsystems, drivers/base/bus.c, line 1264),所以就把它抛弃了(不建议再使用,它的存在只为兼容旧有的实现)。

virtaul bus是一个比较新的bus,主要用来抽象那些虚拟设备,所谓的虚拟设备,是指不是真实的硬件设备,而是用软件模拟出来的设备,例如虚拟机中使用的虚拟的网络设备(有关该bus的描述,可参考该链接处的解释:https://lwn.net/Articles/326540/)。

platform bus就比较普通,它主要抽象集成在CPU(SOC)中的各种设备。这些设备直接和CPU连接,通过总线寻址和中断的方式,和CPU交互信息。

我们会在后续的文章中,进一步分析这些特殊的bus,这里就暂时不详细描述了。

4.3 subsys interface

subsys interface是一个很奇怪的东西,除非有一个例子,否则很难理解。代码中是这样注释的:

Interfaces usually represent a specific functionality of a subsystem/class of devices.

字面上理解,它抽象了bus下所有设备的一些特定功能。

kernel使用struct subsys_interface结构抽象subsys interface,并提供了subsys_interface_register/subsys_interface_unregister用于注册/注销subsys interface,bus下所有的interface都挂载在struct subsys_private变量的“interface”链表上(具体可参考2.2小节的描述)。struct subsys_interface的定义如下:

1. /**
2. \* struct subsys_interface - interfaces to device functions
3. \* @name: name of the device function
4. \* @subsys: subsytem of the devices to attach to
5. \* @node: the list of functions registered at the subsystem
6. \* @add_dev: device hookup to device function handler
7. \* @remove_dev: device hookup to device function handler
8. *
9. \* Simple interfaces attached to a subsystem. Multiple interfaces can
10. \* attach to a subsystem and its devices. Unlike drivers, they do not
11. \* exclusively claim or control devices. Interfaces usually represent
12. \* a specific functionality of a subsystem/class of devices.
13. */
14. struct subsys_interface {
15. const char *name;
16. struct bus_type *subsys;
17. struct list_head node;
18. int (*add_dev)(struct device *dev, struct subsys_interface *sif);
19. int (*remove_dev)(struct device *dev, struct subsys_interface *sif);
20. };

name,interface的名称。

subsys,interface所属的bus。

node,用于将interface挂到bus中。

add_dev/remove_dev、2 つのコールバック関数、subsys インターフェイスのコア関数。バスの下でデバイスが追加または削除されると、バス コアはその下のすべての subsys インターフェイスの add_dev または Remove_dev コールバックを呼び出します。設計者は、「特定の機能」に対応するドライバーのバインドなど、必要な機能をこれら 2 つのコールバック関数に実装できます。

#subsys インターフェイスの実装ロジックは比較的単純なので、ここでは詳しく説明しません。詳細については、「drivers/base/bus.c」内の対応するコードを参照してください。また、後で cpufreq フレームワークを分析する際に、subsys インターフェイスの使用例に遭遇し、その実践的な重要性をさらに理解することができます。

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

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