Electronic Joint Business

Solution for E-Business

Windows Architucture

利用总线接口进行驱动间通讯

这里我们将讨论高级 WDF 驱动开发中最常见的话题之一: 驱动间的通信。当引入 Windows 驱动开发其他话题,貌似简单的驱动间调用很快就变成一系列无穷无尽的选项、功能以及晦涩难懂的 WDM 琐事。1

不过从这些讨论中我们看到了希望之光:总线接口还存在着并活跃在 Windows 中,它不但提供了简洁的、定义良好的方式用于驱动间通信,而且 KDMF 还提供了帮助例程用来快速生产或消费这些接口。

什么是总线接口?
正如其名称所示,总线接口是总线驱动为其子驱动提供的一种标准程序调用接口。总线驱动可以有多个总线接口,每个接口通过 GUID 标识。接口的使用者通过 PnP IRP 来查询接口并返回一个总线定义的数据结构,该数据结构可以包含任何总线驱动想要共享的数据或函数 (如图1-1)。

当总线驱动向其子驱动的提供总线接口时,我们预期只有该 PDO 设备栈中的驱动将使用此接口。这样在卸载驱动时就可以消除任何可能存在的竞态。因为设备栈是从顶向下被逐一销毁的,因此 PDO 总是最后一个被删除的设备。假如总线接口被其设备栈内的驱动所使用,我们就无需担心在 PDO 被删除之后,还有程序试图使用该接口。

总线接口不只是给总线的
显然在总线驱动之外总线接口也相当有用,驱动间通信很常见且不是所有的驱动间通信都只涉及到与自己的 PDO 交互。还好,操作系统中对总线驱动的支持相当普遍,因此过滤型驱动或 FDO 可以对适当的 PnP IRP 进行处理,并发布自己的总线接口。这意味着我们可以使用由另一不同的设备栈 (图 2) 产生的总线接口。

>>> 阅读全文

 

, ,

编写虚拟 Storport 微端口驱动(一)

Storport 广受虚拟存储设备驱动编写者的欢迎。在 Storport 出现前,要完成类似的工作,你不得不修改(hack) SCSIport 微端口驱动,或者自己写个完整的 SCSI 端口驱动。修改微端口的方法十分复杂 (由于锁的问题)、 难以获得支持或者维护,而且性能较差。而编写完整的 SCSI 端口驱动的方法虽然性能不错,但技术难度高,而且也不支持 Windows 徽标认证。因此,这一领域的开发工作只限于那些愿意冒险、敢于尝试、而且不打算过徽标认证的公司和个人。1

Storport 是跟着 Windows Server 2003 被引入的,用来满足 RAID 和 SAN 环境的性能要求,这是以前的 SCSIport 驱动所不具备的。后续在 Windows Vista SP1 和 Server 2008 中,微软又更新了 Storport,提供了对 Storport 虚拟微端口驱动的支持。这个更新终于让存储驱动编写者拥有了一个能导出虚拟设备的模型。

对于虚拟微端口驱动的编写来说,Storport 模型相当不错。它提供了诸如 I/O 队列管理 (如队列深度、到达队列深度阀值时对 I/O 进行处理)、 I/O 错误处理 (如重置、重试等)、 启动或停止 PnP 设备 (LUN) 所需的所有操作以及总线驱动的所有职能。它只是无法提供硬件管理方面的帮助,因为我们开发的是虚拟微端口,所以并未存在任何硬件。另一方面,虚拟微端口驱动则要完全负责处理 I/O 请求。虽然听起来令人生畏,不过作为虚拟微端口驱动,你有全套的内核 API 和其它驱动程序来帮助完成这一工作。

开始
可能你从未接触过 Storport ,所以这里先了解一些基础知识。当你利用 Storport 模型进行开发时,这意味着你的驱动需要将接口暴露给 Storport 驱动 (Storport.sys),这是通过预定义的 API 和数据结构完成的,而不是基于 IRP。与 SCSI 端口/微端口模型类似,Storport 驱动被称为端口驱动,提供了微端口驱动进行操作的环境和支持例程,如此我们的适配器才对 Windows 存储子系统可见。这里适配器可以是物理设备或者虚拟设备,可以控制多条 SCSI 总线或多个 SCSI 设备。基于该模型所开发驱动即微端口驱动,图1-1 显示了 Storport 模型。

