Zircon设备模型

 

Zircon内核中,设备驱动程序以ELF格式的共享库形式存在,由devhost进程按需动态加载(实现代码参见zircon/system/core/devmgr/devhost/目录)。核心设备管理进程(devmgr),包含具有跟踪设备与驱动关联的devcoordinator进程,同时管理着驱动程序发现,devhost进程创建和控制,还要维护设备文件系统(devfs),通过devfs机制,用户层的服务和应用实现对设备的操作。

~/fuchsia$ ls zircon/system/core/devmgr/
BUILD.gn  component  devcoordinator  devhost  dmctl  fshost  OWNERS  shared
~/fuchsia$ 

进程devcoordinator将设备看做是一个统一树状结构。树的分支(和子分支)由一定数量的隶属于devhost进程的设备组成。关于如何将整棵设备树划分以分配到多个devhost进程中,取决于系统的策略:基于安全或者稳定性原因的驱动隔离;以及为了性能原因将驱动进行并置。

注意:当前的策略非常简单(具有总线能力的设备与其子设备属于同一个devhost进程),将来会提供更细粒度的划分策略。如下devmgr命令dump的显示,PCI总线设备的devhost进程号为2498,其下的三个总线设备([00:00.0],[00:02.0]和[00:1f.0])都在此进程中。但是,对于ahci设备及其子设备([sata0]等)又是位于另一个PID为3377的devhost进程中。

$ dm dump
[root]
   <root> pid=2602 
   [sys]
      <sys> pid=2498 /boot/driver/platform-bus.so
         [pci] pid=2498 /boot/driver/platform-bus-x86.so
            [00:00.0] pid=2498 /boot/driver/bus-pci.so
            [00:02.0] pid=2498 /boot/driver/bus-pci.so
               <00:02.0> pid=3377 /boot/driver/bus-pci.proxy.so
                  [ahci] pid=3377 /boot/driver/ahci.so
                     [sata0] pid=3377 /boot/driver/ahci.so
                        [block] pid=3377 /boot/driver/block.core.so
            [00:1f.0] pid=2498 /boot/driver/bus-pci.so


设备、驱动与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

方括号中的为设备名。尖括号中的为代理设备名,当支持进程隔离功能时,将会在设备树层级较低的devhost进程中实现代理设备。标签pid=之后显示的是拥有此设备的devhost进程ID。最后的路径显示此设备的驱动程序。

例如,以上pid等于1416的devhost进程包含有PCI总线驱动,其负责为系统中的PCI硬件创建设备树节点。PCI设备[00:02:00]正好是一个intel的以太网接口设备,对应的驱动程序为:intel-ethernet.so。此驱动程序由一个新创建的devhost进程(pid 2052)负责加载和绑定,并且相较于devhost进程1416更底层的进程,创建一个代理设备<00:02:00>。

由于代理设备在设备文件系统中不可见,以上的以太网设备的文件路径为:/dev/sys/pci/00:02:00/intel-ethernet。


协议、接口与类


设备可以实现协议,子设备可使用协议提供的C ABIs以设备特有的方式与父设备交互。协议的例子有:PCI协议、USB协议、Block Core协议和Ethermac协议等。通常情况下协议接口用于同一个devhost进程内部的设备交互,但是在驱动隔离时,可使用RPC与较高层的devhost进程进行设备协议通信(通过代理)。

设备也可实现接口,其为提供给客户应用(服务、应用程序等)使用的特殊FIDL RPC协议。基础的设备接口支持POSIX格式的open/close/read/write操作。以上的基础设备接口由message()调用提供支持。由于历史原因,ioctl()调用当前也可支持,但是出于废弃状态,正处于被移除的过程。


多数情况下,设备协议可利用通用接口实现带来的益处,使驱动程序变得更简单。例如,块设备驱动实现通用block接口,可绑定到实现Block Core协议的设备;同样的以太网驱动实现通用ethernet接口,可绑定实现Ethermac协议的设备。某一些协议,比如以上刚介绍的两种,利用共享内存和非RPC信号机制来获得比其它方式更高的效率、更低延迟和更高吞吐。

类意味着设备对接口或者协议的实现。设备文件系统中的设备位于一定的拓扑路径下,比如:/sys/pci/00:02:00/intel-ethernet。如果其属于某个特殊类,其别名也将出现在类似这样的目录下:/dev/class/CLASSNAME/...。例如,intel-ethernet驱动实现了Ethermac接口,所以其出现在此目录:/dev/class/ethermac/000。类目录下的名称唯一但没有意义,应需求而赋予之。

注意:当前类目录下的名称为3个十进制数字,但是将来很可能改变此格式。客户应用不应当对设备的类别名假定任何特定的意义。


设备驱动生存期

当有需要的时候devhost进程加载相应设备驱动。加载与否由绑定程序来决定,其描述了驱动可绑定的设备信息,位于ddk/binding.h文件中的宏用来定义绑定程序。

