什么是协议?
协议是一种严格的接口定义。
以太网驱动程序发布了符合 ZX_PROTOCOL_ETHERNET_IMPL 的接口。这意味着,它必须提供在数据结构(在本例中为 ethernet_impl_protocol_ops_t)中定义的一组函数。
这些函数对于实现该协议的所有设备都是通用的,例如,所有以太网设备都必须提供一个查询接口 MAC 地址的函数。
当然,其他协议对它们必须提供的功能有不同的要求。例如,块设备提供 fuchsia.hardware.block.volume.Service FIDL 服务,其 volume 协议 (fuchsia.storage.block.Block) 包含用于返回设备大小(以块为单位)和打开 I/O 会话的方法。
在许多情况下,协议用于通过利用接口的通用实现来简化驱动程序。例如,“以太网”驱动程序实现了以太网接口,并绑定到实现 Ethermac 协议的设备。某些协议利用共享内存和非 RPC 信令来实现比其他方式更高效、延迟更低和吞吐量更高的效果。
类表示设备实现接口或协议的承诺。
设备位于设备文件系统中的拓扑路径下,例如 /sys/platform/pci/00:02:00/e1000。如果它们是特定类,则还会以别名的形式显示在 /dev/class/CLASSNAME/... 下。e1000 驱动程序实现了 Ethermac 接口,因此它也会显示在 /dev/class/ethermac/000 中。类目录中的名称是唯一的,但没有意义,并且是按需分配的。
协议示例:
- PCI 根协议 (
ZX_PROTOCOL_PCIROOT), - PCI 设备协议 (
ZX_PROTOCOL_PCI),以及 - 以太网实现协议 (
ZX_PROTOCOL_ETHERNET_IMPL)。
方括号中的名称是与协议对应的 C 语言常量,供您参考。
依赖于平台的代码与独立于平台的代码
上文中提到,ZX_PROTOCOL_ETHERNET_IMPL“接近”客户端使用的函数,但相差一步。这是因为在客户端和驱动程序之间还有另一个协议 ZX_PROTOCOL_ETHERNET。此附加协议用于处理所有以太网驱动程序共有的功能(以避免代码重复)。此类功能包括缓冲区管理、状态报告和管理功能。
这实际上是一种“平台相关”与“平台无关”的解耦;通用代码存在于平台无关部分(一次),而驱动程序特定代码则在平台相关部分中实现。
此架构在多个位置重复使用,例如显示控制器、I2C 总线和串行驱动程序。
流程 / 协议映射
为了使上述讨论保持简单,我们没有讨论与驱动程序相关的进程分离。 为了了解这些问题,我们来看看其他操作系统如何处理这些问题,并将其与 Fuchsia 的方法进行比较。
在 Linux 等单内核中,许多驱动程序都在内核内实现。 这意味着它们共享相同的地址空间,实际上位于同一“进程”中。
这种方法的主要问题是故障隔离 / 利用。不良驱动程序可能会导致整个内核崩溃,因为它位于同一地址空间中,因此具有对所有内核内存和资源的特权访问权限。出于同样的原因,被破解的驱动程序也可能会带来安全威胁。
另一种极端情况是将每个驱动程序服务都放入自己的进程中,一些微内核操作系统会采用这种做法。其主要缺点是,如果一个驱动程序依赖于另一个驱动程序的服务,内核必须在两个驱动程序进程之间至少执行一次上下文切换操作(如果不是数据传输)。 虽然微内核操作系统通常旨在快速执行此类操作,但以高频率执行这些操作是不理想的。
Fuchsia 采用的方法基于驱动程序宿主的概念。驱动程序宿主是包含协议栈(即协同工作的一个或多个协议)的进程。驱动程序宿主从 ELF 共享库(称为动态共享对象 [DSO])加载驱动程序。DSO
该协议栈可有效地为设备创建完整的“驱动程序”,该驱动程序由平台相关组件和平台无关组件组成,位于独立的进程容器中。
对于高级读者,请查看 Fuchsia 命令行中提供的 driver dump 命令。它会显示设备树,并向您显示进程 ID、DSO 名称和其他实用信息。
以下是经过大幅修改的版本,仅显示了 PCI 以太网驱动程序部分:
1. [root]
2. [sys]
3. <sys> pid=1416 /boot/driver/bus-acpi.so
4. [acpi] pid=1416 /boot/driver/bus-acpi.so
5. [pci] pid=1416 /boot/driver/bus-acpi.so
...
6. [00:02:00] pid=1416 /boot/driver/bus-pci.so
7. <00:02:00> pid=2052 /boot/driver/bus-pci.proxy.so
8. [e1000] pid=2052 /boot/driver/e1000.so
9. [ethernet] pid=2052 /boot/driver/ethernet.so
从上面的信息可以看出,进程 ID 1416(第 3 行到第 6 行)是由 DSO bus-acpi.so 实现的高级配置与电源接口 (ACPI) 驱动程序。
在主枚举期间,ACPI DSO 检测到 PCI 总线。这导致发布了具有 ZX_PROTOCOL_PCI_ROOT 的父级(第 5 行,导致出现 [pci] 条目),然后导致驱动程序宿主加载 bus-pci.so DSO 并绑定到该 DSO。该 DSO 就是我们在上述讨论中一直提到的“基本 PCI 驱动程序”。
在绑定期间,基本 PCI 驱动程序枚举了 PCI 总线,并找到了一张以太网卡(第 6 行检测到总线 0、设备 2、功能 0,显示为 [00:02:00])。(当然,还找到了许多其他设备,但为了简单起见,我们已将其从上面的列表中移除)。
然后,对该设备的检测导致基本 PCI 驱动程序发布了一个新的父级,其中包含 ZX_PROTOCOL_PCI 以及设备的 VID 和 DID。此外,还创建了一个新的驱动程序宿主(进程 ID 2052),并加载了 bus-pci.proxy.so DSO(第 7 行)。此代理充当从新驱动程序宿主(PID 2052)到基本 PCI 驱动程序(PID 1416)的接口。
在此处,我们决定将设备驱动程序“分离”到其自己的进程中 - 新的驱动程序宿主和基本 PCI 驱动程序现在位于两个不同的进程中。
新的驱动程序宿主 2052 随后会找到匹配的子级(第 8 行的 e1000.so DSO;之所以认为它匹配,是因为它具有 ZX_PROTOCOL_PCI 以及正确的 VID 和 DID)。
该 DSO 发布了一个 ZX_PROTOCOL_ETHERNET_IMPL,该会绑定到匹配的
子级(第 9 行的 ethernet.so DSO;由于它具有
ZX_PROTOCOL_ETHERNET_IMPL 协议,因此被视为匹配)。
此链未显示的是,最终 DSO (ethernet.so) 会发布 ZX_PROTOCOL_ETHERNET - 这是客户端可以使用的部分,因此当然不会涉及进一步的“设备”绑定。
驱动程序框架版本 2 (DFv2)
如果启用了驱动程序框架版本 2,driver dump 将显示略有不同的树。
$ driver dump
[root] pid=4766 fuchsia-boot:///#meta/platform-bus.cm
[sys] pid=4766
[platform] pid=4766
[pt] pid=4766 fuchsia-boot:///#meta/platform-bus-x86.cm
[acpi] pid=4766
[acpi-pwrbtn] pid=4766 fuchsia-boot:///#meta/hid.cm
...
[PCI0] pid=4766 fuchsia-boot:///#meta/bus-pci.cm
[bus] pid=4766
...
[00_04_0] pid=4766 fuchsia-boot:///#meta/virtio_ethernet.cm
[virtio-net] pid=4766 fuchsia-boot:///#meta/netdevice-migration.cm
[netdevice-migration] pid=4766 fuchsia-boot:///#meta/network-device.cm
[network-device] pid=4766
...
需要重点指出的是,节点(在 DFv2 中,设备称为节点)没有关联的 .so 文件。而是附加到给定节点的驱动程序的组件清单的网址。