首頁  >  文章  >  系統教程  >  詳解Linux設備模型(6)_Bus

詳解Linux設備模型(6)_Bus

WBOY
WBOY轉載
2024-02-12 13:42:131015瀏覽

1. 概述

#在Linux設備模型中,Bus(匯流排)是一類特殊的設備,它是連接處理器和其他設備之間的通道(channel)。為了方便設備模型的實現,核心規定,系統中的每個設備都要連接在一個Bus上,這個Bus可以是一個內部Bus、虛擬Bus或是Platform Bus。

詳解Linux設備模型(6)_Bus

#核心透過結構體struct bus_type,對Bus進行抽象,它是在include/linux/device.h中定義的。本文將圍繞該結構體,描述Linux核心中Bus的功能,以及相關的實作邏輯。最後,我們會簡單介紹一些標準的Bus(如Platform),並介紹它們的用途和使用場景。

2. 功能說明

#依照慣例,我們在介紹功能之前先介紹一些核心資料結構。對於Bus模組而言,核心資料結構是結構體struct bus_type,此外還有一個與子系統相關的結構體,我們也將一併介紹。

2.1 struct bus_type
 1: /* inlcude/linux/device.h, line 93 */
 2: struct bus_type {
 3:     const char *name;
 4:     const char *dev_name;
 5:     struct device       *dev_root;
 6:     struct bus_attribute    *bus_attrs;
 7:     struct device_attribute *dev_attrs;
 8:     struct driver_attribute *drv_attrs;
 9:  
 10:    int (*match)(struct device *dev, struct device_driver *drv);
 11:    int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
 12:    int (*probe)(struct device *dev);
 13:    int (*remove)(struct device *dev);
 14:    void (*shutdown)(struct device *dev);
 15:  
 16:    int (*suspend)(struct device *dev, pm_message_t state);
 17:    int (*resume)(struct device *dev);
 18:  
 19:    const struct dev_pm_ops *pm;
 20:  
 21:    struct iommu_ops *iommu_ops;
 22:  
 23:    struct subsys_private *p;
 24:    struct lock_class_key lock_key;
 25: };

#name,該bus的名稱,會在sysfs中以目錄的形式存在,如platform bus在sysfs中表現為”/sys/bus/platform」。

dev_name,該名稱和」Linux裝置模型(5)_device和device driver」所講述的struct device結構中的init_name有關。對有些設備而言(例如批量化的USB設備),設計者根本就懶得為它起名字的,而內核也支持這種懶惰,允許將設備的名字留空。這樣當裝置註冊到核心後,裝置模型的核心邏輯就會用」bus->dev_name device ID」的形式,為這樣的裝置產生一個名稱。

bus_attrs、dev_attrs、drv_attrs,一些預設的attribute,可以在bus、device或device_driver新增到核心時,自動為它們新增對應的attribute。

dev_root,根據核心的註釋,dev_root裝置為bus的預設父裝置(Default device to use as the parent),但在核心實際實作中,只和一個叫sub system的功能有關,隨後會介紹。

match,一個由具體的bus driver實現的回呼函數。當任何屬於該Bus的device或device_driver添加到內核時,內核都會調用該接口,如果新加的device或device_driver匹配上了自己的另一半的話,該接口要返回非零值,此時Bus模組的核心邏輯就會執行後續的處理。

uevent,一個由具體的bus driver實作的回呼函數。當任何屬於該Bus的device,發生新增、移除或其它動作時,Bus模組的核心邏輯就會呼叫該接口,以便bus driver能夠修改環境變數。

probe、remove,這兩個回呼函數,和device_driver中的非常類似,但它們的存在是非常有意義的。可以想像一下,如果需要probe(其實就是初始化)指定的device話,需要保證該device所在的bus是被初始化過、確保能正確運作的。這就要就在執行device_driver的probe前,先執行它的bus的probe。 remove的過程則相反。
註1:並不是所有的bus都需要probe和remove介面的,因為對有些bus來說(例如platform bus),它本身就是一個虛擬的總線,無所謂初始化,直接就能使用,因此這些bus的driver就可以將這兩個回呼函數留空。

