Electronic Joint Business

Solution for E-Business

windows

受保护进程的演进 <1>: Windows 8.1 如何抵御哈希传递攻击

6年多前我第一次发文讨论受保护进程的概念—《为什么受保护进程糟糕透顶》1,该标题已经清楚透露了我对这个未经深思熟虑的 DRM 方案的看法。显然之后微软对其内部机制进行了长考(的确,抛开 DRM,一个难以渗透的用户态进程在安全领域也是个有趣的课题),又创造了一类新进程:Protected Process Light,内核中通常缩写为 PPL。

和其笨重的兄弟(受保护进程)不同,Protected Process Light 作为安全屏障,为 Windows 平台带来了三种可以有效抵御威胁的安全增强。在接下来的系列文章里2,我们将会看到这些安全增强是如何实现的,本文先从如何抵御哈希传递攻击(Pass-the-Hash)3开始。

我们将涉及 LSASS 4 在 Windows 安全模型中的作用,然后讨论新的 PPL 模型背后的技术细节。要涵盖新的安全改进,我们也不得不涉及一些内部相关领域,所以我们也会对安全启动和保护变量稍加讨论。不过最重要的是,我们还将了解如何启用设置来抵御 PtH ,因为对于非 RT 的 Windows,该功能默认是禁用的。

LSASS 进程
Windows 使用众所周知的算法(NTLM)对本地用户帐户进行哈希运算并将结果保存到称为 SAM (安全帐户管理器) 的数据库中,SAM 本身是注册表的一个配置单元文件。和其他操作系统一样,总是存在各种脱机或在线攻击,目的不外是为了获取、重置,甚至重用保存在 SAM 中的哈希值,例如常见的”密码重置”应急启动盘,或者是恶意的权级提升等等。此外,还有其它一些加密数据存储在 SECURITY 数据库(也称为 LSASS 策略数据库),这是另一个注册表配置单元文件。该数据包括密文(secrets,如 LSA 密文信息),保存的纯文本密码等等。

Windows 通过被称为本地安全权威子系统(LSASS Local Security Authority Sub-System)的进程管理着这些信息的运行时状态,并最终负责所有登录操作 (包括远程登录到 Active Directory)。因此,为了要获得对这些数据的访问,主要通过用两种机制:

>>> 阅读全文

 

,

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

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

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

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

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

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

>>> 阅读全文

 

, ,

Windows 下的多线程同步机制

广义上的进程是程序在计算机上的一次执行活动。当运行一个程序,你就启动了一个进程。显然,程序是静态的,进程是动态的。进程也称为任务。在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。多任务带来的好处是明显的,比如你可以边听音乐边上网,而这些任务之间丝毫不会相互干扰。

对计算机来说,原则上一个 CPU 只能分配给一个进程,以便运行这个进程。而对于只有一个 CPU 的计算机来说,要想同时运行多个进程,就必须使用并发技术。对于并发技术最容易理解的是“时间片轮转进程调度算法”,即在操作系统的管理下,所有正在运行的进程轮流使用 CPU,每个进程允许占用 CPU 的时间非常短(比如 10 毫秒),这样用户根本感觉不出来 CPU 是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任一时间点有且仅有一个进程占有 CPU。如果一台计算机有多个CPU,情况就不同了,如果进程数小于 CPU 数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行。但如果进程数大于 CPU 数,则仍然需要使用并发技术。

狭义上的进程是操作系统的基本执行单位。在 Windows 中,线程是基本执行单位,而进程是一个容纳线程的容器。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间、文件描述符和信号处理等等。但同一进程中的多个线程各自拥有有名义上私有的调用栈(call stack)、寄存器上下文(register context) 和线程本地存储(thread-local storage)。

由于同一进程内的所有线程共享全部系统资源,所以线程间通信不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。

与多进程相比,多线程具有以下特点:1

>>> 阅读全文

 

,

编写虚拟 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 不一样。

>>> 阅读全文

 

, , ,

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的?以下表格列出了各函数:

>>> 阅读全文

 

, , ,

优化Cyberlink PowerDVD 10* 以提高电池续航时间

电池续航时间不足是困扰移动设备和超级本(包括平板)用户的诸多严重问题之一。用户已经习惯从云端的内容服务器“按需”获取流媒体到移动设备当中。由于这些设备的电池容量有限,提高能源效率凸显重要。 Cyberlink PowerDVD 10* (PowerDVD*) 是业界顶尖的高清电影和 3D 电影播放软件之一。该应用也是 OEM 最经常预捆绑的软件之一。在本案例研究中,我们将展示英特尔是如何与 Cyberlink 合作,使 PowerDVD 在 Intel 设备上可以提供同类最佳的使用体验。1

