11.2.2 Win32应用编程接口

Win32函数调用统称为Win32 API接口。这些接口已经被公布并且详细地写在了文档上。这些接口在调用的时候采用库文件链接流程:通过封装来完成原始NT系统调用,有些时候也会在用户态下工作。虽然原始API没有公布,但是这些API的功能可以通过公布的Win32 API来调用实现。随着新的Windows版本的更新,更多的API函数相应增加,但是原先存在的API调用确很少改变,即使Windows进行了升级。

图11-10表示出各种级别的Win32 API调用以及它们封装的原始API调用。最有趣的部分是关于图上令人乏味的映射。许多低级别的Win32函数有相对应的原始NT函数,这一点都不奇怪,因为Win32就是为原始NT API设计的。在许多例子中,Win32函数层必须利用Win32的参数传递给NT内核函数。例如,规范路径名并且映射到NT内核路径,包括特殊的MS-DOS设备(如LPT:)。当创建进程和线程时,使用的Win32 API函数必须通知Win32子系统进程csrss.exe,告知它有新的进程和线程需要它来监督,就像我们在11.4节里描述的那样。

一些Win32调用使用路径名,然而相关的NT内核调用使用句柄。所以这些封装流程包括打开文件,调用NT内核,最后关闭句柄。封装流程同时包括把Win32 API从ANSI编码变成Unicode编码。在图11-10的Win32函数里使用字符串作参数的实际上是两套API,例如参数CreateProcessW和CreateProcessA。当这些参数要传递到下一个API时,这些字符串必须翻译成Unicode编码,因为NT内核调用只认识Unicode。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

阅读 ‧ 电子书库
图 11-10 Win32 API调用以及它们所包含的本地NT API调用示例

因为已经存在的Win32接口很少随着操作系统的改变而改变,所以从理论上说能在前一个版本系统上运行的程序也能正常地在新版本的系统上运行。可在实际情况中,依然经常存在新系统的兼容性问题。Windows太复杂了以至于有些表面上不合逻辑的改动会导致应用程序运行失败。应用程序本身也有问题,例如,它们也经常做细致的操作系统版本检查或者本身就有潜在的问题只不过是在新系统上暴露出来了。然而,微软依旧尽力在每个版本上测试不同的兼容性问题,并且力图提供特定的解决办法。

Windows支持两种特殊环境,一种叫WOW。WOW32通过映射16位字符串到32位,来在32位x86系统用16位Windows 3.x应用程序。同样,WOW64允许32位的程序在x64架构的系统上运行。

Windows API体系不同于UNIX体系。对于后者来说,操作系统函数很简单,只有很少的参数以及很少的方法来执行同样的操作,从而可以有很多途径来完成同样的操作。Win32提供了非常广泛的接口和参数,常常能通过三四种方法来做同样的事情,同时把低级别和高级别的函数混和到一起,例如CreateFile和CopyFile。

这意味着Win32提供了一组非常多的接口,但是这也增加了复杂度,原因是在同一个API中糟糕的系统分层以及高低级别函数的混和。为了学习操作系统,我们仅仅关注那些低级别的函数封装了相关的NT内核的API的Win32 API。

Win32有创建和管理进程和线程的调用。Win32也有许多进程内部通信的调用,例如创建、销毁、互斥、信号、通信接口和其他IPC实体。

虽然大量的内存管理系统对程序员来说是看不见的,但是一个重要的特征是可见的:即一个进程把文件映射到虚拟内存的一块区域上。这样允许线程可以使用指针来读写部分文件,而不必执行在硬盘和内存之间具体的读写数据操作。通过内存映射,内存系统可以根据需求来执行I/O操作(要求分页)。

Windows处理内存映射文件使用三种完全不同的手段。第一种,它提供允许进程管理它们自己虚拟空间的接口,包括预留地址范围为以后用。第二种,Win32支持一种称作文件映射的抽象,这用来代替可定位的实体,如文件(文件的映射在NT的层次中称作section)。通常,文件映射是使用文件句柄来关联文件。但有时候也用来指向分页系统中的私有页面。

第三种方法是把文件映射的视图映射到一个进程的地址空间。Win32仅仅允许为当前进程创建一个视图,但是NT潜在的手段更加通用,允许为任意你有权限句柄的进程创建视图。和UNIX中的mmap相比,要区分开创建文件映射和把文件映射到地址空间的操作。

