天天看点

Zircon设备模型

#Zircon设备模型

## 介绍

在Zircon中,设备驱动程序实现为ELF共享库(DSO),加载到设备主机(devhost)进程中。设备管理器(devmgr)进程,包含设备协调器(Coordinator),用于跟踪驱动程序和设备,进行管理发现驱动程序,设备主机进程的创建和方向,以及维护设备文件系统(devfs),这是用户空间的服务和应用程序(受其命名空间约束)获得设备访问权限的机制。

设备协调器将设备视为单个统一树的一部分。该树的分支(和子分支)由设备主机进程中的部分设备组成。关于如何细分的决定,设备主机中的整体树基于用于隔离的系统策略驱动程序出于安全性或稳定性原因以及为了性能而共同驱动的原因。

注意:当前策略很简单(每个设备代表一个物理总线主站有能力的硬件设备及其子设备被放入一个单独的devhost)。它将发展为提供更细粒度的分区。

##设备,驱动程序和设备主机

这是设备树的转储(为了清晰起见略微修剪)在Qemu x86-64上运行的Zircon:

```

$ dm dump

[root]

   <root> pid=1509

      [null] pid=1509 /boot/driver/builtin.so

      [zero] pid=1509 /boot/driver/builtin.so

   [misc]

      <misc> pid=1645

         [console] pid=1645 /boot/driver/console.so

         [dmctl] pid=1645 /boot/driver/dmctl.so

         [ptmx] pid=1645 /boot/driver/pty.so

         [i8042-keyboard] pid=1645 /boot/driver/pc-ps2.so

            [hid-device-001] pid=1645 /boot/driver/hid.so

         [i8042-mouse] pid=1645 /boot/driver/pc-ps2.so

            [hid-device-002] pid=1645 /boot/driver/hid.so

   [sys]

      <sys> pid=1416 /boot/driver/bus-acpi.so

         [acpi] pid=1416 /boot/driver/bus-acpi.so

         [pci] pid=1416 /boot/driver/bus-acpi.so

            [00:00:00] pid=1416 /boot/driver/bus-pci.so

            [00:01:00] pid=1416 /boot/driver/bus-pci.so

               <00:01:00> pid=2015 /boot/driver/bus-pci.proxy.so

                  [bochs_vbe] pid=2015 /boot/driver/bochs-vbe.so

                     [framebuffer] pid=2015 /boot/driver/framebuffer.so

            [00:02:00] pid=1416 /boot/driver/bus-pci.so

               <00:02:00> pid=2052 /boot/driver/bus-pci.proxy.so

                  [intel-ethernet] pid=2052 /boot/driver/intel-ethernet.so

                     [ethernet] pid=2052 /boot/driver/ethernet.so

            [00:1f:00] pid=1416 /boot/driver/bus-pci.so

            [00:1f:02] pid=1416 /boot/driver/bus-pci.so

               <00:1f:02> pid=2156 /boot/driver/bus-pci.proxy.so

                  [ahci] pid=2156 /boot/driver/ahci.so

            [00:1f:03] pid=1416 /boot/driver/bus-pci.so

```

方括号中的名称是设备。尖括号中的名称是代理设备,它在“lower”devhost中实例化,当正在提供进程隔离。 pid =字段表示进程对象设备包含在其中的devhost进程的id。路径表明哪个驱动程序实现该设备。

上面,例如,pid 1416 devhost包含pci总线驱动程序,它具有为系统中的每个PCI设备创建设备。 PCI设备00:02:00发生成为一个intel以太网接口,我们有一个驱动程序(intel-ethernet.so)。创建一个新的devhost(pid 2052),使用PCI 00:02:00的代理设备进行设置,并且intel以太网驱动程序已加载并绑定到它。

代理设备在设备文件系统中是不可见的,因此这个以太网设备显示为`/dev/sys/pci/00:02:00/intel-ethernet`。

##协议,接口和类

设备可以实现协议,这些协议是子设备使用的C ABIs以特定于设备的方式与父设备交互。举例:

[PCI协议](../../system/ulib/ddk/include/ddk/protocol/pci.h),

[USB协议](../../system/ulib/ddk/include/ddk/protocol/usb.h),

[块核心协议](../../system/ulib/ddk/include/ddk/protocol/block.h),以及

[Ethermac协议](../../system/ulib/ddk/include/ddk/protocol/ethernet.h),

