本文导航 -1.设备驱动架构08% -1.1.Windows驱动架构09% -1.2.Linux驱动架构16% -2.设备驱动API22% -Windows上的驱动程序I/O模式46% -Linux上的驱动程序I/O模式51% -在Windows上注册设备27% -在Linux上注册设备33% -2.1.初始化23% -2.2.命名和声明设备27% -2.3.交换数据42% -3.设备驱动开发环境56% -Windows驱动程序工具包56% -Linux源代码60% -3.1.设备驱动框架56% -3.2.为设备驱动构建系统65% -3.3.文档支持69% -3.4.调试支持72% -4.设备驱动分发79% -4.2.更新设备驱动84% -4.3.安全方面的考虑88% -5.结论92% 名词缩写: API应用程序接口ApplicationProgramInterfaceABI应用系统二进制接口ApplicationBinaryInterface设备驱动是操作系统的一部分,它能够通过一些特定的编程接口便于硬件设备的使用,这样软件就可以控制并且运行那些设备了。因为每个驱动都对应不同的操作系统,所以你就需要不同的Linux、Windows或Unix设备驱动,以便能够在不同的计算机上使用你的设备。这就是为什么当你雇佣一个驱动开发者或者选择一个研发服务商提供者的时候,查看他们为各种操作系统平台开发驱动的经验是非常重要的。 驱动开发的第一步是理解每个操作系统处理它的驱动的不同方式、底层驱动模型、它使用的架构、以及可用的开发工具。例如,Linux驱动程序模型就与Windows非常不同。虽然Windows提倡驱动程序开发和操作系统开发分别进行,并通过一组ABI调用来结合驱动程序和操作系统,但是Linux设备驱动程序开发不依赖任何稳定的ABI或API,所以它的驱动代码并没有被纳入内核中。每一种模型都有自己的优点和缺点,但是如果你想为你的设备提供全面支持,那么重要的是要全面的了解它们。 在本文中,我们将比较Windows和Linux设备驱动程序,探索不同的架构,API,构建开发和分发,希望让您比较深入的理解如何开始为每一个操作系统编写设备驱动程序。 1.设备驱动架构 Windows设备驱动程序的体系结构和Linux中使用的不同,它们各有优缺点。差异主要受以下原因的影响:Windows是闭源操作系统,而Linux是开源操作系统。比较Linux和Windows设备驱动程序架构将帮助我们理解Windows和Linux驱动程序背后的核心差异。 1.1.Windows驱动架构 虽然Linux内核分发时带着Linux驱动,而Windows内核则不包括设备驱动程序。与之不同的是,现代Windows设备驱动程序编写使用Windows驱动模型(WDM),这是一种完全支持即插即用和电源管理的模型,所以可以根据需要加载和卸载驱动程序。 处理来自应用的请求,是由Windows内核的中被称为I/O管理器的部分来完成的。I/O管理器的作用是是转换这些请求到I/O请求数据包IORequestPackets(IRP),IRP可以被用来在驱动层识别请求并且传输数据。 Windows驱动模型WDM提供三种驱动,它们形成了三个层: 过滤Filter驱动提供关于IRP的可选附加处理。 功能Function驱动是实现接口和每个设备通信的主要驱动。 总线Bus驱动服务不同的配适器和不同的总线控制器,来实现主机模式控制设备。 一个IRP通过这些层就像它们经过I/O管理器到达底层硬件那样。每个层能够独立的处理一个IRP并且把它们送回I/O管理器。在硬件底层中有硬件抽象层(HAL),它提供一个通用的接口到物理设备。 1.2.Linux驱动架构 相比于Windows设备驱动,Linux设备驱动架构根本性的不同就是Linux没有一个标准的驱动模型也没有一个干净分隔的层。每一个设备驱动都被当做一个能够自动的从内核中加载和卸载的模块来实现。Linux为即插即用设备和电源管理设备提供一些方式,以便那些驱动可以使用它们来正确地管理这些设备,但这并不是必须的。 模式输出那些它们提供的函数,并通过调用这些函数和传入随意定义的数据结构来沟通。请求来自文件系统或网络层的用户应用,并被转化为需要的数据结构。模块能够按层堆叠,在一个模块进行处理之后,另外一个再处理,有些模块提供了对一类设备的公共调用接口,例如USB设备。 Linux设备驱动程序支持三种设备: 实现一个字节流接口的字符Character设备。 用于存放文件系统和处理多字节数据块IO的块Block设备。 用于通过网络传输数据包的网络Network接口。 Linux也有一个硬件抽象层(HAL),它实际扮演了物理硬件的设备驱动接口。 2.设备驱动API Linux和Windows驱动API都属于事件驱动类型:只有当某些事件发生的时候,驱动代码才执行——当用户的应用程序希望从设备获取一些东西,或者当设备有某些请求需要告知操作系统。 2.1.初始化 在Windows上,驱动被表示为DriverObject结构,它在DriverEntry函数的执行过程中被初始化。这些入口点也注册一些回调函数,用来响应设备的添加和移除、驱动卸载和处理新进入的IRP。当一个设备连接的时候,Windows创建一个设备对象,这个设备对象在设备驱动后面处理所有应用请求。 相比于Windows,Linux设备驱动生命周期由内核模块的module_init和module_exit函数负责管理,它们分别用于模块的加载和卸载。它们负责注册模块来通过使用内核接口来处理设备的请求。这个模块需要创建一个设备文件(或者一个网络接口),为其所希望管理的设备指定一个数字识别号,并注册一些当用户与设备文件交互的时候所使用的回调函数。 2.2.命名和声明设备 在Windows上注册设备 Windows设备驱动在新连接设备时是由回调函数AddDevice通知的。它接下来就去创建一个设备对象deviceobject,用于识别该设备的特定的驱动实例。取决于驱动的类型,设备对象可以是物理设备对象PhysicalDeviceObject(PDO),功能设备对象FunctionDeviceObject(FDO),或者过滤设备对象FilterDeviceObject(FIDO)。设备对象能够堆叠,PDO在底层。 设备对象在这个设备连接在计算机期间一直存在。DeviceExtension结构能够被用于关联到一个设备对象的全局数据。 设备对象可以有如下形式的名字\Device\DeviceName,这被系统用来识别和定位它们。应用可以使用CreateFileAPI函数来打开一个有上述名字的文件,获得一个可以用于和设备交互的句柄。 然而,通常只有PDO有自己的名字。未命名的设备能够通过设备级接口来访问。设备驱动注册一个或多个接口,以位全局唯一标识符(GUID)来标示它们。用户应用能够使用已知的GUID来获取一个设备的句柄。 在Linux上注册设备 在Linux平台上,用户应用通过文件系统入口访问设备,它通常位于/dev目录。在模块初始化的时候,它通过调用内核函数register_chrdev创建了所有需要的入口。应用可以发起open系统调用来获取一个文件描述符来与设备进行交互。这个调用后来被发送到回调函数,这个调用(以及将来对该返回的文件描述符的进一步调用,例如read、write或close)会被分配到由该模块安装到file_operations或者block_device_operations这样的数据结构中的回调函数。 设备驱动模块负责分配和保持任何需要用于操作的数据结构。传送进文件系统回调函数的file结构有一个private_data字段,它可以被用来存放指向具体驱动数据的指针。块设备和网络接口API也提供类似的字段。 虽然应用使用文件系统的节点来定位设备,但是Linux在内部使用一个主设备号majornumbers和次设备号minornumbers的概念来识别设备及其驱动。主设备号被用来识别设备驱动,而次设备号由驱动使用来识别它所管理的设备。驱动为了去管理一个或多个固定的主设备号,必须首先注册自己或者让系统来分配未使用的设备号给它。 目前,Linux为主次设备对major-minorpairs使用一个32位的值,其中12位分配主设备号,并允许多达个不同的设备。主次设备对对于字符设备和块设备是不同的,所以一个字符设备和一个块设备能使用相同的设备对而不导致冲突。网络接口是通过像eth0的符号名来识别,这些又是区别于主次设备的字符设备和块设备的。 2.3.交换数据 Linux和Windows都支持在用户级应用程序和内核级驱动程序之间传输数据的三种方式: 缓冲型输入输出BufferedInput-Output它使用由内核管理的缓冲区。对于写操作,内核从用户空间缓冲区中拷贝数据到内核分配的缓冲区,并且把它传送到设备驱动中。读操作也一样,由内核将数据从内核缓冲区中拷贝到应用提供的缓冲区中。 直接型输入输出DirectInput-Output它不使用拷贝功能。代替它的是,内核在物理内存中钉死一块用户分配的缓冲区以便它可以一直留在那里,以便在数据传输过程中不被交换出去。 内存映射Memorymapping它也能够由内核管理,这样内核和用户空间应用就能够通过不同的地址访问同样的内存页。 Windows上的驱动程序I/O模式 支持缓冲型I/O是WDM的内置功能。缓冲区能够被设备驱动通过在IRP结构中的AssociatedIrp.SystemBuffer字段访问。当需要和用户空间通讯的时候,驱动只需从这个缓冲区中进行读写操作。 Windows上的直接I/O由内存描述符列表memorydescriptorlists(MDL)介导。这种半透明的结构是通过在IRP中的MdlAddress字段来访问的。它们被用来定位由用户应用程序分配的缓冲区的物理地址,并在I/O请求期间钉死不动。 在Windows上进行数据传输的第三个选项称为METHOD_NEITHER。在这种情况下,内核需要传送用户空间的输入输出缓冲区的虚拟地址给驱动,而不需要确定它们有效或者保证它们映射到一个可以由设备驱动访问的物理内存地址。设备驱动负责处理这些数据传输的细节。 Linux上的驱动程序I/O模式 Linux提供许多函数例如,clear_user、copy_to_user、strncpy_from_user和一些其它的用来在内核和用户内存之间进行缓冲区数据传输的函数。这些函数保证了指向数据缓存区指针的有效,并且通过在内存区域之间安全地拷贝数据缓冲区来处理数据传输的所有细节。 然而,块设备的驱动对已知大小的整个数据块进行操作,它可以在内核和用户地址区域之间被快速移动而不需要拷贝它们。这种情况是由Linux内核来自动处理所有的块设备驱动。块请求队列处理传送数据块而不用多余的拷贝,而Linux系统调用接口来转换文件系统请求到块请求中。 最终,设备驱动能够从内核地址区域分配一些存储页面(不可交换的)并且使用remap_pfn_range函数来直接映射这些页面到用户进程的地址空间。然后应用能获取这些缓冲区的虚拟地址并且使用它来和设备驱动交流。 3.设备驱动开发环境 3.1.设备驱动框架 Windows驱动程序工具包 Windows是一个闭源操作系统。Microsoft提供Windows驱动程序工具包以方便非Microsoft供应商开发Windows设备驱动。工具包中包含开发、调试、检验和打包Windows设备驱动等所需的所有内容。 Windows驱动模型WindowsDriverModel(WDM)为设备驱动定义了一个干净的接口框架。Windows保持这些接口的源代码和二进制的兼容性。编译好的WDM驱动通常是前向兼容性:也就是说,一个较旧的驱动能够在没有重新编译的情况下在较新的系统上运行,但是它当然不能够访问系统提供的新功能。但是,驱动不保证后向兼容性。 Linux源代码 和Windows相对比,Linux是一个开源操作系统,因此Linux的整个源代码是用于驱动开发的SDK。没有驱动设备的正式框架,但是Linux内核包含许多提供了如驱动注册这样的通用服务的子系统。这些子系统的接口在内核头文件中描述。 尽管Linux有定义接口,但这些接口在设计上并不稳定。Linux不提供有关前向和后向兼容的任何保证。设备驱动对于不同的内核版本需要重新编译。没有稳定性的保证可以让Linux内核进行快速开发,因为开发人员不必去支持旧的接口,并且能够使用最好的方法解决手头的这些问题。 当为Linux写树内in-tree(指当前Linux内核开发主干)驱动程序时,这种不断变化的环境不会造成任何问题,因为它们作为内核源代码的一部分,与内核本身同步更新。然而,闭源驱动必须单独开发,并且在树外out-of-tree,必须维护它们以支持不同的内核版本。因此,Linux鼓励设备驱动程序开发人员在树内维护他们的驱动。 3.2.为设备驱动构建系统 Windows驱动程序工具包为MicrosoftVisualStudio添加了驱动开发支持,并包括用来构建驱动程序代码的编译器。开发Windows设备驱动程序与在IDE中开发用户空间应用程序没有太大的区别。Microsoft提供了一个企业Windows驱动程序工具包,提供了类似于Linux命令行的构建环境。 Linux使用Makefile作为树内和树外系统设备驱动程序的构建系统。Linux构建系统非常发达,通常是一个设备驱动程序只需要少数行就产生一个可工作的二进制代码。开发人员可以使用任何IDE,只要它可以处理Linux源代码库和运行make,他们也可以很容易地从终端手动编译驱动程序。 3.3.文档支持 Windows对于驱动程序的开发有良好的文档支持。Windows驱动程序工具包包括文档和示例驱动程序代码,通过MSDN可获得关于内核接口的大量信息,并存在大量的有关驱动程序开发和Windows底层的参考和指南。 Linux文档不是描述性的,但整个Linux源代码可供驱动开发人员使用缓解了这一问题。源代码树中的Documentation目录描述了一些Linux的子系统,但是有几本书[1]介绍了关于Linux设备驱动程序开发和Linux内核概览,它们更详细。 Linux没有提供设备驱动程序的指定样本,但现有生产级驱动程序的源代码可用,可以用作开发新设备驱动程序的参考。 3.4.调试支持 Linux和Windows都有可用于追踪调试驱动程序代码的日志机制。在Windows上将使用DbgPrint函数,而在Linux上使用的函数称为printk。然而,并不是每个问题都可以通过只使用日志记录和源代码来解决。有时断点更有用,因为它们允许检查驱动代码的动态行为。交互式调试对于研究崩溃的原因也是必不可少的。 Windows通过其内核级调试器WinDbg支持交互式调试。这需要通过一个串行端口连接两台机器:一台计算机运行被调试的内核,另一台运行调试器和控制被调试的操作系统。Windows驱动程序工具包包括Windows内核的调试符号,因此Windows的数据结构将在调试器中部分可见。 Linux还支持通过KDB和KGDB进行的交互式调试。调试支持可以内置到内核,并可在启动时启用。之后,可以直接通过物理键盘调试系统,或通过串行端口从另一台计算机连接到它。KDB提供了一个简单的命令行界面,这是唯一的在同一台机器上来调试内核的方法。然而,KDB缺乏源代码级调试支持。KGDB通过串行端口提供了一个更复杂的接口。它允许使用像GDB这样标准的应用程序调试器来调试Linux内核,就像任何其它用户空间应用程序一样。 4.设备驱动分发 4.1.安装设备驱动 在Windows上安装的驱动程序,是由被称为为INF的文本文件描述的,通常存储在C:\Windows\INF目录中。这些文件由驱动供应商提供,并且定义哪些设备由该驱动程序服务,哪里可以找到驱动程序的二进制文件,和驱动程序的版本等。 当一个新设备插入计算机时,Windows通过查看已经安装的驱动程序并且选择适当的一个加载。当设备被移除的时候,驱动会自动卸载它。 在Linux上,一些驱动被构建到内核中并且保持永久的加载。非必要的驱动被构建为内核模块,它们通常是存储在/lib/modules/kernel-version目录中。这个目录还包含各种配置文件,如modules.dep,用于描述内核模块之间的依赖关系。 虽然Linux内核可以在自身启动时加载一些模块,但通常模块加载由用户空间应用程序监督。例如,init进程可能在系统初始化期间加载一些模块,udev守护程序负责跟踪新插入的设备并为它们加载适当的模块。 4.2.更新设备驱动 Windows为设备驱动程序提供了稳定的二进制接口,因此在某些情况下,无需与系统一起更新驱动程序二进制文件。任何必要的更新由WindowsUpdate服务处理,它负责定位、下载和安装适用于系统的最新版本的驱动程序。 然而,Linux不提供稳定的二进制接口,因此有必要在每次内核更新时重新编译和更新所有必需的设备驱动程序。显然,内置在内核中的设备驱动程序会自动更新,但是树外模块会产生轻微的问题。维护最新的模块二进制文件的任务通常用DKMS[2]来解决:这是一个当安装新的内核版本时自动重建所有注册的内核模块的服务。 4.3.安全方面的考虑 所有Windows设备驱动程序在Windows加载它们之前必须被数字签名。在开发期间可以使用自签名证书,但是分发给终端用户的驱动程序包必须使用Microsoft信任的有效证书进行签名。供应商可以从Microsoft授权的任何受信任的证书颁发机构获取软件出版商证书SoftwarePublisherCertificate。然后,此证书由Microsoft交叉签名,并且生成的交叉证书用于在发行之前签署驱动程序包。 Linux内核也能配置为在内核模块被加载前校验签名,并禁止不可信的内核模块。被内核所信任的公钥集在构建时是固定的,并且是完全可配置的。由内核执行的检查,这个检查严格性在构建时也是可配置的,范围从简单地为不可信模块发出警告,到拒绝加载有效性可疑的任何东西。 5.结论 如上所示,Windows和Linux设备驱动程序基础设施有一些共同点,例如调用API的方法,但更多的细节是相当不同的。最突出的差异源于Windows是由商业公司开发的封闭源操作系统这个事实。这使得Windows上有好的、文档化的、稳定的驱动ABI和正式框架,而在Linux上,更多的是源代码做了一个有益的补充。文档支持也在Windows环境中更加发达,因为Microsoft具有维护它所需的资源。 另一方面,Linux不会使用框架来限制设备驱动程序开发人员,并且内核和产品级设备驱动程序的源代码可以在需要的时候有所帮助。缺乏接口稳定性也有其作用,因为它意味着最新的设备驱动程序总是使用最新的接口,内核本身承载较小的后向兼容性负担,这带来了更干净的代码。 了解这些差异以及每个系统的具体情况是为您的设备提供有效的驱动程序开发和支持的关键的第一步。我们希望这篇文章对Windows和Linux设备驱动程序开发做的对比,有助于您理解它们,并在设备驱动程序开发过程的研究中,将此作为一个伟大的起点。 via:怎样治疗白癜风白癜风那家医院治疗好
|