以下示例为Intel以太网驱动的绑定程序:

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区(.note.zircon.driver),以便devcoordinator进程不需要整体加载驱动就可获得绑定信息。红顶_BEGIN的第二个参数是zx_driver_ops_t类型的结构指针 (定义于文件 [ddk/driver.h](../../system/ulib/ddk/include/ddk/driver.h),此结构定义了init, bind, create, 和release方法。


init()方法在驱动加载到devhost经常时触发,可用来做一些全局的优化。通常情况下不需要,但是如果实现了init()方法不过执行失败了,驱动的加载将会失败。


bind()方法使得驱动程序可以绑定设备。设备应匹配驱动声明的绑定程序中的设备。如果bind()方法成功,驱动必须创建一个新的设备,并且将其添加为bind()参数中传入设备的子设备。更多信息参见设备生存期的介绍(下一节)。

create()方法由platform/system总线驱动或者proxy代理驱动触发。对于绝大多数驱动来说,不需要此方法。

release()方法在驱动卸载之前,所有其通过bind创建的设备或者其它地方创建的设备已经销毁之后触发。当前此方法不会触发。驱动一旦加载,一直到其devhost进程结束。


设备生存期

在devhost进程中,设备以zx_device_t结构类型(驱动并不知晓此结构)的树形式存在。在这些设备树节点的创建函数device_add的参数中,驱动程序提供一个zx_protocol_device_t结构数据。此结构中定义的函数指针正是“device ops”。这些都定义在文件device.h中(zircon/system/ulib/ddk/include/ddk目录)。

函数device_add()创建一个新的设备,并且将此添加为参数中提供的父设备的子设备。父设备必须是传入驱动函数bind()的参数设备,或者是相同驱动创建的另一设备。

device_add()函数的一个副作用是新创建的设备将被添加到devcoordinator维护的设备文件系统中。如果设备创建时使用了DEVICE_ADD_INVISIBLE标志,在调用device_make_visible函数之后,不能够通过打开此设备在devfs中的设备节点进行访问。这一点对于一些需要做额外的初始化或者探测工作,而不希望在成功完成之前可见的设备有用处(失败的话移除设备)。


设备具有引用计数。当驱动使用device_add函数创建设备时,将保有一个该设备引用直到调用device_remove移除设备。如果设备被一个进程通过设备文件系统中的节点打开,其也将获得一个设备引用。当一个设备的父设备移除时,触发其unbind方法,以通知驱动程序应当关闭此设备,并且通过调用device_remove函数移除其所有子设备。


如果子设备的unbind函数被调用时还正在使用,调用其device_remove函数的父设备很可能继续接收到针对子设备的设备方法调用或者协议方法调用。建议在子设备移除之前,父设备为这些方法调用返回错误,使得在子设备完全移除之前这些调用不会发起更多的工作或者引起不可预测的交互。


device_add()函数不带有DEVICE_ADD_INVISIBLE标志调用之后,或者对一个不可见设备调用了device_make_visible()函数之后,devhost进程可能调用其它的设备处理函数。


当创建设备的驱动调用了device_remove之后触发设备的release()方法,所有此设备的实例将被关闭,所有的子设备将被移除和释放。这是最后的机会驱动程序去销毁和释放与设备关联的资源。release方法返回之后,设备的zx_device_t结构变为无效。任何设备方法或者由父设备协议获取的协议方法的调用都是非法的,并且很可能导致系统崩溃。

设备拆毁时序示例


为解释unbind()和release()函数在拆毁过程中的作用,看一下USB WLAN驱动的处理示例。简单来说,unbind()方法是由上到下,而release()方法的调用顺序是由下到上。


以下仅是一个示例。可能并不符合WLAN驱动的真实情况。


假设WLAN设备作为一个USB设备插入系统,首先在USB设备下创建一个WLAN PHY接口设备。PHY接口之下,创建2个MAC接口设备。

            +------------+
            | USB Device |
            +------------+
                  |
            +------------+
            |  WLAN PHY  | .unbind()
            +------------+ .release()
              |        |
    +------------+  +------------+
    | WLAN MAC 0 |  | WLAN MAC 1 | .unbind()
    +------------+  +------------+ .release()

现在,我们拔出USB WLAN设备。

USB XHCI驱动检测到拔出操作,调用device_remove方法(隶属于USB设备)。

既然父设备移除,WLAN PHY的unbind方法被调用。在unbind方法中,应当移除通过device_add创建的接口。

    wlan_phy_unbind(void* ctx) {
        // Stop interrupt or anything to prevent incoming requests.
        ...

        device_remove(wlan_phy);
    }

当WLAN PHY被移除后,将对其所有的子设备(wlan_mac_0, wlan_mac_1)调用unbind方法。

    wlan_mac_unbind(void* ctx) {
        // Stop accepting new requests, and notify clients that this device is offline (often just
        // by returning an ZX_ERR_IO_NOT_PRESENT to any requests that happen after unbind).
        ...

        device_remove(iface_mac_X);
    }

当设备所有的客户被移除之后,并且设备没有任何子设备后,其引用计数为0时,release()方法被调用。

WLAN MAC 0 和 1的release()方法被调用。

    wlan_mac_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }

WLAN PHY没有打开的连接,但是仍然由子设备(wlan_mac_0 and wlan_mac_1)。一旦子设备全部被释放,其引用计数最终达到0,release()方法被调用。

    wlan_phy_release(void* ctx) {
        // Release sources allocated at creation.
        ...

        // Delete the object here.
        ...
    }

 

END

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值