Storport 微端口驱动必须符合预定义的 Storport 规则以便将适配器(虚拟或物理)的服务提供给操作系统。Storport 通过 SCSI_REQUEST_BLOCKS (SRB — SCSI 请求块) 来调用微端口驱动, SRB 描述了要执行的操作。与 IRP 类似,SRB 包含了微端口驱动执行操作需要的所有信息。它包含一个操作码、 缓冲区以及用来描述请求的参数。驱动执行 SRB 描述的操作、将完成状态放入 SRB ,并完成请求时通知 Storport。该过程与普通驱动处理 IRP 不一样。

>>> 阅读全文

 

, , ,

小议微端口驱动、端口驱动和类驱动之间的关系

驱动程序由CLASS DRIVER,PORT DRIVER,MINIPORT,三部分组成,从层次上来说,LCASS DRIVER是针对几大类设备的驱动,比如存储设备驱动,就可以通过一个CLASS DRIVER来统一管理,完成一些这类设备共有的操作,主要是与设备无关的操作.PORT DRIVER从层次上来说,是在CLASS DRIVER和MINIPORT中间的一层驱动,相当与在CLASS DRIVER这个大类上面,在细分一些小类,把这些小类的共同操作写到这个驱动中,但是从功能上面说,PORT DRIVER又在MINIPORT之下,因为PORT DRIVER完成的操作可能是同步等等相当具体的功能,所以由PORT DRIVER提供具体操作供MINIPORT调用.MINIPORT则是针对每一个具体的设备的驱动,从层次上是在最底层,但是在功能上却是调用PORT DRIVER提供的功能来实现的.所以功能上来说,确实处于中间层次.

详解Windows 2000/XP Class/Port/Miniport驱动模型

对于软件复用,Microsoft Windows在用户层提供了COM(Component Object Model),其实现了二进制层级的兼容,使Windows开发进入了组件时代。COM的概念之一进程内组件,在用户态使用动态联接库作为载体。所以不管在COM之前或是之后,DLL都是Windows下实现软件复用的一个最主要的途径。只是单纯的DLL并未实现COM一样的二进制兼容的目的。但对于驱动开发者而言,不同于用户态的代码,2000/XP并未有相应的像COM一样的机制,实现核心态的二进制兼容方式。实际上对于内核态也没有必要提供一个像ole32.dll在用户态实现的COM Library这样的复杂机制(用户态COM组件遵循的简单的规则主要隐藏在COM Library的复杂之下)。

传统的DLL复用模式同样的适用于核心态代码的开发,加上为了简化同一类型设备开发工作等目的,Windows 2000/XP形成了Class/Port/Miniport这样的概念。我们知道对于Driver其提供的.sys文件实际与dll没有什么实质上的变化。我们首先讨论一下Class/Port/Miniport的概念与这种机制带来的益处:

1、我们知道PC机硬件通常可分成许多大类,如存储类设备等等。各大类又分成许多小类。如存储类设备又可分为Cdrom、Tape、Harddisk等等。对于每一类设备,提供一个class driver。这样的driver实现某一类型设备Driver与设备无关的公共的功能集合。对于Driver开发者而言,有了class driver的介入,仅仅需要实现一部分需要针对特定设备的功能,IO管理器需要的功能要不由class driver直接完成,要不就由class driver通过调用底层的模块来实现。class driver通常由Microsoft提供,通常实现的是设备无关的部分,这里的与设备无关主要是指不直接操作设备硬件。而这底层的模块通常称为miniport driver,后者由port driver提供服务(port driver类似于用户态DLL,不过她还实现一些简化设备驱动编写的功能,例如隐藏对IRP的繁琐操作等等)。Class driver调用miniport driver的方式主要有Device IOCTL,class driver导出的例程(就是DLL的概念)或是通过回调函数等等。这样分离出共有的部分,即可以实现所谓的复用,既减少了系统资源的占用,又减轻了开发工作量。例如对于存储类设备Microsoft分别提供了供了cdrom.sys,tape.sys与disk.sys三个class driver。这三个driver之中当然也存在一定的共同点,毕竟他们都属于存储类设备(底层硬件的区别通过miniport driver来区分)。Windows提供了classpnp.sys,三个driver将一些通用的例程置于这个sys中。这才更像是文章开头介绍的DLL复用技术,而不是这里介绍的class/port或是miniport driver。我在此段开头定义class实际上特别提出了与设备无关的共有功能的集合。即class driver实现的是与设备硬件无关的部分,这样做的好处是对于用户态的客户程序来说,不管对于scsi还是ide的设备,我们都可以使用一样的io操作来进行读写等操作,这里面的主要不同由class driver分发不同的请求至相应的port/miniport driver来实现对用户的透明。这才是最主要的class driver的目的。Microsoft还提供了一个miniclass的概念,在本文未加以叙述(具体请自行参考相关文档)。