shutdown、suspend、resume,和probe、remove的原理類似,電源管理相關的實現,暫不說明。

pm,電源管理相關的邏輯,暫不說明。

iommu_ops,暫不說明。

p,一個struct subsys_private類型的指針,後面我們會用一個小節來說明。

#2.2 struct subsys_private

#該結構和device_driver中的struct driver_private類似,在」Linux裝置模型(5)_device和device driver」章節中有提到它,但沒有詳細說明。

要说明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的struct driver_private指標(priv)分配空間,並初始化其中的priv->klist_devices、priv->driver、priv->kobj.kset等變量,同時將該指針保存在device_driver的p處
  • # 將driver的kset(priv->kobj.kset)設定為bus的drivers kset(bus->p->drivers_kset),這意味著所有driver的kobject都位於bus->p->drivers_kset之下(寄/ sys/bus/xxx/drivers目錄下)
  • 以driver的名字為參數,呼叫kobject_init_and_add接口,在sysfs中註冊driver的kobject,體現在/sys/bus/xxx/drivers/目錄下,如/sys/bus/spi/drivers/spidev
  • 將該driver保存在bus的klist_drivers鍊錶中,並根據drivers_autoprobe的值,選擇是否呼叫driver_attach進行probe
  • 呼叫driver_create_file接口,在sysfs的該driver的目錄下,建立uevent attribute
  • 呼叫driver_add_attrs接口,在sysfs的該driver的目錄下,建立由bus->drv_attrs指標定義的預設attribute
  • 同時根據suppress_bind_attrs標誌,決定是否在sysfs的該driver的目錄下,建立bind和unbind attribute(具體可參考」Linux裝置模型(5)_device和device driver」中的介紹)

3.3 driver的probe

我們在」Linux裝置模型(5)_device和device driver」中,我們已經介紹過driver的probe時機及過程,其中大部分的邏輯會依賴bus模組的實現,主要為bus_probe_device和driver_attach介面。同樣,這兩個介面都是在drivers/base/base.h中聲明,在drivers/base/bus.c中實作。

這兩個結構的行為類似,邏輯也很簡單,既:搜尋所在的bus,比對是否有同名的device_driver(或device),如果有並且該設備沒有綁定Driver(註:這一點很重要,透過它,可以使同一個Driver,驅動相同名稱的多個設備,後續在Platform設備的描述中會提及)則調用device_driver的probe介面。

4. 雜項

#4.1 再談Subsystem

在舊的Linux核心版本中(以蝸蝸使用的linux2.6.23版本的核心為例),sysfs下所有的頂層目錄(包括一些二級目錄)都是以呼叫subsystem_register接口,以sub-system的形式註冊到內核的,如:

#/sys/bus/

/sys/devices/

/sys/devices/system/

/sys/block

/sys/kernel/

/sys/slab/

#那時的subsystem_register的實作很簡單,就是呼叫kset_register,建立一個kset。我們知道,kset就是一堆kobject的集合,並會在sysfs中以目錄的形式呈現出來。

在新版本的核心中(如「Linux核心分析」系列文章所參考的linux3.10.29),subsystem的實作有了很大變化,例如:去掉了subsystem_register介面(但為了相容/sys/device/ system子系統,在drivers/base/bus.c中,增加了一個subsys_register的內部接口,用於實現相應的功能)。根據這些變化,現在註冊subsystem有兩種方式:

方式一,在各自的初始化函數中,呼叫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,兩個回呼函數,subsys interface的核心功能。當bus下有設備增加或刪除的時候,bus core會呼叫它下面所有subsys interface的add_dev或remove_dev回呼。設計者可以在這兩個回呼函數中實現所需功能,例如綁定該「specific functionality」所對應的driver,等等。

#subsys interface的實作邏輯比較簡單,這裡不再詳細描述了,具體可參考「drivers/base/bus.c」中對應的程式碼。另外,後續分析cpufreq framework的時候,會遇到使用subsys interface的例子,到時候我們再進一步理解它的現實意義。

以上是詳解Linux設備模型(6)_Bus的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除