在Windows中,文件映射的内核态实体被句柄所取代。就像许多句柄一样,文件映射能够被复制到其他进程中去。这些进程中的任意一个能够根据需求映射文件到自己的地址空间中。这对共享进程间的私有内存是非常有用的,而且不必再创建文件来实现。在NT层,文件的映射(sections)也和NT名字空间保持一致,能够通过文件名来访问。

对许多程序来说,一个重要的领域是文件I/O操作。在Win32基本视图中,一个文件仅仅是一组有顺序的字节流。Win32提供超过60种调用来创建和删除文件和目录、打开关闭文件、读写文件、提取设置文件属性、锁定字节流范围以及更多基础操作的功能,这些功能基于文件系统的组织以及文件的各自访问权限。

还有更高级的处理文件数据的方法。除了主要的文件流,存在NTFS文件系统上的文件可以拥有额外的文件流。文件(甚至包括整个卷)可以被加密。文件可以被压缩成为一组相对稀疏的字节流,从而节省磁盘空间。不同硬盘的文件系统的卷可以通过使用不同级别的RAID存储而组织起来。修改文件或者目录可以通过一种直接通知的方式来实现,或者通过读NTFS为每个卷维护的日志来实现。

每个文件系统的卷默认挂载在NT的名字空间里,根据卷的名字来排列,因此,一个文件\foo\bar可以命名成\Device\HarddiskVolume\foo\bar。对于NTFS的卷来说,挂载点(Windows称作再分解点)和符号链接用来帮助组织卷。

低级别的Windows I/O模式基本上是异步的。一旦一个I/O操作开始,系统调用将允许线程对I/O操作进行初始化并且开始I/O操作。Windows支持取消操作,以及一系列的不同机制来支持线程和I/O操作完成之后的同步。Windows也允许程序规定在文件打开时I/O操作必须同步,许多库函数,例如C库和许多Win32调用,也规定I/O的同步已支持兼容性或者简化编程模型。在这些情况下,执行体会在返回到用户态前和I/O操作结束时进行同步。

Win32提供的另一些调用是安全性相关的。每个线程将和一个内核对象进行捆绑,称作令牌(token),这个令牌提供关于该线程的身份和权限相关的信息。每个目标可以有一个ACL(访问权限控制列表),这个列表详细描述了哪种用户有权限访问并且对其进行操作。这种方式通过了一种细粒度的安全机制,可以指定具体哪些用户可以或者禁止访问特定的对象。这种安全模式是可以扩展的,允许应用程序添加新的安全规则,例如限制访问时间。

Win32的名字空间不同于前面描述的NT内核名字空间。NT内核空间仅仅只有一部分对Win32 API函数可见(即使整个NT名字空间可以通过Win32使用特殊字符串来访问,如“\\.”)。在Win32中,文件访问权限和驱动器号相关。NT目录\DosDevices里包含了对一个从驱动器号到实际设备对象的数个符号链接。例如,\DosDevices\C:是指向\Device\HarddiskVolume1。这个目录同样也包含了其他Win32设备的链接,如COM1:、LPT1:和NUL:(端口号和打印端口,以及非常重要的空设备)。\DosDevices是一个真正指向\??的链接,这样有利于提高效率。另外一个NT文件夹,\BaseNamedObjects用来存储各种各样的内核对象,这些文件可以通过Win32 API来访问。这些对象包括用来同步的对象,如信号、共享内存、定时器以及通信端口,MS-DOS和设备名称。

对于底层系统接口,我们额外说一下,Win32 API也支持许多GUI操作,包括系统所有图形接口的调用。有对窗口的创建、摧毁、管理和使用的调用,以及支持菜单、工具条、状态栏、滚动条、对话框、图标和许多在屏幕上显示的元素。Win32还提供调用来画几何图形、填充、使用调色板、处理文字以及在屏幕上放置图标等。也支持对键盘鼠标和其他输入设备的响应,如音频、打印等其他输出设备。

GUI操作直接使用win32k.sys驱动,这个驱动使用特殊的函数从用户态去访问内核态的接口。因为这些调用不包含NT操作系统中的系统调用,我们将不会详细讨论。