>>> 阅读全文

 

,

IRP

在 Windows NT内核的 IO 模型中,有两类 IRP:threaded irp和non-threaded irp,顾名思义,前者跟thread绑定,后者跟thread无关。当一个threaded irp被创建时,创建线程会有一个队列保存该irp,直到irp完成之后才释放。当你试图让这条线程退出时,系统会检测队列看里面是否还有irp没完成,如果有,线程会一直等待,直到所有的irp全部完成。而non-thread irp则正好相反,如果该irp已经返回到了创建它的地方你还继续complete它,BSOD将会发生。1

Threaded IRP
如前面所讲,threaded irp和线程绑定在一起。当user mode程序发起IO操作(比如WriteFile函数),或者一个驱动向另一个驱动发起IO操作(比如ZwWriteFile),IOManager首先找到文件handle对应的驱动栈,获取栈顶的PDEVICE_OBJECT,生成一个irp,将IO操作的各种信息放入irp中,并将此irp放入线程的irp队列中,然后调用IoCallDriver将irp转给驱动栈一层层处理,每往下一层,IO_STACK_LOCATION中的指针就会往下移一个位置(实际情况比这稍微复杂一点点,以后再说,现在先把它想成是一个栈,每往下一层就是push一次),直到最后跟硬件打完交道返回。返回的过程正好与上述相反。最末一层处理完irp后,会调用IoCompleteRequest,此函数会将IO_STACK_LOCATION指针往上移(pop),并最后调用上层驱动设置的CompletRoutine。每层的CompleteRoutine做完相应工作后都会调用IoCompleteRequest讲irp继续往上转,直到栈顶PDEVICE_OBJECT。此时IO_STACK_LOCATION已经在顶部,所谓再上层的CompleteRoutine也不存在,所以本层CompleteRoutine中操作将irp归还给IOManager,IOManager负责回收资源,并从线程irp队列中去除相应的项。

这个过程和函数调用很像,像到你都会发问:为什么搞成这种模式,函数调用和返回不也有压栈出栈的操作吗,干嘛不借用这套机制反而要另起炉灶单独搞一套。答案很简单,就是效率。nt内核的IO操作全是异步的,调用IoCallDriver的线程和CompleteRoutine响应到的线程,不一定是同一条。关于这点我们以后再谈,但它却引出了一个目前我们就需要关心的问题:既然CompleteRotine的线程环境可能已经切换了,那么IOManager如何知道从哪个线程的irp队列里去除相应项?答案也很简单,就是保存在irp中。每一个threaded irp的PIRP->Thread域都保存它的创建线程,IOManager就是用它来寻找线程。注意:有人会想从PIRP->Thread里获得当前线程的信息,这是不对的。

Non-threaded irp
non-threaded irp一般都由驱动创建而不是IOManager,线程不在irp队列里保存它的实例,并且它的PIRP->Thread为NULL。当CompleteRoutine一路返回调到该irp的创建者时,千万不要返还给IOManager,直接free掉就可以了。

irp创建函数
从user mode一路下来的irp一定是threaded irp这很明显。那么内核态的函数中,哪些是创建threaded irp,哪些是创建non-threaed irp的?以下表格列出了各函数:

>>> 阅读全文

 

, , ,

为 Windows NT 内核扩展 API

与 Unix 基于中断的系统调用接口类似, Windows NT 提供了大量未经文档化的基础系统服务,即原生 API (Native API)。这些内核模式的系统服务被 Win32、 Posix 和 OS2 等环境子系统用于在 NT 微内核(Windows NT 是个修改过的微内核体系结构的操作系统)之上实现其运行环境。在 Win32 下,原生 API 通过 NTDLL.DLL 的导出函数提供给其他 API 和程序使用,如 KERNEL32.DLL 中的许多函数只是简单地重定向到 NTDLL.DLL 中。

事实上 NTDLL.DLL 那些调用系统服务的函数不过是个存根函数,它们只是对 SysEnter/SysCall(早些版本使用 INT 2EH)1等系统调用接口的简单包装。以 NtCreateFile 函数为例:(Windows 7 SP1 64位):2 ,它的具体实现如下:

mov     r10,rcx ;保存 rcx 的值,rcx 将被置为 rip(返回值)
mov     eax,52h ;系统调用号
syscall
ret
nop     dword ptr [rax+rax]