协议通常是进程间的交互设备在同一个devhost中,但在驱动程序隔离的情况下,它们可能会占用通过RPC放置到“higher”的devhost(通过代理)。

设备可以实现接口,即[FIDL](../../../docs/development/languages/fidl/README.md)RPC协议客户(服务,应用程序等)使用。基础设备接口支持POSIX样式打开/关闭/读/写IO。通过支持接口基本设备接口中的`message()`操作。由于遗留原因,一个`ioctl()`操作当前存在,但它已被弃用并且在被删除的过程。

在许多情况下,协议通过利用接口的通用实现来使驱动更简单。例如,“block”驱动程序实现公共块接口,并绑定到实现块核心协议的设备,“以太网”驱动程序对以太网接口和Ethermac做同样的事情协议。一些协议,例如这里引用的两个协议,使用共享内存,和非rpc信令比更高效,更低的延迟和更高的吞吐量否则就会实现。

类表示设备实现接口或协议的承诺。设备存在于拓扑路径下的设备文件系统中,如

`/sys/pci/00:02:00/intel-ethernet`。如果他们是特定的类,他们也会出现作为`/dev/class/CLASSNAME/...`下的别名。 `intel-ethernet`驱动程序实现Ethermac接口,所以它也显示在`/dev/class/ethermac/000`。名字类目录中的内容是唯一的但没有意义,并且是按需分配的。

注意:目前类目录中的名称是3位十进制数,但它们可能会在未来改变形式。客户不应该假设有任何类别名的具体含义。

##设备驱动程序生命周期

设备驱动程序在需要时会加载到devhost进程中。决定它们是否被加载的是绑定程序,它是驱动程序可以绑定到哪个设备的描述。绑定程序是使用[`ddk/binding.h`]中的宏定义(../../system/ulib/ddk/include/ddk/binding.h)

英特尔以太网驱动程序的示例绑定程序:

```

ZIRCON_DRIVER_BEGIN(intel_ethernet,intel_ethernet_driver_ops,“Zircon”,“0.1”,9)

    BI_ABORT_IF(NE,BIND_PROTOCOL,ZX_PROTOCOL_PCI),

    BI_ABORT_IF(NE,BIND_PCI_VID,0x8086),

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x100E),// Qemu

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15A3),// Broadwell

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x1570),// Skylake

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x1533),// I210 standalone

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15b7),// Skull Canyon NUC

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15b8),// I219

    BI_MATCH_IF(EQ,BIND_PCI_DID,0x15d8),// Kaby Lake NUC

ZIRCON_DRIVER_END(intel_ethernet)

```

ZIRCON_DRIVER_BEGIN和_END宏包含必要的编译器指令将绑定程序放入ELF NOTE部分,允许对其进行检查通过设备协调器,无需将驱动程序完全加载到其进程中。

_BEGIN宏的第二个参数是`zx_driver_ops_t`结构指针(已定义)通过`[ddk/driver.h](../../system/ulib/ddk/include/ddk/driver.h)`来定义init,bind,create和release方法。

当驱动程序加载到设备主机进程并允许任何全局初始化时,调用`init()`。通常什么都不需要。如果`init()`方法实现了但失败,驱动程序加载将失败。

调用`bind()`以向驱动程序提供要绑定的设备。该设备已匹配驱动程序发布的绑定程序。如果`bind()`方法成功,驱动程序**必须**创建一个新设备并将其添加到通过`bind()`方法传入设备的子设备。有关更多信息,请参阅设备生命周期。

为平台/系统总线驱动程序或代理驱动程序调用`create()`。为了绝大多数驱动,这个方法不是必需的。

在驱动程序释放之前,在`bind()`或其他地方创建的所有设备被销毁之后,`release()`被调用。目前这种方法是**从不**调用。驱动程序一旦加载,将在设备主机进程的生命周期内保持加载状态。

##设备生命周期

在设备主机进程中,设备作为`zx_device_t`结构树存在,这对驱动来说是不透明的。这些是使用`device_add()`创建的驱动程序提供`zx_protocol_device_t`结构。由此结构中的函数指针定义的方法是“[device ops](device-ops.md)”。该各种结构和函数在[`device.h`]中定义(../../system/ulib/ddk/include/ddk/device.h)

