# 入门
本文档是[Driver Development Kit教程](ddk-tutorial.md)文档的一部分。
编写设备驱动程序通常被视为一项艰巨的任务,充满了复杂性,并且需要鲜为人知的内核机密的晦涩知识。
本节的目标是揭开这一过程的神秘面纱;你会学到一切需要知道的如何编写设备驱动程序的知识。从他们的工作开始,它们如何起作用,以及它们如何适应整个系统。
##概述
在最高级别,设备驱动程序的工作是为特定设备提供统一的接口,同时隐藏特定于设备实现的细节。
例如,两个不同的以太网驱动程序都允许客户端发送数据包,使用完全相同的C语言函数输出接口。每个驱动程序都负责管理自己的硬件,但是,即使硬件不同,客户端接口相同。
请注意,驱动程序提供的接口可能是“中间”&mdash层面的。也就是说,它们可能不一定代表链中的“最终”设备。
考虑一个基于PCI的以太网设备。
首先,需要基本PCI驱动程序,了解如何与PCI总线本身通信。
这个驱动程序对以太网一无所知,但确实知道如何与处理机器上的特定PCI芯片组通信。
它枚举该总线上的设备,收集来自每个设备上的各种寄存器信息,并提供允许其客户端(如基于PCI的以太网驱动程序)执行PCI操作的功能,比如分配中断或DMA通道。
因此,这个基本PCI驱动程序为以太网驱动程序提供服务,以太网驱动程序来管理其相关硬件。
同时,其他设备(如视频卡)也可以使用基本PCI驱动程序以类似的方式管理其硬件。
##zircon模型
为了提供最大的灵活性,Zircon世界的驱动允许绑定到匹配的“父”设备,并发布自己的“儿子”。
此层次结构根据需要进行扩展:一个驱动程序可能只发布一个子项,另一个驱动程序认为该子项是其parent,第二个驱动程序发布自己的child,等等。
为了理解其工作原理,让我们按照基于PCI的以太网示例进行操作。
系统首先提供一个特殊的“PCI root”父级。
实际上,它说的是“我知道当你在这个系统上有一个PCI总线,当你找到它时,把它绑定在*这里*。“
>下面的“高级主题”部分提供了有关此过程的更多详细信息。
驱动程序由系统(搜索目录)和驱动程序进行评估匹配自动绑定。
在这种情况下,绑定到“PCI root”父级的驱动程序找到了,并绑定了。
这是基本的PCI驱动程序。它的工作是配置PCI总线,并枚举总线上的外设。
PCI总线具有关于如何识别外围设备的特定约定:
供应商ID(** VID **)和设备ID(** DID **)的组合是所有可能的PCI设备的唯一标识。
在枚举期间,将从外设和新父级读取这些值,发布包含检测到的VID和DID(以及其他主机)的节点信息)。
每次发布新设备时,都会执行与上述相同的重复过程(像最初的PCI root设备发布一样);
也就是说,系统会评估驱动程序,搜索匹配的驱动程序是否符合新父母的特点。
而对于PCI根设备,我们之前在搜索匹配某种功能(称为“协议”,我们很快就会看到这一点)的驱动程序,但是,在这种情况下,我们将搜索匹配不同协议的驱动程序,即满足“是一个PCI设备且有给定的VID和DID”的协议要求。
如果找到合适的驱动程序(一个匹配所需的协议,VID和DID),它将与父母绑定。
作为绑定的一部分,我们初始化驱动程序—这涉及此类操作,如设置card进行操作,启动接口,和发布此设备的一个或多个子设备。
对于PCI以太网驱动程序,它发布“以太网”接口,同时符合另一种协议,称为“以太网实现”协议。
该协议代表了一种与客户使用(但删除了一步;我们将回到此处)功能相近的通用协议。
###协议
我们上面提到了三个协议:
* PCI根协议(`ZX_PROTOCOL_PCIROOT`),
* PCI设备协议(`ZX_PROTOCOL_PCI`),和
*以太网实现协议(`ZX_PROTOCOL_ETHERNET_IMPL`)。
括号中的名称是与协议对应的C语言常量,供参考。
什么是协议?
协议是严格的接口定义。
以太网驱动程序发布了一个符合`ZX_PROTOCOL_ETHERNET_IMPL`的接口。
这意味着它必须提供在数据结构中定义的一组函数(在本案例中是`ethmac_protocol_ops_t`)。
这些功能对于实现协议的所有设备都是通用的 - mdash;例如,
所有以太网设备必须提供查询该MAC地址功能的接口。
其他协议当然对它们的功能有不同的要求,必须提供。
例如,块设备将发布符合“块实现协议”(`ZX_PROTOCOL_BLOCK_IMPL`)的接口,必须提供`block_protocol_ops_t`定义的函数。
该协议包括一个以块为单位返回设备大小的函数。
我们将在以下章节中研究这些协议。
#高级主题
上面介绍了zircon驱动器的全景图,重点是协议。
在本节中,我们将研究一些高级主题,例如平台相关的主题和平台无关的代码解耦,
“杂项”协议,以及协议和进程的映射方式。
##平台依赖与平台无关
上面,我们提到`ZX_PROTOCOL_ETHERNET_IMPL`是“接近”由客户端使用的函数,但这个接口将会被删除。那是因为还有一个协议,`ZX_PROTOCOL_ETHERNET`,介于客户端和驱动两者之间。
此附加协议用于处理所有以太网驱动程序的通用函数(以避免代码重复)。
此类功能包括缓冲区管理,状态报告和管理功能。
这实际上是“平台依赖”与“平台无关”的解耦;
公共代码存在于平台独立部分(一次),而特定于驱动程序的代码在平台相关部分中实现。
这种架构(套路)在多个地方都有使用。
例如,对于块设备,硬件驱动程序绑定到总线(例如,PCI),并提供`ZX_PROTOCOL_BLOCK_IMPL`协议。
独立于平台的驱动程序绑定到`ZX_PROTOCOL_BLOCK_IMPL`,并发布面向客户的协议,`ZX_PROTOCOL_BLOCK`。
您还可以通过显示控制器,I2C总线和串行驱动程序看到这一点。
##杂项协议
在[simple drivers](simple.md)中,我们展示了几个驱动程序的代码基本功能,但不提供与特定协议相关的服务(即,它们不是“以太网”或“块”设备)。
这些驱动程序绑定到`ZX_PROTOCOL_MISC_PARENT`。
> @@@更多内容?
##进程/协议映射
为了使讨论保持简单,我们没有讨论进程分离,因为它与驱动有关。
要了解这些问题,让我们看看其他操作系统如何处理它们,并将其与zircon方法进行比较。
在宏内核(如Linux)中,许多驱动程序都在内核中实现。
这意味着它们共享相同的地址空间,并且有效地生活在同一个“进程”中。
这种方法的主要问题是故障隔离/利用。
坏驱动程序可以取出整个内核,因为它位于同一个地址空间中,因此具有对所有内核内存和资源的特权访问。
受损的驱动程序可能出于同样的原因而出现安全威胁。
另一个极端,在一些微内核操作系统中,将每个驱动程序服务都放在自己的进程中。
它的主要缺点是如果一个驱动程序依赖于另一个驱动程序的服务,
内核至少会影响两个驱动进程的上下文切换操作(如果不是数据传输)。
虽然微内核操作系统通常对这种操作设计得很快,但是高频率的调用它们也是不合需求的。
Zircon采用的方法基于设备主机(** devhost **)的概念。
devhost是一个包含协议栈的进程—也就是说,一个或更多协同工作的协议。
devhost从ELF共享库加载驱动程序(称为动态共享对象,或** DSO **)。
在[simple drivers](simple.md)部分中,我们将看到meta信息包含在DSO中以加快发现过程。
协议栈有效地允许为设备创建完整的“驱动程序”,由平台相关和平台独立组件组成,在一个独立的过程容器中。
对于高级阅读器,请查看Zircon命令行可用的`dm dump`命令。
它显示设备树,并显示进程ID,DSO名称和其他有用的信息。
这是一个高度编辑的版本,仅显示PCI以太网驱动程序部分:
```
1. [root]
2. [sys]
3.
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. [intel-ethernet] pid=2052 /boot/driver/intel-ethernet.so
9. [ethernet] pid=2052 /boot/driver/ethernet.so
```
从上面可以看到进程ID“1416”(第3到6行)是"高级配置和电源接口"(** ACPI **)驱动程序,
通过DSO`bus-acpi.so`实现。
在主枚举期间,ACPI DSO检测到PCI总线。
这导致parent发布了`ZX_PROTOCOL_PCI_ROOT`(第5行,导致`[pci]`条目的出现)
然后导致devhost加载`bus-pci.so` DSO并绑定到它。
DSO是我们上面讨论的在整个过程中引用的“基本PCI驱动程序”。
在绑定期间,基本PCI驱动程序枚举了PCI总线,并找到了以太网卡(第6行检测总线0,设备2,功能0,显示为“[00:02:00]”)。
(当然,还发现了许多其他设备,但简单起见,我们已将它们从上面列出中移除)。
然后,检测到此设备会导致基本PCI驱动程序发布使用`ZX_PROTOCOL_PCI`和设备的VID和DID新parent。
此外,创建了一个新的devhost(进程ID“2052”)并加载了`bus-pci.proxy.so` DSO(第7行)。
此代理充当从新devhost(pid`2052`)到基本PCI驱动(pid`1416`)的接口。
>这是“切断”设备驱动程序只在自己进程的地方&mdash;新的devhost和基本的PCI驱动程序现在在两个不同的进程中。
新的devhost`2052`然后找到一个匹配的child(`intel-ethernet.so`第8行的DSO;它被认为是匹配的,因为它有'ZX_PROTOCOL_PCI`和正确VID和DID)。
DSO发布了一个`ZX_PROTOCOL_ETHERNET_IMPL`,它绑定到一个匹配的child(第9行的`ethernet.so` DSO;它被认为是匹配的,因为它有一个`ZX_PROTOCOL_ETHERNET_IMPL`协议)。
这个链没有显示的是最终的DSO(`ethernet.so`)发布了一个`ZX_PROTOCOL_ETHERNET`&mdash;这是客户端可以使用的部分,所以当然不会涉及进一步的“设备”绑定。