存根函数负责在 EAX 中填入系统调用号,随后执行本机调用接口实现从用户态向内核态的跳转。EAX 中的调用号将用以查找并调用系统服务以完成 API 的最终功能。每个原生 API 的调用号各不同,例子中 NTCreateFile 的调用号是 0x52h,且随着 Windows 的版本和架构不尽相同,有兴趣的话可以查询 Windows X86 系统调用表Windows X86-64 系统调用表

执行 SYSCALL 或 SYSENTER 时,CPU 并不会象执行 CALL一样保存返回地址和状态信息,所以需要存根函数来保存返回信息。3

当从原生 API 进入内核态以后,后续则交由 NCI 派发器 (NCI Dispatcher)处理。 NCI 派发器在微内核中(NTOSKRNL.EXE) 实现的, NCI 派发器( _KiSystemService ) 本质上就是 INT 2EH 的处理函数(handler)。NCI 派发器通过系统服务描述符表 (System Service Descriptor Table — SSDT) 来查找并调用某个服务的相应处理函数,函数参数则通过 NCI 传递。(一些内核调试器比如 IMHO 或者 NuMega Soft-ICE 可以通过 NTCALL 命令来显示 SSDT)。 微软并未提供文档来说明如何扩展原生 API,并通过 NCI 来调用自己的内核服务。所以本文试图通过变通的办法来解决这一问题。不过在此之前,我们还是大致看一下来如何通过 NCI 来调用原生 API 服务,或者其他 NCI 服务。

>>> 阅读全文

 

, , , ,

基于 UMDF 编写虚拟读卡器驱动

智能卡和 PKI 是有趣的领域。这里你可以看到最先进的计算机安全技术及其在现实环境中的运用。但是,调试测试与智能卡相关的应用有时候十分痛苦,尤其是在运行负面测试用例的时候,很多时候,你手上没有太多的智能卡以供测试。一旦 PIN 码被屏蔽或 CSP 发出错误命令都可能导致智能卡状态不一致。这类问题相当普遍,所以我意识到,从事智能卡开发第一要务就是:你需要个模拟器来进行各种试验而不会有任何损失。本文并不准备谈论该如何实现智能卡的操作系统仿真(也许以后吧),而是先从开发虚拟智能卡阅读器的驱动程序入手。1

在网上搜索“虚拟驱动程序”可以找到很多有用的资源,但大多不是我想要的“傻瓜式指导”。我不是驱动开发专家;所以本文主旨不是“如何编写驱动程序”。而只是展示我在这个新领域里的一些探索,并希望对其他人有所帮助。

还有一种编写智能卡驱动的方法是编写自己的 winscard.dll 版本,并将它保存在要调试的应用程序的目录下。这种方法更简单,但也有一些缺点:

  • 要完全模拟 Windows 智能卡资源管理器,需要自己实现一些缺失的函数。
  • 要实现诸如 SCardGetStatusChange 等函数十分痛苦,特别要同时考虑真实的读卡器和模拟读卡器
  • 由于系统文件保护机制,你无法覆盖掉系统的 winscard.dll,对有些应用来说,想跳过它得大费周章。

尝试了两种做法之后,我认为重新编写个驱动的做法更为妥帖,并从中学习了不少有益的经验。要实现它,通过 Google 可以得到很多有用的资料。为了简单期间,我选用 UMDF (User Mode Driver Framework) 作为开发驱动的基础。我个人认为,它有以下优势:

  • 驱动中的错误不会导致蓝屏死机,开发更容易
  • 可以用你熟悉的调试器,比如 VS2008 来调试代码,而无需内核调试器,调试更容易
  • 在此用例中性能不是关键需求,一点性能开销不会有任何问题。

这里的代码是从 WDK 7.1中的示例 UMDFSkeleton 修改而来的。我会对代码中的重点部分进行解释,然后说明安装流程。

>>> 阅读全文

 

, , , , , , ,

设备节点和设备栈

在 Windows 中,设备被表示为即插即用 (PnP) 设备树中的设备节点(device node)。通常当 I/O 请求被发送给设备时,多个驱动程序会帮助处理该请求。这些驱动程序中的每一个都与一个设备对象相关联,这些设备对象在栈中进行排列。设备对象的顺序与它们的关联驱动程序一起被称为设备栈(device stack)。每个设备节点都有自己的设备堆栈。1

设备节点和即插即用设备树
Windows 在称为“即插即用设备树”(或简称为“设备树”)的树结构中组织设备。 通常,设备树中的节点表示某个设备或者复合设备的某个独立功能。 不过节点也可以表示与物理设备无关软件组件。

设备树上的节点称为“设备节点”。设备树的根节点称为“根设备节点”。约定俗成地,根设备节点被绘制在设备树的底部,如下图所示。