`device_add()`函数创建一个新设备,将其作为子设备添加到提供的父设备。那个父设备**必须**是通过到设备驱动程序的`bind()`方法传递的设备,或者已经有的由同一设备驱动程序创建的另一个设备。

`device_add()`的副作用是添加新创建的设备到设备协调器维护的全局设备文件系统。如果使用** DEVICE_ADD_INVISIBLE **标志创建设备,它将无法访问通过在devfs中打开它的节点,直到调用`device_make_visible()`。这个对于必须进行扩展初始化或探测的驱动程序非常有用,不想明显地发布他们的设备,直到成功(并且如果失败则安静地删除它们)。

设备引用会计数。当驱动程序使用`device_add()`创建一个时,然后它在该设备上保存一个引用,直到它最终调用`device_remove()`。如果设备由远程进程通过设备文件系统打开,则会在那里获得一个引用。删除设备的父项时,其“unbind()”方法被调用。这向驱动发出信号,告知它应该开始关闭设备并通过调用`device_remove()`删除它创建的所有子设备。

由于`unbind()`方法调用时,子设备可能有工作正在处理,有可能刚在child身上调用`device_remove()`的父设备可以继续接收那个child的设备方法调用或协议方法调用。建议在除去child之前,父设备应该安排这些方法返回错误,这样在child移除完成之前从child过来的调用不要再开始工作或引起意外的互动。从没有** DEVICE_ADD_INVISIBLE **标志的`device_add()`调用或者在不可见的设备上的`device_make_visible()调用起,其他设备ops可以由设备主机调用。

只有在创建驱动程序设备上调用`device_remove()`后,才会调用`release()`方法,该设备的所有打开实例都已关闭,该设备的所有child都已被移除并释放。这个是驱动程序销毁或释放设备关联的任何资源的最后机会。在`release()`返回之后引用该设备的`zx_device_t`是无效的。在此之后,调用任何设备方法或从父设备获得协议方法是非法的并且将是可能导致崩溃。

###一个例子(Tear-Down Sequence)

解释`unbind()`和`release()`在teardown过程中如何工作,下面是USB WLAN驱动程序通常如何处理它的示例。简而言之,`unbind()`调用序列是自上而下的,而`release()`序列是自下而上的。

请注意,这只是一个例子。这可能与真正的WLAN驱动程序完全不匹配。

假设WLAN设备作为USB设备插入,并且PHY接口已经在USB设备下创建。除PHY接口外,还有2个MAC接口已在PHY接口下创建。

```

            + ------------ +

            |  USB Device  |

            + ------------ +

                  |

            + ------------ +

            | WLAN PHY     | .unbind()

            + ------------ + .release()

              |          |

    + ------------ + + ------------ +

    | WLAN MAC 0 | | WLAN MAC 1     | .unbind()

    + ------------ + + ------------ + .release()

```

现在,我们拔掉这个USB WLAN设备。

* USB XHCI检测到,并调用`device_remove(usb_device)`删除。

*由于移除了父设备,因此调用WLAN PHY的`unbind()`。  在这个`unbind()`中,它会删除它通过`device_add()`创建的接口:

```

    wlan_phy_unbind(void * ctx){

        //停止中断或任何阻止传入请求的内容。

        ...

        device_remove(wlan_phy);

    }

```

*当删除wlan_phy时,将在其所有子节点(wlan_mac_0,wlan_mac_1)上调用unbind()。

```

    wlan_mac_unbind(void * ctx){

        //停止接受新请求,并通知客户端此设备处于脱机状态(通常只是

        //将ZX_ERR_IO_NOT_PRESENT返回给unbind之后发生的任何请求)。

        ...

        device_remove(iface_mac_X);

    }

```

*一旦设备的所有客户端都被删除,并且该设备没有子设备,它的引用计数将达到零,并将调用其release()方法。

*调用WLAN MAC 0和1的`release()`。

```

    wlan_mac_release(void * ctx){

        //释放在创建时分配的源。

        ...

        //在这里删除对象。

        ...

    }

```

* wlan_phy没有打开的连接,但仍然有子设备(wlan_mac_0和wlan_mac_1)。

  一旦他们都被'release()`',它的引用计数最终达到零并且它的release()方法被调用。

```

    wlan_phy_release(void * ctx){

        //释放在创建时分配的源。

        ...

        //在这里删除对象。

        ...

    }

```

继续阅读