首先,我们将讨论 Cyberlink 在 PowerDVD 中加入流媒体功能时遇到的挑战,以及 Intel 用来改善 PowerDVD 功耗所使用的工具。

接着,我们将讨论 Cyberlink PowerDVD 流媒体应用对电力消耗的影响概况以及对移动设备的电池续航时间的影响。我们还会对 PowerDVD 的行为进行分析以找出症结,如 CPU 解码、大量的上下文切换、 高中断频率等,这些都会导致功耗的增加。最后,我们会用数据来显示优化后所减少的电力消耗。

这次优化是一次巨大的成功。 Intel 团队在以下方面对 PowerDVD 进行了诸多改善:

  • 在播放媒体时 Package C0 从 100% 减少到 20%
  • 借助 Intel Power Gadget SoC 功耗从约 6 瓦减少到约 1.8 瓦
  • Intel VTune analyzer 所报告的 CPU 占用率从 70% 减少到 25%
  • Windows 性能分析工具显示 frequent wakeups (5 Msec) vs. 10 msec wake up frequency for local or streaming media playback frequency of 10%.

一些缩写

>>> 阅读全文

 

,

为 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 服务。

>>> 阅读全文

 

, , , ,

.NET 编译平台 (Roslyn) 概述

文章评价:
一直以来,编译器都按黑盒的方式运作 — 这头放入源代码,中间部分施展魔法,在另一头就会生成目标文件或程序集。当编译器施展魔法时,它建立了对代码的深刻理解,但这些知识除了实现编译器的巫师外,旁人无法掌握。一旦生成编译输出,这些信息就被彻底忘却。几十年来,这种方式一直运作良好,但现在却不能满足需要了。

如今为了提高生产力, 人们越来越依赖于集成开发环境(IDE)所提供的智能感知、重构、智能重命名、 “查找所有引用” 和 “转到定义” 等功能。我们用代码分析工具来改善代码质量,用代码生成器来协助代码构建。想要让这些工具更聪明,就需要它们能更多地访问编译器,同时也需要对编译器中深厚的代码处理知识有更多的了解。

这就是 .NET 编译器平台(“Roslyn”)的核心使命:打开黑盒让工具和用户可以分享编译器处理代码时的大量信息。与 “源代码—目标文件”的直译方式不同,Roslyn 使编译器成为平台,这意味着工具和应用程序可以通过 API 来处理代码相关的任务。

将编译器过渡到平台,这大大降低了构建代码相关(code focused)工具与程序的门槛,在许多方面推动了创新,包括:元编程、代码生成、代码转化、C#\VB 的交互式使用、领域特定语言内嵌 C#\VB 等等。

在 .NET 编译器平台 (“Roslyn”) SDK 预览版中包括了最新的语言对象模型,可用于代码生成、代码分析与重构。我们还包括了一些 API 草稿,以便在后续的版本中实现支持脚本、C#\VB的交互式使用。本文是对 .NET 编译器平台(“Roslyn”)的概念性描述,更多情况可以参考预览版 SDK 中的演示与示例。

>>> 阅读全文

 

, , , , , , , ,

基于 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 端口是连接有显示适配器的总线,该显示适配器则是连接有两个监视器的总线。

>>> 阅读全文

 

, , , ,

并行程序中的同步与通讯

在任何程序中,总会有些执行阶段要等待操作系统或者其他进程完成 I/O 操作或者同步操作。这时候,程序就无法处理其它工作,因为实际上是操作系统或者其它进程占用了宝贵的 CPU 时间。这就是滥用文件读/写操作的程序会如此之慢的原因。1

这时候进程不得不等待操作系统服务例程将 CPU 从其他进程那里释放出来。这种等待也被称为进程处于阻塞状态。诸如 EDA 这类串行计算,整个算法作为单个任务被执行,如果该进程在等待任一种阻塞操作,整个算法就会完全停顿。在处于阻塞状态的这段时间里,另一个进程占用了 CPU 的所有权,所以算法根本没办法进行。

并行程序提供了一种手段,能在同一计算中调用另一个进程以提高 CPU 利用率,当一个进程被阻塞的时候(如等待 I/O 操作或者同步操作),另一个进程则继续处理共同工作的其它部分。这种情况在单 CPU 或者多 CPU 机器上皆可发生。