设备树说明了 PnP 环境中固有的父/子关系。设备树中的一些节点用来表示连接有子设备的总线设备。例如,PCI 总线节点表示主板上的物理 PCI 总线。在启动过程中,PnP 管理器请求 PCI 总线驱动程序枚举连接到 PCI 总线的设备。这些设备被表示为 PCI 总线节点的子节点。在上图中,PCI 总线节点包含一些子节点,这些子节点用来表示连接到 PCI 总线的一些设备,其中包括 USB 主控制器、音频控制器以及 PCI Express 端口。

有些连接到 PCI 总线的设备也是总线。PnP 管理器请求这些总线中都枚举与之连接的设备。在上图中,我们可以看到音频控制器是连接有音频设备的总线,而 PCI Express 端口是连接有显示适配器的总线,该显示适配器则是连接有两个监视器的总线。

>>> 阅读全文

 

, , , ,

Windows 内部探秘:对象和对象管理器

内核需要维护有关众多资源的大量数据,如进程、线程、文件等等,为此内核使用了“内核数据结构”,即对象。从物理上看:每个内核对象只是内核分配的一个内存块,并只能给内核访问。从逻辑上看:该内存块是个数据结构,其数据成员维持有关该对象的信息。

在计算机术语中,“对象” 就是一个(或一组)数据结构以及定义于其上的若干操作,换句话说,想要对该数据结构有所动作,就必须通过定义的操作,别无它途。计算机历史上,“对象”这个概念出现于 Unix 之后,所以在 Unix 以及 Linux 中并没有明确的“对象”概念,Unix 把所有设备当成特殊文件,这已经是超越以往操作系统的一次飞跃,但是微软在设计 Windows 的时候,“对象”这个概念已经相当成熟,因此在设计中运用“对象”这个概念就成为顺理成章的事,所以这两种操作系统在设计理念上的显著不同在于:Unix 把设备看成是特殊文件, 而 Windows 把设备和文件都看成是对象。

对象和普通数据结构之间最根本的区别在于:对象的内部结构是被隐藏的,除了通过对象服务,否则无法直接读取或者改变对象的内部数据。请注意:为了兼顾可移植性,大部分操作系统代码都是用 C 语言编写的,因此在 Windows 内核中对象的“操作”与其数据结构的结合是松散的,并不具有封装、继承、多态等这些基本要素。

如果你编写 Windows 应用程序,你可能会遇到进程、线程、文件或者事件,这些都是对象的一些例子。反映到 Windows 内核中,任何一个进程都是进程对象类型的一个实例,文件是文件对象类型的一个实例,如此等等。这些对象又是以内核创建并管理的一些底层对象为基础的。这些顶层对象被称为“管理层对象 Executive Object”,底层的对象为“核心层对象 Kernel Object”。

核心层对象
核心层对象是在内核中实现的一些更加基础的对象,它实现了操作系统底层原语与机制。核心层对象对用户态代码是不可见的,只能在内核代码被创建与使用,核心层对象提供了操作系统的基础功能与机制比如同步、中断等等,在核心层对象之上再构建出管理层对象。每个管理层对象都包含一个或者多个核心层对象。

>>> 阅读全文

 

,

WDDM 编程与调试 (1) — GPU 架构概览

从 Vista 开始, Windows 的显示驱动全面采用新的编程框架,WDDM 即 Windows Display Driver Model。 WDDM 在 Windows 中之所以不可或缺,是因为 WDDM 与全新的桌面窗口管理器 DWM (Desktop Window Manager) 紧密相联。从技术的角度上来看, DWM 与 Mac OS X 中的 Quartz Compositor 类似。在 DWM 中,应用窗口不再像以前的 Windows 被直接绘制在屏幕上,而是经后台渲染后放入缓存中,再由 DWM 进行组合并最终呈现在屏幕上,即所谓“桌面组合”的概念。由于每个窗口分别在不同的视频内存区进行渲染,这使得 Aero 能够将窗口与桌面背景图像混合创建出类似霜冻玻璃之类的图形效果,为此,在设计上需要对每个窗口都使用图形加速,而不再仅限于 DirectX® 应用程序。

在 Vista 中,最终呈现操作是由 DWM 中的独立线程处理,而应用程序窗口的呈现则由窗口的 UI 线程负责操作。DWM 通过窗口列表,在树结构中管理各个窗口位图,然后将其组合到最终桌面。换言之,应用程序的主窗口线程呈现其场景,DWM 呈现线程对该场景进行访问,并通过其 DirectX 接口更新桌面。为了实现这一点,DWM 需要与 WDDM 通信,后者是图形处理器和显存的最终所有者。

