在 Fuchsia 中,每个驱动程序都绑定到一个节点。节点是驱动程序框架的主要构建块。节点可以看作硬件或虚拟设备,节点也可以看作硬件设备的一部分。例如,GPIO 节点可以表示连接到 GPIO 控制器的单个 GPIO 引脚,而 RAM 磁盘节点可以表示虚拟磁盘,而不是真正的硬件设备。
驱动程序绑定到节点时,可以创建子节点。因此,节点会形成一个有向无环图,表示紫红色系统中的所有已知硬件和虚拟设备。
图 1. 一种节点拓扑,其中绿色圆圈表示表示设备的节点,蓝色方框表示驱动程序。
节点特性
节点具有以下属性:
- 节点属性:描述可绑定到节点的驱动程序的键值对。bind键可以是整数类型,也可以是字符串类型。值可以是整数、字符串、布尔值或枚举类型。
- 功能:驱动程序绑定到节点时提供的 FIDL 功能。这些功能可以是进程内功能,也可以是进程外功能。
- 符号:键值对,其中键为字符串,值为虚拟地址。驱动程序使用这些符号为子驱动程序添加指针,以进行进程内通信。只有当驱动程序与父级驱动程序位于同一驱动程序主机中时,系统才会将符号提供给该驱动程序,否则驱动程序会使用 FIDL 调用进行通信。
创建子节点的驱动程序需要为新节点分配节点属性、功能和符号。
节点功能
节点表示资源的集合,而资源又表示 Fuchsia 系统中的硬件或虚拟设备。当父驱动程序创建新节点时,父驱动程序会指定哪些功能与该节点相关联。当子驱动程序绑定到该节点时,驱动程序框架会将与该节点关联的功能路由到子驱动程序的传入命名空间。
但是,并非驱动程序的传入命名空间中的所有功能都来自父级驱动程序。某些功能可能来自系统中的非驱动程序组件。
节点拓扑
Fuchsia 的驱动程序管理器维护着一个节点拓扑,用于描述 Fuchsia 系统中节点(表示设备)之间的父子关系。从根节点开始,绑定到根节点的驱动程序会创建子节点,而一个节点可以拥有的子节点数量没有限制。绑定到这些子节点的驱动程序通常会创建自己的子节点。因此,这些节点形成单个节点拓扑,以有向无环图表示,该图描述了在 Fuchsia 系统中发现的所有硬件和虚拟设备。
图 2. USB 总线拓扑示例。
在上面的示例中,USB 总线驱动程序 (usb-bus-driver
) 绑定到表示 USB 总线的节点 (usb-bus
)。然后,驱动程序会为系统中发现的每个新 USB 设备创建一个子节点。根据节点的属性,每个 USB 设备节点都可以绑定到一个特定的 USB 驱动程序。例如,USB 键盘驱动程序 (usb-keyboard-driver
) 绑定到其中一个 USB 设备节点。由此,我们可以猜测,usb-device-2
节点很可能表示通过 USB 端口连接到系统的键盘设备。
与组件拓扑的比较
与驱动程序框架类似,组件框架有自己的拓扑,组件可以在其中声明子项。但是,节点拓扑和组件拓扑是分开的。第一个原因是,并非所有节点都会绑定驱动程序。这意味着这些未绑定的节点与组件拓扑中显示的任何特定组件无关。事实上,节点拓扑中的许多节点通常在系统运行时处于未绑定状态(即与驱动程序不匹配)。其次,在节点拓扑中,一个节点可以具有多个父节点(请参阅复合节点),这在组件拓扑中是不允许使用的。
从组件框架的角度来看,驱动程序的拓扑似乎扁平。驱动程序框架将驱动程序保留在三个平面组件集合中:启动集合、软件包集合和 Universe 软件包集合。在组件拓扑中,所有驱动程序组件在父组件(即驱动程序管理器)下看起来都是彼此的同级。
图 3:在三个集合中显示驱动程序组件的组件拓扑
不过,与组件拓扑中的其他组件不同,驱动程序框架负责设置驱动程序组件的名称。驱动程序框架会根据驱动程序在节点拓扑中的位置(而不是组件拓扑)来命名驱动程序组件的名称。例如,PCI 驱动程序的组件名称可能类似于 /bootstrap/boot-drivers:root.sys.platform.pci.00_14_0
。该组件名称表示 PCI 驱动程序的以下节点拓扑:root
-> sys
-> platform
-> pci
-> 00_14_0
。组件名称表明该 PCI 驱动程序组件在组件拓扑中只有 2 层,但绑定到 PCI 驱动程序的节点 (00_14_0
) 位于节点拓扑中下面的 5 层。
节点生命周期
驱动程序在 Fuchsia 系统中的生命周期与其所绑定的节点的生命周期相关联。
驱动程序可以对其控制的 Node
对象执行以下生命周期操作:
- 创建具有特定节点属性和功能的子节点。
- 删除节点,这会使驱动程序管理器清理节点拓扑中的节点及其后代。
节点创建
在现有节点上调用 AddChild
FIDL 方法时,系统会创建一个节点。拥有现有节点的驱动程序或有权访问节点对象的其他驱动程序都可以触发节点上的子节点创建。
驱动程序创建子节点后,可以执行以下操作:
- 为子节点提供决定哪些驱动程序可以绑定到该节点的属性。
- 为子节点提供稍后可供绑定到节点的驱动程序使用的capabilities。
- 保留子节点的
NodeController
对象,以便父驱动程序能够停止子节点。
驱动程序创建子节点时,驱动程序可以选择拥有该节点。为了拥有子节点,驱动程序会向 AddChild
调用添加一个额外的参数。如果驱动程序请求拥有子节点,则驱动程序框架不会将新驱动程序绑定到该节点。但是,如果子节点不归驱动程序所有,则驱动程序框架会尝试查找可以绑定到新节点的其他驱动程序。
驱动程序创建子节点时,也能够保留该子节点的 NodeController
对象。驱动程序可以随时使用此对象(通过调用 Remove
)停止子节点,从而停止绑定到该节点的驱动程序。当子节点绑定到驱动程序时,驱动程序会通过此对象接收 OnBind
FIDL 事件。
移除节点
发生以下事件之一时,系统会移除节点:
- 绑定到节点的驱动程序会丢弃其
Node
对象。 - 节点(例如父节点)会对目标节点的
NodeController
对象调用Remove
方法。 - 组件框架会关闭驱动程序组件,因此会移除绑定到该节点的驱动程序。
在节点拓扑中,首先从底部移除节点。如果某个节点已绑定到一个驱动程序,则必须先停止该驱动程序,然后才能移除该节点。如果要移除的节点位于节点拓扑的中间,驱动程序框架会确保在移除目标节点之前停止所有子驱动程序并移除所有子节点。
复合节点
当驱动程序希望将自身绑定到多个父节点时,驱动程序管理器会创建一个复合节点。在这种情况下,驱动程序管理器会将复合节点作为子节点添加到每个父节点中。然后,驱动程序管理器会将驱动程序绑定到复合节点。
复合节点具有复合绑定规则,使驱动程序能够为每个父节点指定一组不同的绑定规则。复合节点的功能会从每个父节点转发,使驱动程序能够访问父节点的组合功能。但是,绑定属性不会从父节点转发到复合节点。在创建复合节点的过程中,驱动程序框架会创建一个复合节点来响应绑定到多个父节点的驱动程序,因此它已经知道将绑定到该节点的驱动程序。
可以使用如下所示的复合节点创建镜头:
图 4. 复合节点的表示。
在上面的示例中,我们在 Fuchsia 系统中发现了一部相机设备。该摄像头设备有一个用于开启摄像头的 GPIO 引脚,以及一个将图片数据从摄像头传输出去的 PCI 设备。驱动程序索引将此摄像头设备与名为 camera-driver
的驱动程序匹配。此驱动程序具有复合绑定规则,指示它希望有两个父节点:一个用于 GPIO 设备,另一个用于 PCI 设备。驱动程序管理器会创建一个名为 camera
的复合节点,并将该节点作为子节点添加到 gpio-enable
节点和 pci-device
节点中。然后,父节点会将其功能转发给复合节点。最后,驱动程序管理器会将 camera-driver
驱动程序绑定到复合节点。
图 5. 一个真实示例,展示了相机控制器驱动程序的复杂绑定拓扑