反之,对于串行程序(即非并行算法)来说,而无论有多少可用的 CPU ,在任何情况下都只能用到单个 CPU。除非有编译器将源程序从串行转成并行,不过能实现这一目标的实属特例。 因此如果想同时利用多个 CPU ,编码时要手工运用某种并行技术。(在源代码中采用特殊指令,如 OpenMP)。

并行程序能将工作划分为较小的部分,因此多个进程可以一起工作并互相协作以便能在更短的时间内计算出同样的结果。这时,在等待 I/O 设备完成操作的同时,并行程序能可以继续处理另一部分工作。此外,并行程序还可以同时使用系统所有可用的 CPU。

>>> 阅读全文

 

, , , , , , ,

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

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

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

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

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

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

>>> 阅读全文

 

,

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

3D 图形处理主要目的是把 3D 场景变成一个 2D 图像。这里涉及的主要是几何学,主要需要解决的问题是坐标变换(Transform)、裁剪(clipping)和投影(lignting), 简称 T&L 或 TCL:

  • 坐标变换(Transform):即将一个 3D 物体变化为 2D 图像,基本做法是投影,包括透视投影和垂直投影。 其实这里面还会涉及把物体的“物体坐标”转换成世界坐标,再转换成“相机坐标”等。这涉及一系列矩阵变换。
  • 裁剪(clipping):3D 物体投影到 2D 平面上后,或是投影过程中,发现有一些部分是不应该被看见的,这时需要根据遮挡信息等把他们裁掉。
  • 光源(lighting):光把 3D 物理投影到 2D 平面并不能给人们带来足够真实感,因为同样的几何体,由于光照角度不同,材质不同,可能会显示出非常不一样的颜色,人眼需要这种颜色或灰度变化来区分物体间的位置关系。

一、3D 图形处理和 GPU 架构发展
Nvidia 在 1999 年推出的 GeForce256 第一次提出了 GPU 的概念,它第一次完整实现了整个渲染管线(pipeline). 这个时期使用 AGP 替代 PCI 传输数据,并且增加了诸如硬件 T&L、立方环境材质贴图、顶点混合、纹理压缩和凹凸映射贴图、双重纹理四像素、256 位渲染引擎等诸多先进技术。而硬件 T&L (Transform & Lighting 坐标转换和光源处理)可以说是 GPU 问世的标志。这个时期实现的渲染管线就是熟知的 fixed function pipeline,即固定管线渲染。从此 3D 游戏中坐标和光源的转换工作就由 CPU 转交给了 GPU。

渲染管线也叫渲染流水线,是显卡芯片中和图形信号处理单元相互独立的并行处理单元,渲染管线提高了显卡的工作能力和效率,传统的一条渲染管线由 PSU(像素着色单元 Pixel Shader Unit)、TMU (纹理贴图单元,Texture Mapping Unit) 和 ROP (光栅化引擎 Raster Operations Pipeline)三部分组成,PSU 负责像素处理,TMU 负责纹理渲染,而 ROP 负责最终的像素输出,用公式表示为 PS = PSU + TMU + ROP。一条完整的渲染管线意味着在一个时钟周期里至少进行一次 PS 计算,并输出一次纹理。

接下来的重大革新则是 DirectX 8.0 引入了着色器 (Shader) 。着色器就是一段可以改变顶点和像素的小程序,可被加载并在 GPU 上运行,它替代了传统的固定渲染管线参与到流程中,可以实现 3D 图形学中的相关计算。同硬件 T&L 仅能实现的固定光影转换相比,Shader 的灵活性更大,它使 GPU 真正成为了可编程的处理器。例如它可以只让特定的蓝色材质发生移动、扭曲和倒映,这样就产生波光粼粼的水波效果。

早期的着色器有两种:顶点着色器(Vertex Shader,在 OpenGL 中称为 Vertex Program),它取代固定渲染管线中坐标变换和光照部分,可以用于控制顶点变换和投影变换等,另一种称为像素着色器(Pixel Shader,OpenGL 称为 Fragement Program),用来取代固定渲染管线中的光栅化部分,可以用于控制像素颜色和纹理采样。换句话说,着色器就是一段由 GPU 执行的用于对 3D 对象进行操作的程序。像素着色器和顶点着色器都是 ShaderModel 中的以一部分,而 ShaderModel 可以理解为 GPU 的渲染指令集。事实上,着色器与汇编语言非常相似,只不过它是运行在 GPU 上的。1此后每逢 DirectX 有重大版本更新时,ShaderModel 也会相应的升级版本,技术特性都会大大增强。

>>> 阅读全文

 

, , , , , , , ,

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 操作系统最重要的部分

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

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

>>> 阅读全文

 

, ,

Previous Posts Next posts