WDDM 引入了显存管理器 ( VidMM — Video Memory Manager),可以在系统内存和显存之间进行交换。这意味着 WDDM 可以虚拟化显卡的资源,因而在共享和交换显存方面以及在线程间对 GPU 进行上下文切换方面可以做得更好。

WDDM 的版本随 Windows 有所不同:Windows Vista 支持 WDDM 1.0 ,而 Window7 支持 WDDM 1.1, Windows 8 支持 1.2版本。Windows 8.1 则将 WDDM 的版本更新到 1.3。目前最新的 windows10 中,WDDM 更新至 2.0 版本,将支持 DX12。

WDDM 的新变化
要了解 WDDM 有什么新变化,我们先看一下在 Windows XP 显卡驱动程序是如何工作的。对于 Windows XP 来说,绝大多数显示驱动程序都驻留在操作系统的内核空间,以便直接与图形硬件进行通讯,只有一小部分组件(如驱动程序中的 OpenGL) 留在了用户空间中且不直接访问硬件。这个模型的问题在于,操作系统内核空间的错误(包括驱动)可能会导致整个系统崩溃。图 1-1 是 ATI 显卡的 XP 驱动程序模型。

>>> 阅读全文

 

, , , , , , ,

Windows 下如何实现键盘记录器

首先强调的是本文不提供任何键盘记录器的源代码,我们所要侧重了解的是键盘记录器背后所涉及的系统机制,从而更好地了解 Windows 操作系统是如何与硬件协调工作的。本文适合那些对内核或驱动开发有经验的用户。1

在 Windows 中处理键盘输入数据
有多种技术可以截取键盘和鼠标事件,而键盘记录器或多或少都用到了这些技术。因此在剖析某款键盘记录器之前,有必要先了解一下 Windows 是如何处理键盘输入数据的。这个过程可以简单描述为:

  • 1. 键盘上某一个键被按下;
  • 2. 键盘系统中断控制器被激活;
  • 3. WM_KEYDOWN 消息出现;

有关内容可以参考:

  • “Apparatnoe obeshpechenie IBM PC” 作者 Alexander Frolov 和 Grigory Frolov, 第2卷, 第 1 册。 Dialog-MIFI出版社, 1992 年出版。第二章 “键盘” 介绍了键盘的工作原理、使用的端口和键盘硬件中断。
  • MSDN 上 “HID / Human Input Devices” 小节内介绍了键盘输入处理过程的底层部分(驱动)。
  • Jeffrey Richter 的著作 《Creating effective Win32 applications for 64-bit Windows》 第 27 章 “A model of hardware input and the local input condition” 中谈到了键盘输入处理过程的顶层部分(用户模式)。

作为物理设备的键盘是如何工作的。
大多数键盘都是独立设备,通过 PS/2 或 USB 接口连接到计算机上。有两个微控制器负责处理从键盘输入数据,i8042 在主板上,i8048 在键盘内。可以说 PC 键盘本身就是个小型计算机系统,其所使用的 i8048 微控制器独立于中央 CPU,负责不断地扫描被按下的按键。

每个按键都会被指定某一特定值,具体值和按键矩阵图强相关,而与按键上印刷的字符无关。这些数值被称为“扫描码”(这个名称凸显了计算机通过扫描键盘来搜索敲键的事实)。扫描码是 IBM 公司在为其 PC 创造出第一个键盘时选择的随机数。对单个按键来说,其扫描码并不是和 ASCII 码一一对应,相反它可以对应于多个 ASCII 码值。扫描码表可以在《汇编语言程序设计的艺术》一书的第 21 章中找到。

>>> 阅读全文

 

, , ,

Windows 内部探秘之线程

文章评价:
在当今的编程世界中,多线程已经成为 .NET、Java 或 C++ 等各种编程语言的一个必要组成部分。要编写具备高响应且可扩展的应用程序,就必须借、助多线程编程的力量。你也许用过 TPL、PLINQ、Task Factories、线程池、异步编程等等基础类库(Framework Class Libraries –FCL)进行过多任务处理,所有的这些在背后都依赖于 Windows 线程的力量来实现并行的场景。了解 Windows 线程的基本结构对于更好地运用和理解诸如 TPL、PLINQ 这些高级特性总是非常有帮助,并帮助你以可视化的方式了解在系统中多个线程是如何协同工作的,特别是当你在调试多线程应用程序的时候。在这篇文章中,我想和大家分享一些关于Windows线程的基础知识,它可以帮助您了解操作系统如何实现线程的。

Windows 线程由什么组成?
让我们看看 Windows 线程的一些基本组件。在 Windows 线程中有三个基本组件:

  • 线程内核对象
  • TEB

所有这三个组件一起创造了 Windows 线程。我会尝试逐一解释这些组件,但在这之前,让我们先对 Windows 内核和内核对象做简要介绍,因为这些都是Windows 操作系统最重要的部分

操作系统内核
内核是任何操作系统的主要组件。它是应用程序和硬件之间的桥梁。内核提供了抽象层,使得应用程序可以由此与硬件交互。

内核是操作系统最先加载的部分之一,它会一直保留在物理内存中。内核的主要功能是管理计算机的硬件和资源,让其他程序可以运行并使用这些资源。要更多地了解内核,请访问此链接

>>> 阅读全文

 

, ,

Windows 驱动开发(二)实现 IOCTLs

这是关于编写设备驱动程序系列教程的第二篇。我们会从上一篇谈到的东西继续下去。这一系列文章的主旨一点点打造编写设备驱动程序所需的知识。我们会利用上一篇文章的代码进行修改构建。在本文中,我们扩展这些代码以提供包括读取功能,处理输入/输出控制(也称为 IOCTL)并且更多地了解 IRP。

在开始之前,我先澄清一些常见问题。

哪里可以下载 DDK 或 WDK?
每一版 Windows 的发布,微软会同步推出驱动程序工具包,目前最新的是 WDK8,可以用来为 Vista, Windows 7 和 Windows 8 开发驱动。你可以在微软站点上下载该工具包。

我可以在驱动中使用 windows.h 头文件吗?
不要混用 Windows SDK 与 Windows DDK 的头文件。定义冲突会让代码编译变得十分困难。如果用户模式程序用到部分 DDK 的内容,通常我们会将需要的 DDK 或 SDK 的定义抽取出来放到源代码中。你还可以将 DDK 和 SDK 相关的文件隔离开来,每一个 .C 文件都可以包含正确的头文件而不会冲突。

可以用同样的方式实现某种特定类型的驱动吗?
这里谈的是适用大多数 Windows 驱动程序开发的通用框架。驱动程序不需要紧贴着硬件实现,而是通常有一个驱动程序栈。如果想实现某种特定类型的驱动程序,本文是了解驱动程序通常是如何工作的起点。后续的不同之处在于如何向系统宣告设别、 要实现哪些 IOCTL、 需要和哪些下层驱动进行通讯、以及其他一些要求实现的部分如配套驱动或用户模式组件。具体实现某种特定类型的驱动时,你应该在 MSDN 、DDK 多阅读一些与该类型驱动相关的内容。有些框架封装了我们在这里谈到的大部分内容,利用这些框架写起例子来会更简单。

>>> 阅读全文

 

, , ,

玩转 UEFI Secure Boot

对 Windows 8.0 的安全启动 (secure boot) 不少人都有困惑,但鲜有人真正了解它是如何工作的。我觉得理解它的最好方式就是从头开始配置安全启动,之后能就看清楚安全启动究竟带来了什么新功能。

首先要解决硬件问题。因为我手边缺少 UEFI 硬件以供测试。幸运的是 OVMF 开源项目提供了可供虚拟机使用的 UEFI 参考固件,它可以在 QEMU 中运行。EDK2 项目提供了编译好的二进制文件。但要支持安全启动,就需要自己动手重新编译。

我们需要的工具是 Intel 的 UEFI Build Tools,它是 EDK2 开发工具的一部分。 利用 svn 或者 git 可以检出 EDK2 源代码。

git clone git://github.com/tianocore/edk2.git

EDK2 随源代码还提供了最新的 Windows 版和 Unix 版的 BaseTools ,这是进行 UEFI 编程的一些辅助工具;如果选择在 Linux 上进行开发,你不得不选择重新编译 BaseTools,详情请参考 Ubuntu Wiki。1

这里假设 EDK2 源代码保存在 C:\edk2 目录中,后续的编译过程均以此为工作目录。你还需安装 iasl 和 Visual Studio 2012 Express 作为编译工具,isal 用于编译 ACPI 代码。此外还需要 Windows 8.0 SDK 或以上版本以及 .NET 4.5,该 SDK 提供了微软签名工具。

>>> 阅读全文

 

, , , , ,

在 Visual C++ 中使用嵌入汇编

文章评价:
如果你的应用对性能要求十分严苛,使用汇编语言来进行编程无疑是最佳选择。不过,编程技术发展到今天,代码动辄百万行,直接用汇编语言来进行编程,成本实在太高了。作为一种折中方案,你可以选择在部分代码里使用汇编语言 — Inline Assembly 以达到改善性能的目的。
 
所谓嵌入汇编 — Inline Assembly 也叫做内联汇编,即直接在 C/C++ 代码里加入汇编语言代码。同传统的汇编方式相比,我们可以完全避免那些烦琐的汇编和链接步骤,可以直接在汇编代码中使用 C 变量名和函数名 。这样一来汇编代码就能够很容易地 C 语言程序紧密而自然地结合在一起了。另外,由于是把汇编语句和 C 或者 C++ 语句混合在一起进行编程,我们还将可以实现许多原来光凭 C 或者 C++ 语句无法办到的事。GCC, Visual C++ 等都提供了各自的嵌入汇编的实现。在 Visual C++ 中使用嵌入汇编不需要额外的编译器和联接器,十分的方便。

在 VC++ 中,嵌入汇编主要用于如下场合:

  • 使用汇编语言写函数;
  • 实现那些对速度要求非常高的代码;
  • 设备驱动程序中用于直接访问硬件;
  • “Naked” 函数的 prolog 和 epilog。

所谓 Naked 的函数,VC++ 不帮着做栈处理,需要用户自己取输入参数、保护寄存器、甚至自己写 ret 等等,因此称为 “纯粹”的函数(asm 函数)。有关 Naked 函数可以参考: Rules and Limitations for Naked Functions

嵌入汇编代码的缺点也很明显:代码不易于移植,如果你的程序打算在不同类型的机器(比如 x86 和 ARM)上运行,应当尽量避免使用嵌入汇编。特别要注意,微软在 64 位 VC++ 上已经不再支持嵌入汇编。这时候你应该使用 MASM,因为 MASM 支持更方便的的宏指令和数据指示符。

嵌入汇编关键字
在 Visual C++ 中加入嵌入汇编是只需要使用 __asm 关键字,这点和 C++ 标准不同,VC 不支持 C++ 中的 asm 关键字,所以你需要使用 __asm(两个下划线)关键字来编写你的嵌入汇编代码。这个关键字有两种使用方法:

>>> 阅读全文

 

, , , ,

WDF USB设备驱动开发指南

USB 的全称是 Universal Serial BUS(通用串行总线), 它有两个重要的特点:串行数据传输、支持热拔插。从 1996 年 1 月至今,USB 已经经历了四个版本:1.0、1.1、2.0 和 3.0。其应用遍及各个领域, USB 采用主从结构。两个可以互联的设备,一定有主从之分。这导致了两台主机(主设备)或者两个 U 盘之间没有办法互联,只能在设备(U盘)和主机之间建立主从互联关系。

要成为 USB 主机,就一定有两种设备:USB 主控制器、USB 根集线器。主控制器用来处理根集线器上的数据,交给系统处理;根集线器用来连接多个外部设备。请注意:根集线器和普通 USB 集线器是不同的,普通 USB 集线器也是 USB 外部设备的一种,不是主机的组成部分。

我们在使用的电脑,每台电脑都有若干个控制器,控制器上有一个或多个根集线器,集线器上又对外暴露出一个或多个 USB A 型接口让外部设备连接。在设备管理器的视图模式选择 “按连接排序设备” 后,可看到和图 1-1 类似的”设备->集线器–>控制器->系统”层次结构。

最上层是 USB EHCI 接口的控制器,中间层是控制器上唯一的根集线器,最下层是连接在根集线器上的设备(包括普通集线器)。 每个 USB 控制器是一个 USB 族群的核心,其驱动程序负责为子设备分配总线地址。总线地址为 7 位宽,由于控制器自己占一个地址,故而最多可提供(2^7 -1 = 127)个子设备地址,也就是说,每个控制器上最多能连接 127 个子设备。并且这个数目包含了根集线器。

USB 电气特性和枚举
标准 USB 接口有 4 个金属针脚,对应着 USB 线中,就是 4 根金属线:两根电源线(5V的 VBus 和地线 GND )和两根数据线(D+ 和 D-)。这两根数据线实现了数据的差分传输,其实等于是一根线,但是提高了稳定性,这也就是“串行”之由来了。 USB 设备可以自己供电,也可以从总线获取电源,即通过 5V 电源线传过来的。像 U 盘、鼠键这类小型设备,5V 电源或者 100mA 电流足以满足其设备需求,故而多从总线获取电源;移动硬盘、打印机、USB 声卡这种较大设备,往往需要外置电源。

>>> 阅读全文

 

, ,

Previous Posts Next posts