Wine 架构
文档来源：http: //www.winehq.org/site/docs/winedev-guide/index
中文翻译：李梦卓，姜长龙
        Chapter 7. Overview        第七章 Wine 架构概述

        7.1. Wine Overview        Wine 架构概述
    随着 Wine 基础架构稳定下来，大家希望我们应该很快发布这篇文章，也是时候应该看看 Wine 究竟是如何工作的了。

        7.1.1. Foreword        序言
    Wine 是“Wine Is Not an Emulator”的缩写。有时也被认为是“Windows Emulator”的简写。从某种角度来说，两种意义都是正确的，取决于所处的视角。第一种意义是要说明 Wine 不是虚拟机，它不是模拟一个 CPU ，也不支持 Windows 安装和 Windows 设备驱动；Wine 是 Windows API 的一个实现，它能够被当作一个用来迁移 Windows 应用程序到 Unix 平台的库来使用。第二种意义很明显是针对二进制文件来说的(.exe)，Wine 看上去像 Windows，并尽量接近地模拟它的行为和一些古怪的特点。
    “模拟器”：从模拟器地角度看，它不是像想象的那样，是一个典型的低效率的模拟层，这就意味着 Wine 除了慢一无是处，还热衷于 Windows API 拙劣的设计当然也可能在某些方面造成额外开销。但是在 Wine 所运行的高效的 Unix 平台上这两方面都得到了平衡，而且对于 Wine 来说这些可能存在的抽象库(Motif,GTK+,CORBA,etc)都有一个合理的运行开销。

        7.1.2. Executables      可执行程序
    Wine 的主要的任务是在非 Windows 操作系统上运行 Windows 可执行程序。它也支持其他不同类型的可执行程序。
    DOS 可执行程序。这都是相当古老的使用 DOS 格式的程序（.com 和 .exe ，后来 .exe 被叫做 MZ ）。
    Windows NE 可执行程序，它也被称作“16 bit”。是运行于 Windows 2.X 和 3.X 平台上的本地程序。NE 代表 New Executable 。
    Windows PE 可执行程序。这种程序是在 Windows 95 提出的（后来成为 Windows 后续版本的 native formats －本地格式），它也支持 16 bit 的应用程序。PE 代表 Portable Executable ，在这里 Portable 从某种意义上是说，可执行程序（作为一个文件而存在）的格式是不依赖 CPU 的（即使文件内容－代码－是依赖 CPU 的）。（ PE 表示可以用在各个 Windows 平台上，而不是其他 OS 上。）
    Winelib 可执行程序。这些应用程序是用 Windows API 写成的，但却是作为 Unix 可执行程序编译的。Wine 提供这样的工具，用来创建这样的可执行程序。
    我们快速浏览一遍 Wine 所支持的这些可执行程序的主要区别。
    表 7-1 。Wine executables

	 	DOS(.COM 和 .EXE) 			 	Win16 (NE) 	
多任务 	 	某一时刻只运行一个程序（TSR 除外） 	 	分时 		
地址空间 	1 MB 内存空间，所有程序在其中加载和卸载 	保护模式，所有16位应用程序公用一个单独的地址空间  	
Windows API 	无，有 DOS API（如： Int21h中断） 		可以使用 16 位Windows API 	
Code(CPU level) 仅在x86实时模式有效，代码和数据分段存储， 	仅在IA32架构有效，代码和数据分段存储，16位地址， 	 			16位地址，CPU处于实时模式			及命名，CPU处于保护模式
多线程 		不支持						不支持
	 	支持 	

	 	Win32 (PE)      					Winelib
多任务	 	抢占 							抢占
地址空间 	每一个程序都拥有独立的地址空间，需要CPU支持MMU		每一个程序都拥有独立的地址空间，需要CPU支持MMU
Windows API	可以使用 32 位Windows API				可使用32位Win API也可使用 Unix API
Code(CPU level) 包括IA32架构在内的若干 CPU都有效(NT系列)，		平面内存模型，32位地址空间。
		平面内存模型，32位地址，及命名
多线程		支持							Win32API支持多线和同步，Unix不支持

    Wine 处理这些不同的格式是通过为每一个 Win32 进程调用一个独立的 Wine 进程（实际上是一个 Unix 进程）实现的，但对于 Win16 任务不这样处理。所有的 Win16 任务都是作为不同的 Unix 线程运行的，这些线程也都属于同一个 Wine 进程，并在进程内部保持同步；这个 Wine 进程就是声名狼籍的 WOW (Windows on Windows) 进程，这和 Windows NT 所用的机制是一样的。
    在 WOW 进程中的 Win16 任务之间的同步机制是通过 Win16 互斥量控制的，保证在任意时刻只有一个在运行。当一个任务希望其他任务运行时，线程就释放互斥量，然后一个等待运行的线程就开始执行了。
    winevdm 是一个专门用于运行 Win16 程序的 Wine 进程，这个程序可以同时存在几个实例。Windows 为了实现几个 Win16 程序同时运行在不同的地址空间里，而支持 VDM (Virtual Dos Machinese) ；Wine 也用同样的方式运行 DOS 程序，从这个角度说，Wine 提供的 DOS 模拟器仅是一个叫做 winedos 的 DLL 。

        7.2. Standard Windows Architectures        标准 Windows 架构

        7.2.1. Windows 9x architecture        Windows 9x 架构
    Windows (Win 9x) 架构大概是这样的：

    +---------------------+   \
    | Windows EXE |            } application
    +---------------------+   /

    +---------+ +---------+   \
    | Windows | | Windows |    \  application & system DLLs
    |   DLL   | |   DLL   |    /
    +---------+ +---------+   /

    +---------+ +---------+   \
    |  GDI32  | | USER32  |    \
    |    DLL  | |   DLL   |     \
    +---------+ +---------+      } core system DLLs
    +---------------------+     /
    |    Kernel32 DLL     |    /
    +---------------------+   /

    +---------------------+   \
    |   Win9x kernel      |    } kernel space
    +---------------------+   /

    +---------------------+   \
    | Windows low-level   |    \ drivers (kernel space)
    |      drivers        |    /
    +---------------------+   /

        7.2.2. Windows NT architecture        Windows NT 架构
    Windows (Win NT) 架构如下图所示。新的 DLL (NTDLL) 可以运行不同的子系统（如：Win32）；在 NT 架构下 kernel32 是运行在 NTDLL 之上的 Win32 子系统。

    +---------------------+                \
    |   Windows EXE       |                 } application
    +---------------------+                /

    +---------+ +---------+                \
    | Windows | | Windows |                 \ application & system DLLs
    |   DLL   | |   DLL   |                 /
    +---------+ +---------+                /

    +---------+ +---------+ +-----------+  \
    |  GDI32  | |  USER32 | |           |   \
    |   DLL   | |    DLL| | |           |    \
    +---------+ +---------+ |           |     \ core system DLLs
    +---------------------+ |           |     / (on the left side)
    | Kernel32 DLL        | | Subsystem |    /
    | (Win32 subsystem)   | |Posix, OS/2|   /
    +---------------------+ +-----------+  /

    +-----------------------------------+
    |             NTDLL.DLL             |
    +-----------------------------------+

    +-----------------------------------+   \
    |             NT kernel             |    } NT kernel (kernel space)
    +-----------------------------------+   /

    +-----------------------------------+   \
    |      Windows low-level drivers    |    } drivers (kernel space)
    +-----------------------------------+   /

    注：（上图未提及）16 位应用程序被当作一个特殊的子系统支持的。Win9x 和 NT 架构存在一些根本的不同，包括：
    ＊ 几种子系统 (Win32,Poxix...) 可以运行在 NT 上，Win9x 不行
    ＊ Win9x 植根于 16 位系统，而 NT 是纯 32 位系统
    ＊ 驱动模块和接口也是不同的（即使 MS 试图在 Win98 及更新的系统中借助 WDM 模型来弥合差异）

        7.3. Wine architecture        Wine 架构

        7.3.1. Global picture        架构图
    Wine 的结构很接近 Windows NT ，尽管一些子系统还没有实现（要记得对 16 位的支持是以 32 位 Windows EXE 方式实现的，而不是一个子系统），下面是整个结构图。
    
    +---------------------+                          \
    |     Windows EXE     |                           } application
    +---------------------+                          /

    +---------+ +---------+                          \
    | Windows | | Windows |                           \ application & system DLLs
    |  DLL    | |   DLL   |                           /
    +---------+ +---------+                          /

    +---------+ +---------+ +-----------+ +--------+ \
    |  GDI32  | |  USER32 | |           | |        |  \
    |   DLL   | |   DLL   | |           | |  Wine  |   \
    +---------+ +---------+ |           | | Server |    \ core system DLLs
    +---------------------+ |           | |        |    / (on the left side)
    |     Kernel32 DLL    | | Subsystem | | NT-like|   /
    |  (Win32 subsystem)  | |Posix, OS/2| | Kernel |  /
    +---------------------+ +-----------+ |        | /
                                          |        |
    +-----------------------------------+ |        |
    |         NTDLL                     | |        |
    +-----------------------------------+ +--------+

    +-----------------------------------+            \
    |  Wine executable (wine-?thread)   |             } unix executable
    +-----------------------------------+            /
    +----------------------------------------------+ \
    | Wine drivers                                 |  } Wine specific DLLs
    +----------------------------------------------+ /

    +-------------+ +-------------+ +--------------+ \
    |    libc     | |   libX11    | |  other libs  |  } unix shared libraries
    +-------------+ +-------------+ +--------------+ /     (user space)

    +----------------------------------------------+ \
    |  Unix kernel (Linux,*BSD,Solaris,OS/X)       |  } (Unix) kernel space
    +----------------------------------------------+ /
    +----------------------------------------------+ \
    | Unix device drivers                          |  } Unix drivers (kernel space)
    +----------------------------------------------+ /

    Wine 至少要能代替三大 dlls(KERNEL/KERNEL32,GDI/GDI32,USER/USER32)，这是其它所有dlls的基础。但是由于各种原因，Wine 在实现过程中倾向于使用 NT 方式，所以 NTDLL 是另一个 wine 要实现的核心的 dll，许多 KERNEL32 和 ADVAPI32 的特性通过 NTDLL 来实现。
    迄今为止，除了 Win32 Wine 没有实现一个真正的子系统。
    Wine server 为核心 dlls 的实现提供了中枢，它主要实现进程间同步和对象共享。从功能的角度来说，可以将它看作一个 NT kernel（尽管 Wine 的 dll 和 Wine server 之间使用的 APIs 和协议是由 Wine 定义的）。
    Wine 使用 unix 驱动来访问各个硬件，然而在某些情况下，Wine 提供驱动（Windows意义上的）来访问物理硬件设备。这个驱动是 unix 驱动的代理（例如，图形部分的 X11 或 SDL 驱动，声音部分的 OSS 或 ALSA 驱动等)。
    所有 Wine 提供的 dlls 尽可能的和 Windows 平台导出的 APIs 一致。极少有例外的情况，如果有则会有文档说明（Wine dlls 导出一些 Wine 特定的 APIs）。通常，这些会添加前缀 __wine 。
    下面让我们更进一步的了解一下这些组成部分。

        7.3.2. The Wine server        The Wine server
    Wine server 是 Wine 中最令人头疼的概念之一。它有什么功能呢？简单来说，它提供进程间通信(IPC)，同步，进程/线程管理。当 Wine server 启动，它会为基于用户目录 .wine （或使用 WINEPREFIX 环境变量所指）的当前用户生成一个 unix socket，所有随后产生的 Wine 进程都通过这个 socket 连接到 Wine server 上。如果 Wine server 没有运行，第一个 Wine 进程将会以自动终止的模式启动 Wine server （也就是说，当最后一个 Wine 进程结束时 Wine server 将会自动终止）。
    Wine 的早期版本中上面提到的主 socket 是在配置目录中生成的，不管你的用户目录的 .wine 子目录还是使用 WINEPREFIX 环境变量指定。因为在 /tmp 目录中，用一个反映配置目录的名字创建一个 socket 是不可能的。这就意味着可能有多个不同的 Wine server 运行着，每一个都是用户和配置目录的结合。注意使用同一配置目录同时不该有多个用户，它们将导致多个不同的 Wine server 副本运行，这就带来了共享的注册信息的问题。
    在每个 Wine 进程中的每个线程都有它自己的请求缓存区，和 Wine server 共享。当一个线程需要和别的线程或进程同步或通信时，它填充请求缓存区，然后通过 socket 写命令代码。Wine server 酌情的处理命令，而客户线程等待回应。某些情况下，像各种各样的 WaitFor??? 同步指令，当等待条件被满足之前服务器不会发送回应给它,而是让其继续等待。
    Wine server 本身是一个单独的 Unix 进程，没有它自己的线程，而是建立在大量的 poll() 循环之上的。当有事情发生时，如客户端发送命令，或等待条件被满足，它会提醒 Wine server 。这样 Wine server 自身没有竞争的危险－它通常被调用进行一些对于它的客户端来说看起来相当微小的操作。
    因为 Wine server 需要管理进程，线程，共享处理，同步和有关的任意东西，所有的 Wine 32 客户对象也都由 Wine server 管理，当客户端需要知道 win32 对象处理相关的 Unix 文件描述符时必须发送请求（这种情况下 Wine server 复制文件描述符，传递给客户端，当客户端使用完该复制时由客户端关闭它）。

        7.3.3. Wine builtin DLLs: about Relays, Thunks, and DLL descriptors
        Wine 内建 DLLs：传递，替换程序和 DLL 描述符
    这部分主要关于 builtin DLLs(Wine 提供的 DLLs )，下一节 Wine/Windows DLLs（7.4.3 部分）介绍 native 和 builtin DLL 处理的细节。
    载入一个 Windows 二进制文件到内存中并不那么难，难的是要处理各种各样的 DLLs，和它们导入导出的入口点，这些入口点既要在指定的地址，也要提供指定的功能；显然，这就是 Wine 要做的。Wine 实现了一定数量的 DLL。可以在 DLLs/ 目录中找到这些 DLLs 的实现。
    每个 DLL（至少是 32 位版本的，见下文）以一个 Unix 共享库的方式被实现。而这个共享库的文件名就是 DLL 模块名加上 .dll.so 后缀（或者是 .drv.so 或其它基于 DLL 类型的相关扩展名）。该共享库除了包含 DLL 自身的代码，还有一些其它的信息，如 DLL 资源和 Wine 定义的 DLL 描述符。
    当 DLL 被实例化时，DLL 描述符被用来生成一个内存中的 PE 头，通过它可以访问 DLL 的各种信息，不仅限于入口点，还包括资源，各个段，调试信息等。
    DLL 描述符和入口点表由工具 winebuild（以前只叫做 build）生成，带有 .spec 扩展名的 DLL 定义文件作为输入。资源（通过 wrc 编译的）或消息表（wmc 编译的）也被 winebuild 添加到描述符。
    当一个应用程序想载入一个 DLL，Wine 将：
        * 首先查看已注册的 DLLs 列表（事实上，包括已经载入的 DLLs 和注册成为 DLL 描述符的已经载入的共享库－.dll.so）。当共享库被载入时，DLL 描述符会被自动注册，注册调用被放在共享库的构造中，运行一个 Wine 进程时使用 PRELOAD 环境变量可以强制一些 DLL 描述符的注册。
        * 如果没有被注册，Wine 将在磁盘上查找由 DLL 模块名构造的共享库。查找路径由 WINEDLLPATH 环境变量定义。
        * 如果还是没有，Wine 将查找真正的 Windows .dll 文件，然后调用 native DLLs 加载过程。
    在找到 DLL 后（假定它是 native 的），将使用 dlopen() 调用将其映射到内存中，注意，Wine 不使用共享库机制来解析和／或导入两个共享库（两个 DLL）之间的功能。共享库只用来根据需求提供载入代码的方法。通过 DLL 描述符，这段代码，将提供和一个 native DLL 相同类型的信息。这样 Wine 就可以用这段适用于 naitve 和 builtin DLL 的代码来处理导入和导出。
    在 Wine 中使用一些技巧注册 DLL 描述符。winebuild 工具在生成 DLL 描述符时也生成了构造函数，在共享库被载入到内存中时会被调用。该构造函数将描述符注册到 Wine DLL 载入器中。这样，在 dlopen 调用返回之前，将获得 DLL 描述符并注册它。这也有助于在不同共享库间存在静态依赖关系（在 ELF 共享 lib 层次上，而不是内置的 DLL 层次上）的情况：而内置的 DLL 会被恰当的注册，（从 Windows 的角度来说）并正确的载入。
    由于 Wine 自身是 32 位的，如果编译器支持 Windows 调用约定，stdcall（gcc是这样），Wine 直接用 Wine 处理程序的地址替换 win32 代码的输入，而两者之间不存在任何的转换层。这就减少了很多人会想到的“模拟”的花费，也是应用程序所期望的。
    然而，如果用户定义了 WINEDEBUG=+relay，在应用程序的输入和 Wine 的处理程序之间就插入了一个转换层（事实上是修改了 DLLs 的导出表，一个转换部分被插入到表中）；这一层被叫做 relay（传递）因为它的工作就是打印参数和返回值（通过在DLL 描述符入口点表中使用参数表），然后传递调用，但是这个对于调试错误的调用到 Wine 中的代码是没有意义的。在 Windows DLLs 之间也存在一个相似的机制－从而 Wine 可以通过 WINEDEBUG=+snoop 在它们之间有选择的插入转换层，但是由于非 Wine 的 DLLs 没有 DLL 描述符，这样做不太可靠，可能会导致崩溃。
    对于 win16 代码，没有办法转换。Wine 需要在 16 位和 32 位之间传递。这些转换在 app 的 16 位栈和 Wine 的 32 位栈之间切换，准确的复制和转换参数（一个 int 在 16 位系统下是 16 位的，在 32 位系统下是 32 位的，在 16 位的情况下指针是段形式的，而在 32 位情况下是 32 位的线性值），处理 Win16 的互斥标记。在转换过程中有一些不错的控制，可以参考 winebuild 手册看细节。简单来说，栈的复杂性使这个处理过程不易理解，不适合初学者学习。
    基于每个 16 位的 DLL 也会生成一个 DLL 描述符。然而，这个 DLL 通常和一个 32 位的 DLL 是成对的。它或者是16位 DLL 的 16 位对应部分（如 KRNL386.EXE 和 KERNEL32，USER 和 USER32），或者直接是一个链接到 32 位 DLL 的 16 位 DLL（如 SYSTEM 和 KERNEL32，DDEML 和 USER32）。这些情况下，16 位的描述符被插入到和相应的 32 位 DLL 一样的共享库中。Wine 将在 Kernel32.dll.so 和 system.dll.so 之间创建符号链接，这样无论是载入 Kernel32.dll 或 system.dll 都将结束于相同的共享库上。

        7.3.4. Wine/Windows DLLs
    这部分主要介绍 Wine 现在支持的 DLL 的状态。Wine 的 ini 文件提供了用来改变 DLL 载入顺序的设置。载入顺序取决于几个方面，因此不同的 DLL 的设置也就不同。

        7.3.4.1. Pros of Native DLLs        native DLLs 的优点
    对于要执行的任务 native DLLs 保证 100% 的兼容性。例如，使用 native 的 USER DLL 将具有很完美的像 Windows 95样子的窗口边框，对话框控件等等。然而，使用 builtin 的 Wine 的该库，将不能生成精确的像 Windows 95 的界面。当设置 builtin 的 Wine DLLs 具有较高载入顺序级别时，可能由于其它一些重要的 DLLs 造成了这些微妙的差别，例如标准控件库 COMMCTRL 或标准对话框库 COMMDLG。
    更重要的是，如果在包含一些步骤的（如安装工具使用的一些用来创建桌面快捷方式的）native 版本的 shell 库载入之前使用 builtin 的 Wine 版本的 Shell DLL，可能不是很完美。而当使用 Wine builtin 的 Shell 时一些安装可能会失败。

        7.3.4.2. Cons of Native DLLs        native DLLs 的缺点
    在使用 native DLLs 情况下不是所有应用程序都会运行的更好。如果一个库要访问系统的其它特性，而这些特性在 Wine 中没有完全实现， 那么 native DLL 的运行效果可能不如对应（如果有的话）的 builtin DLL。例如，native 的Windows GDI 库必须和一个 Windows 的显示驱动器配对，当然在 intel Unix 和 Wine 下面是没有该驱动器的。
    最后，偶尔，built-in Wine DLLs 实现比相应的 native Windows DLLs 更多的特性。这种情况最重要的例子是 Wine 和由 Wine builtin USER DLL 提供的 X 的集成。如果 native Windows USER 库应该具有较高的载入顺序，将失去一些特性，如使用剪切板或在 Wine 窗口和 X 窗口之间的拖拽功能。

        7.3.4.3. Deciding Between Native and Built-In DLLs
        选择 Native 还是 Built-In DLLs
    显然，没有一个经验法则能决定采用哪一种载入顺序。所以，你必须非常熟悉具体的 DLLs 是做什么的，所给的库如何和其它的 DLLs 或特性交互，用这个信息来做出一对一的选择。

        7.3.4.4. Load Order for DLLs
    使用 Wine 配置文件中的 DLL 段，可以调高载入顺序。通常建议不要改变配置文件的设置。默认配置为大多数重要的 DLLs 定义了正确的载入顺序。
    默认的载入顺序遵循下面原则：对于全部功能都有了 Wine 的实现的 DLLs，或已知 native DLL 不能工作，builtin 库应该被先载入。在其它情况下，native DLL 应具有较高的载入顺序。
    [DLLDefaults] 部分的 DefaultLoadOrder 为所有的 DLLs 定义了哪个版本的应该先载入。见手册中参数的解释。
    [DllOverrides] 部分提供给 DLLs 一个不同于缺省情况的加载方式。
    [DllPairs] 部分必须成对出现。通常 DLLs 分 16 位和 32 位版本。Windows 系统中在大多数情况下，32 位版本不能在没有 16 位版本与其匹配的情况下运行。对于 Wine ，通常是 16 位的实现依赖 32 位的实现，把结果转换成 16 位系统的参数形式。这节里提到的一些定义好象已经没有了。
    将来， Windows  DLL 的 Wine 尽可能的实现朝着将 16 位和 32 位 DLLs 整合成更大的 DLLs 的方向发展，它们将以 32 位的名字保存在 DLLs/ 子目录中。

        7.3.5. Memory management        内存管理
    在主机系统上，Wine 中每个 win32 进程都有它自己专用的 native 进程，因此也有它自己的地址空间。这部分介绍一下  Windows 地址空间的结构和它是如何模拟出来的。
    首先，简要说明一下虚拟内存的工作过程。在 RAM 芯片上的物理内存被分成帧，每个进程所见的内存被分成页，每个进程有 4G 的地址空间（4G 是一个 32 位指针能寻址的最大空间）。页可以被映射也可以不被映射，试图访问没有被映射的页会得到一个 EXCEPTION_ACCESS_VIOLATION 异常，该异常的识别代码为 0xC0000005。任一页都可以被映射到任一个帧上，因此你可能有多个地址实际上包含同一段内存。页也可以被映射到一些地方，如文件或交换空间，在这些情况下，访问页就会产生一个磁盘访问，将内容读取到一个可用的帧上。

        7.3.5.1. Initial layout (in Windows)        起始内存布局(Windows)
    当一个 win32 进程开始时，并没有一个清晰的地址空间供它使用。许多页已经被映射到操作系统。特别是，EXE 文件本身和它所需要的 DLLS 被映射到内存中，空间被保留给栈和一对堆（用来给app分配内存）。这些东西有的需要分配固定的地址，有的则任意。
    exe 文件自身通常映射到从 0x400000 开始的地址上，事实上，大多数 EXEs 文件有重定位记录，这意味着它们必须在其基地址上载入，而不是其它任意的地址上。
    DLLs 和 exe 文件内部差不多，但是它们有自己的重定位记录，这意味着它们可以被映射到任何地址空间的任何地址上。记住我们不是在处理物理内存，而且每个进程的虚拟内存是不同的。因此 OLEAUT32.dll 可以在某一进程的某一地址上被载入，也完全可以在另一个进程的另一个地址被载入。确保所有被载入内存的函数能找到彼此是 Windows 动态链接器（NTDLL 的一部分）的工作。
    这样，我们有了映射到内存的 EXE 和 DLLs 。其它两个非常重要的区域是：栈和进程堆。进程堆和 unix 上的 libc 的 malloc 具有相同的作用：它是操作系统管理的一个区域，该区域通过 malloc/HeapAlloc 分配给应用程序。Windows 应用程序能够在进程堆存在的情况下创建几个堆。
    Windows 9x 也实现了另一种堆：共享堆。共享堆不寻常的地方在于它分配的所有东西在每个进程里都是可见的。

        7.3.5.2. Comparison        各种系统内存管理方式的简单对比
    现在我们以为应用程序有整个 4G 的地址空间可用。事实上不是如此，只有较低的 2G 可以使用，在 Windows NT 上较高的 2G 被操作系统使用，装载 kernel（从 0x80000000 开始）。为什么 kernel 要映射到每一个地址空间呢？主要是考虑性能：虽然也可以给 kernel 自己的地址空间，这也是 Ingo Molnars 4G/4G VM split patch 补丁在 Linux 系统上要做的－它需要每个进入 kernel 的系统调用切换地址空间。由于这是相当麻烦的操作（需要 flushing the translation lookaside buffers 等等）并且将 kernel 映射在每个进程的地址空间的固定位置上可以很好的避免经常性的进行系统调用。
    基本来说，内存映射之间的区别如下：
    Table 7-2 Memory layout ( Windows  and Wine )

         Address         Windows9x     WindowsNT      Linux
    00000000-7fffffff      User           User         User
    80000000-bfffffff      Shared         User         User
    c0000000-ffffffff      Kernel         Kernel       Kernel

    在 Windows 9x 上，只有较高的地址空间（0xC0000000以上）被 Kernel 使用， 2G 到 3G 的空间是一个共享的区域用来载入系统的 DLLs 和用于文件映射。最低的 2G 在 NT 和 9x 上都用于程序内存分配和建栈。

        7.3.6. Wine drivers        Wine 驱动程序
    在 Unix 下 Wine 没有实现 native 的 Windows 驱动。这主要是由于（看系统结构图）Wine 没有实现 Windows 的核心特性（此核心非彼核心，不是 kernel32.dll）。而是在 Unix 核心上建立一个代理层，提供 NTDLL 和 KERNEL32 的部分的功能。这就意味着 Wine 不能提供内部的基础结构来运行 native 驱动，无论是 Win9x 还是 NT 的。
    换句话说，Wine 只能针对特定的设备提供访问，它必须满足以下条件：
        1，Unix 支持该设备（有 Unix 驱动支持它）；
        2，Wine 已经实现联系 Windows 驱动的 API 和 Unix 驱动的 Unix 接口的代理代码；
    然而，为了在用户空间通过标准的 Windows 设备驱动 APIs 访问设备，Wine 努力的实现各个所需要的 DLL。多媒体驱动就是一个例子，Wine 载入 builtin DLLs 来访问 OSS 接口或 ALSA 接口。这些 DLLs 实现了和 Windows 用户空间任何音频驱动相同的接口。

        Chapter 8. Kernel modules        核心模块

        Kernel modules        核心模块
    这部分主要讲 kernel 模块。正如以前提到的， Wine 实现 NT 的结构，也就是通过  NTDLL  提供核心 kernel 功能，还有  KERNEL32 ，它在  NTDLL  基础上，实现了 win32 子系统的基础。
    这章包含两类内容（从不同角度）。从全局的角度展开一部分内容，因此在需要的时候会解释 NTDLL 和 KERNEL32 之间的工作；另一部分内容是从 DLL 的角度（NTDLL 或 KERNEL32）展开。这样选择更具有可读性和可理解性。至少，计划是这样。

        8.1. The Wine initialization process         Wine 初始化过程
     Wine 的启动过程相当复杂，不像大多数程序， Wine 开始展开代码的最好位置并不在 main() 函数，而是在位于外围的一些非常直接的 DLLs 中，例如：MSI，构件库（USER 和 COMCTL32）等。这部分的目的介绍从用户运行  Wine myprogam.exe 到 myprogram 获得控制权之间  Wine 是如何启动的。

        8.1.1. First Steps        第一步
    用户真正运行的  Wine 二进制文件并没有做太多事情，事实上它只负责检查使用中的线程模型(NPTL vs LinuxThreads)并触发一个新的二进制文件来执行启动序列中的下一步。看这一章的开头了解关于这步检查的更多信息以及它的必要性。你可以在 loader/glic.c 中看到代码。（在 Linux 上）可能借助于 preloader ，检查执行 wine-pthread  或 wine-kthread 的结果。在这我们需要使用不同的二进制文件，因为覆盖 native 的 pthreads 库需要我们利用 ELF 标志的属性来组织语义：如果不开始一个新的进程就无法这么做。
     Wine preloader 在 loader/preloader.c 中实现，用来给新创建的 win32 进程上强加一个 win32 样式的地址空间。这部分的细节在地址空间分布章节中。preloader 是一个静态链接的 ELF 二进制文件，它传递真正的 Wine 二进制文件来运行（无论是 wine-kthread 或 wine-pthread），所带的参数是从用户输入的命令行得来的。preloader 不是一个普通的程序，它没有 main() 函数。在标准的 ELF 应用程序中，入口点事实上是一个叫做 _start() 的标志处：它通过标准的 gcc 基础部分提供并通常跳到 __libc_start_ main() 处，该处代码会在将控制权传给程序员定义的 main 函数之前初始化  glibc  。
    preloader 从入口点直接获得控制权是有原因的。第一， glibc  不能被初始化两次：这样的后果是 undefined 并且 subject to change without notice 。第二，作为初始化 glibc 的一部分，地址空间分布可能被改变，例如任何对 malloc() 的调用将初始化一个用来修改 VM 映射的堆。最后，glibc 不会返回到 _start() ，所以通过再次利用它我们避免了重新创建 ELF 引导栈(env, argv, auxiliary array etc.)的需求。
    preloader 负责两件事情：一，保护地址空间中重要的区域，这样动态链接器不能映射共享库到其上；二，当从磁盘载入了真正的 Wine 二进制文件后，负责链接和启动。通常这些是 glibc 和 kernel 自动完成，但是我们使用一个静态的二进制文件拦截进程时，需要由我们来重启动进程。在 preloader 中大部分代码是关于从磁盘上载入 Wine -[pk]thread 和 ld-linux.so.2，链接它们然后启动动态链接进程。
    在跳到动态链接器之前 preloader 最后做的事情之一是扫描载入的 Wine 二进制文件的符号表，直接设置一个全局变量的值：这种将信息传递给 main Wine 程序的方法比将数据结构填充到一个环境变量或命令行参数然后在另外一边展开它更有效，但是它们完成的是同一件事情。全局变量设置指出了 perloader 描述表，它包含 preloader 保护的 VMA 区域。一旦启动动态链接器，这使得 Wine 可以取消它们的映射关系，留出了空间我们稍后可以进行初始化。

        8.1.2. Starting the emulator        启动模拟器
    启动模拟器自身的过程主要是链接定义在各个核心库和 DLLs 中的初始化函数，它们是：libwine, NTDLL, KERNEL32 。
    wine-pthread 和 wine-kthread 二进制文件共享同一个 main() 函数，它定义在 loader/main.c 中，所以，在 preloader 后无论选择那个二进制文件，我们都从这里开始。这将 preloader 提供的信息传给 libwine 然后调用定义在 libs/wine/loader.c 中的wine_init() 。这才是模拟器真正开始的地方，如果准备充分，可以从程序而不是 wine loader 自身来调用 wine_init() 。
    wine_init() 进行了一些非常基本的设置工作，如初始化调试的基础结构，然后是更多的地址空间的操作（见地址空间那章中关于 4G/4G VM split的内容），在载入 NTDLL － Wine 和 Windows NT 的核心－之前，跳到定义在 dlls/ntdll/loader.c 的 __wine _process_init() 函数中.
    这个函数用来初始化原始的 win32 环境。在 thread_init() 函数中，它建立了 TEB，为主线程和进程堆建立的 wineserver 连接。详细的见这章的开头部分。
    最后，它载入并跳到 KERNEL32.dll 的 __wine_kernel_init() 函数中，它定义在 dlls/kernel32/process.c 中。这里做了大部分工作。 KERNEL32 初始化代码从服务器获取了进程的启动信息，初始化注册表，建立磁盘映射系统和本地数据，开始载入应用程序本身。每个进程都有一个 STARTUPINFO 块，可以将它传递给 CreateProcess 用于定义各种东西，如第一个窗口将如何显示：这个通过 wineserver 发送给新进程。
    在用户决定了传递给 Wine 什么类型的文件后（一个 Win32 EXE 文件，一个 Win16 EXE 文件，一个 Winelib app 等），在控制权到达 __wine_kernel_init() 结束之前，程序被载入到内存中（可能连同载入和初始化其它 DLLs ，和大部分 Wine 启动代码）。这个函数由被初始化的新进程栈结束，然后新的栈调用 start_process 。就要完成了。
    初始化 Wine 最后部分是开始新的被载入的程序自身，start_process() 建立了 SEH backstop handler，叫做 LdrInitializeThunk() ，它执行了进程初始化的最后一步（例如执行重定位，和调用带有 PROCESS_ATTACH 参数的 DLL main()），获取可执行程序的入口点，然后是这一行：
        ExitProcess( entry(peb) );
    在经过这一系列过程后，跳到程序的入口点。用户程序从这里开始执行，Wine 提供的 API 也准备就绪，等待被调用。当从入口点返回时，ExitProcess() API 函数被用来初始化一个优雅的结束。


        8.2. Detailed memory management        内存管理的细节
    正如在前面章节解释的（Wine 架构－内存管理）， Wine 在每个进程的 32 位地址空间中为其创建一个 32 位的 Windows 进程。Wine 还尝试映射相关的地址，就像 Windows 要做的那样。这有必要进一步了解一下。

        8.2.1. Implementation        实现
    Wine（有点巫术的意味）可以将主要模块映射到期望的地址上（很可能是 0x40000000），生成进程堆，栈（同一个 Windows 可执行程序一样可以请求指定大小）。Wine 只是使用 ELF 可执行程序最初的栈来进行初始化，为可执行程序的主线程创建一个新的栈（如同一个 Win32 程序）。Wine 还将所有 native DLLs 映射到它们期望的地址，这样就不用再执行重定位。
    Wine 还实现共享堆，这样就可以使用 native Wine 9x DLLs 。这个堆总是在 SYSTEM_HEAP_BASE 地址或 0x80000000 被创建，且默认大小为 16 megabytes 。
    还有其它一些有魔力的位置。最低的 64K 内存专门留出来为捕获空指针引用取消链接关系使用。64K 到 1MB＋64K 的范围为 DOS 兼容性保留起来，它包含了各种 DOS 数据结构。最后，还有一部分地址空间用于映射以下内容：Wine 二进制文件自身，Wine 使用的 native 库，glibc malloc 区域等。

        8.2.2. Laying out the address space        布置地址空间
    直到 2004 年初，Linux 的地址空间和 Windows 9x 分布还是很像的：kernel 在最高的部分，最低的页用来取消捕获空指针引用链接关系，其余的都是可用的。kernel mmap 算法是可预知的：它通过映射文件到低地址开始工作。
    一系列底层补丁的开发，颠覆了上述前提条件，在 Wine 中的影响就是迫使 Win32 地址空间分布基于系统。这部分介绍为什么这么做和如何做的。
    exec-shield 保护程序补丁通过随机化 kernel mmap 算法，增强了安全性。不像以前总是为相同顺序的请求选择相同的地址那样，现在 kernel 将选择随机地址。因为 linux 动态链接器(ld-linux.so.2)使用 mmap 将 DSOs 载入到内存中，这就意味着载入 DSOs 的地址不再是可预知的了，所以很难使用缓存溢出来攻击软件。它还试图将某二进制文件重定位到内存中一个较低的叫做 ASCII armor 的区域，这样使得基于字符串的攻击较难跳到这些地方。
    Prelink 是一项加快启动速度的技术，它通过预先计算出 ELF 全局偏移表并保存到二进制文件中来实现。通过网格对齐将每个 DSO 放入地址空间中，动态链接器不必执行太多的重定位就允许严重依赖动态链接的应用程序很快的被载入内存。由于这项技术，复杂的 c++ 应用程序，如 Mozilla，OpenOffice 和 KDE 都能获得益处。
    4G VM split patch （内核）补丁是 Ingo Molnar 写的。它使 linux kernel 拥有自己的地址空间，因此就允许进程能够访问 32 位计算机的最大寻址空间－4G 。这就允许在损失效率的前提下指定的进程可以利用最大限度的内存；将 kernel 映射到每一个进程空间中的原因，就是要避免 syscall（系统调用）切换所带来的效率问题。
    每个改变都以一种和 Windows 不兼容的方式改变地址空间，prelink 和 exec-shield 意味着 Wine 使用的库可以放在地址空间的任何地方，通常这意味着一个库处于你想运行的 EXE 文件已经被载入的位置上（记住和 DLLs 不一样，EXE 文件不能在内存中移动）。4G VM split 意味着程序可以接受指向高端内存的指针，这些内存还没有准备好（例如，可能存储一些额外的信息）。特别是同 exec-shield 组合在一起时，这是非常致命的，因为可能出现进程堆分配超过了 ADDRESS_SPACE_LIMIT 限制的情况，这将导致 Wine 初始化失败。
    对于 Wine 的这些问题，解决办法是在地址空间保留特别的部分，这样我们不让系统使用这些空间，则它们将避免被使用，然后我们可以根据需要分配这些空间。问题之一是通过动态链接器自动进行映射：例如 Wine 被链接的任何一个库（如 libc, libwine, libpthread 等）将在 Wine 得到控制权之前被映射到内存中。为了解决该问题， Wine 在底层覆盖了默认的 ELF 初始化顺序，在重启标准的初始化，让动态链接器继续之前通过使用直接到 kernel 的系统调用保留需要的区域。这涉及到 preloader ，在 loader/preloader.c 中可以找到。
    一旦通常的 ELF 启动顺序完成，一些 native 库就可以很好的被映射到 3gig 限制之上的地址上：由于 3G 是 Windows 的限制，而不是 linux 的限制，所以这没有关系。我们仍然必须避免系统在高于那里的地方分配其它东西（像堆和其它 DLLs ），尽管如此， Wine 在地址空间较高的地方进行搜索，以便用 MAP_NORESERVE（不保留交换空间）的映射来迭代的填充位置。这样地址空间被分配，但是事实上后来内存并没有被分配。这部分代码可以在 libs/wine/mmap.c 中的 reserve_area 找到。

        8.3. Multi-processing in Wine        Wine 中的多进程
    让我们更仔细的看看在内存中 Wine 载入运行进程的过程吧。

        8.3.1. Starting a process from command line        从命令行启动一个进程
    当从命令行启动一个 Wine 的进程时（稍后，我们会区分一下 NE，PE 和 Winelib 可执行程序），有许多事情需要 Wine 来做。第一个可执行程序运行用来检查基于操作系统的线程模块（见 Wine 中的多线程，Secton8.4）然后启动与所选线程模块对应的 Wine 载入器。
    然后 Wine 从 Unix 中获取一些东西：环境变量，程序参数。然后使用标准的共享动态载入器将 ntdll.dll.so 载入到内存中。当 ntdll 被载入时，它首先生成一个层级的 Windows 环境。
        * 生成一个 PEB(Process Environment Block) 和一个 TEB(Thread Environment Block)
        * 建立和 Wine 服务器的链接，如果没有运行着的 Wine 服务器，则启动它
        * 生成进程堆
    然后 kernel 32 被载入（但是现在不是用 Windows 动态载入功能）然后一个 Wine 定义的入口点 __wine_kernel_init 被调用。事实上该函数处理所有进程的载入和执行的逻辑。并且从不会从它的调用中返回。
    __wine_kernel_init 将经历下面的步骤：
        * 从 Unix 程序参数中初始化程序参数
        * 在文件系统中查找可执行文件
        * 如果没找到，打印一个错误， Wine loader停止
        * 随后我们会隐藏其非 PE 文件类型，所以假设现在它是 PE 文件。使用和一个 native DLLs 相同的机制，将 PE 模块载入到内存中（主要是将文件数据和代码部分映射到内存中，如果需要还处理重定位）。注意模块的依赖性并不是在这里解决的。
        * 创建一个新的栈，大小由 PE header 决定，这个栈是为运行着的线程创建的（也是进程里唯一的一个）。仅在启动时使用该栈，后来将不再使用。
        * 谁调用了这个新的栈，ntdll，LdrInitializeThunk，谁就执行剩下的初始化部分，包括处理在 PE 模块中所有的 DLL 导入，进行 TLS slots 的初始化。
        * 控制权现在可以被传送给 PE 模块的 EntryPoint，它让可执行程序运行。

        8.3.2. Creating a child process from a running process        从正在运行的进程创建子进程
    这部分的步骤与前面所做的有着紧密的联系。
    这有几点需要了进一步了解一下。内部的实现通过 fork() 和 exec() 调用创建子进程。这意味着我们不需要再次检查线程模型，我们可以使用从命令行启动的父进程（或 grand-parent 进程）找到的线程模型。
    win32 进程创建允许在父子进程之间传递大量信息。这包括对象处理，窗口标题，终端参数，环境字符等。wine 会利用标准的 Unix 继承机制（如环境）和 wineserver（用来从父到子传递一组包含相关信息的数据）以前提到的载入机制将在 wineserver 中检查是否存在这样一组数据，如果存在，将执行相关的初始化。
    还要进行进一步的同步工作，父进程将等待直到子进程已经启动或失败。wineserver 也用来执行这些任务。

        8.3.3. Starting a Winelib process        启动一个 Winelib 进程
    在进行复杂的细节之前，让我们回顾一下什么是 Winelib 应用程序。它可以是一个普通的 Unix 可执行程序或是一个相当特别的 Wine beast 。这样针对一个可执行程序（假定 foo.exe）生成两个文件。第一个，叫做 foo 是 wine loader (wine) 的一个符号链接。第二个，叫做 foo.exe.so，和我们谈到 DLLs 时提到的 .dll.so 文件相当。在 Windows 中，如同任一个 DLL，可执行程序主要是一个包含导入导出信息的模块，因此 Wine 采用同样的机制载入 native 可执行文件和 DLLs 是合理的。
当从命令行开始一个 Winelib 应用程序（如 foo arg1 arg2）时，Unix shell 将以 Unix 可执行程序的方式来执行 foo，由于这事实上是 wine loader ，所以 Wine 将启动。然而，由于它不是作为 Wine 启动，而是 foo，这样将载入（使用 Unix 共享库机制）第二个文件 foo.exe.so 。一旦共享库被载入，Wine 将识别内含在共享库中的 32 位模块（用描述符），当载入一个标准的 native PE 可执行程序时它将继续同样的流程。
    Wine 需要实现第二种形式的可执行程序用来保持可执行程序中部分内容的初始化顺序。一个特别的问题是什么时候处理全局 c++ 对象。在标准 Unix 可执行程序中，对这些对象构造函数的调用保存在可执行程序的特定部分（ .init 没有为它命名）。 这部分所有的构造函数都在调用 main() 或 WinMain 函数之前被调用。用上面提到的第一种形式生成的 Wine 可执行程序将在 Wine 初始化它自身之前使得这些构造函数被调用。这样使用 Windows API 的任何构造函数都将失败，因为这时 Wine 的基础结构 infrasturcture 还没就位。使用第二种形式为 Winelib 可执行程序保证我们使用下面步骤初始化：
        * 初始化 Wine 基础结构
        * 载入可执行程序到内存中
        * 处理可执行程序的导入部分
        * 调用全局对象构造函数（若有的话）现在它们可以调用 Windows APIs
        * 调用可执行程序入口点
    留心的读者可能注意到如同 DLL，当注册可执行程序或DLL 描述符时可执行程序的导入部分已经处理了。然而，这也可以通过在 .init 部分添加一个特别的构造函数实现。为了使该方案可行，这个构造函数必须是在其它构造函数之前第一个被调用的构造函数，由可执行程序自身产生。Wine 编译链会维护它，也会为 Winelib 可执行程序生成可执行程序或DLL 的描述符。

        8.4. Multi-threading in Wine        Wine 中的多线程
    这部分假定你理解多线程技术的基本概念。如果不是的话，你可以从网上找到足够的教程开始学习。
    由于某种原因，Wine 中的线程有些复杂。第一，Windows 提供高级的多线程支持，与 Linux 对应的(pthreads)相比在 win32 中有更多的和线程相关的结构。第二是将 win32 线程映射到 native 的 Linux 线程上，给我们带来了很多好处，如：让 kernel 安排它们，而我们无须介入。然而因为没有 kernel 支持可以完全实现线程技术，在大多数 Wine 运行的平台上这样做是不理想的。

        8.4.1. Threading support in Win32        Win32 中的多线程支持
    win32 是线程友好 API，它不仅是完全线程安全的，而且它还提供了许多不同处理线程的工具。这些内容包括从基本的开始和停止线程，到相当复杂的如将线程插入到另外的进程中，COM inter-thread 线程间列集。
    编写 Wine 代码的首要挑战之一就是保证我们所有的 DLLs 的线程安全，没有竞争情况等等。这并不简单，如果你不确定一段代码是否是线程安全的，要及时提问。
    win32 提供了许多方法让你保证你的代码是线程安全的，然而最常见的是临界区和互锁函数。临界区是一类互斥标记，用来保护一段区域的代码。如果你不想让多线程同时运行在同一段代码中，你可以通过调用 EnterCriticalSection() 和 LeaveCriticalSection() 来保护它们。第一个调用 EnterCriticalSection() 的线程将锁住该区域并继续运行。如果另外一个线程调用它，则它被阻塞直到先前的线程调用 LeaveCriticalSection() 。
    因此，如果你使用临界区来保证代码的线程安全，检查每一个代码流程来确认任何一段区域都是安全的，将是非常重要的。如下：
        if (res != ERROR_SUCCESS) return res;
    在一个包含调用 EnterCriticalSection() 的函数中是非常需要的。要仔细哦。
    如果一个线程块在等待另一个线程离开一个临界区，你将会发现从 RtlpWaitForCriticalSection() 函数生成一个错误，还带着哪个线程获得锁定的信息。这只是在超时后才会出现，通常是几秒钟。获得锁定的线程可能很慢，这也是为什么 Wine 不能够像 non-checked build of Windows 那样中止应用程序，但是最通常的原因是由于某些原因，一个线程忘记了调用 LeaveCriticalSection() ，或者是在获得锁定时死掉了（也可能因为它转而等待另一个锁定了）。这不只是在 Wine 代码中出现：在等待临界区时出现的死锁可能是由于应用软件中的一个bug（仿真中的一个细小的差别导致的）造成的。
    另一个通用的机制是使用如 InterlockedIncrement() 和 InterlockedExchange() 函数。它们利用 native cpu 能力来执行一个简单的指令并保证系统上任何其它的处理器不能访问内存，而且在没有互斥标记的情况下使得你可以线程安全的进行一些基本的操作如添加删除检查一个变量。这些对于在安全线程 COM 对象中引用计数是很有用的。
    最后，TLS slots 线程本地存储槽的使用也是很普遍的。（线程本地存储(Thread local storage，TLS)）TLS 意味着线程本地存储，它是一系列作用域在一个线程中的槽，你可以将指针保存在其中。在 MSDN 上查找 TlsAlloc() 函数可以了解更多的关于 win32 实现这部分的内容。重要的是，在每个线程中特定槽的内容都是不同的，所以你可以使用它存储仅对一个线程有意义的数据。在最近的 linux 版本中 __thread 关键字为该功能提供了一个很方便的接口，线程库中有了一个更容易移植的 API 。然而，这些方便之处不能被 Wine 使用，我们只能自己实现 win32 TLS 。

        8.4.2. POSIX threading vs. kernel threading        POSIX 线程与 kernel 线程的对比
    Wine 以两种模式之一运行：pthread（POSIX 线程）或 kthreads（ kernel 线程）。这部分将介绍它们之间的区别。使用哪一种是通过一个小的测试程序自动选择，随后运行正确的二进制文件，wine-kthread 或 wine-pthread 。在 NPTL 系统上使用 pthreads ，非 NPTL 系统上使用 kthreads 。
    然我们开始回顾一下历史。在那个黑暗的年代，当 Wine 线程支持第一次被实现的时候，一个问题出现了，Windows 比起 Linux 来有更多的好用的处理线程的 APIs。这提出了一个问题， Wine 或者重新实现完整的 API，或者映射到系统的对应物上。如何使用没有包含全部所需特性的库来实现 win32 线程处理呢？答案当然是不可能。
    在 Linux 上用 pthreads 接口启动，终止和控制线程。pthreads 库建立在“内核线程” kernel  threads 上，内核线程是通过 clone(2) 系统调用生成。pthreads 为这部分功能提供了一个更好的（更具有可移植性的）接口，也为控制互斥标记提供了 APIs 。如果你想了解的更多，这有一个很好的关于 pthreads 的教程。
由    由于 pthreads 没有提供需要的语义来实现 win32 线程，所以决定基于使用系统调用 clone() 生成的底层核心线程来实现 win32 线程。这就提供了最大的灵活性并允许正确的实现，但会导致一些负面影响。特别显著的是，所有 userland linux APIs 会假定用户在使用 pthreads 库。当它们监测到 pthreads 在使用时，一些只能保证线程的安全性，例如：glibc 就是这样。更糟糕的是，当运行在同一个进程中时 pthreads 和纯核心线程具有奇怪的交互作用，然而 Wine 使用的一些库在内部就应用了 pthreads 。使用 Winelib 在源代码增加接口－这样在同一个进程中使用了 UNIX 和 win32 代码－结果造成混乱。
    解决办法很简单但很有创意：Wine 在它自己的二进制文件中实现自己的 pthread 库。由于 ELF 标识区域的语义，这将导致 Wine 自己的实现覆盖后来载入的其它实现（如 libpthread.so）。因此，任何调用 pthread APIs 的外部库将被链接到 Wine 的而不是系统的 pthreads 库，Wine 使用标准的 Windows 线程 APIs 来实现 pthreads ，反过来实现它自身。
    因此，只有那些在 pthreads 库被加载时保证线程安全的库，将会这么做，而任何使用 pthreads 的外部代码事实上将以创建 Wine 能够意识到并控制的 win32 线程的方式结束。尽管它需要做一些事情如覆盖内部的 libc 结构和函数，但在很长一段时间这样做效果都不错。也就是，使用这种方法直到 NPTL 出现，那一刻 linux 底层线程实现有了巨大的变化。
    在 loader/kthread.c 中可以找到 pthread 的实现，它用来生成 wine-kthread 的二进制文件。相比之下，loader/pthread.c 生成了用在较新的 NPTL 系统之上的 wine-pthread 二进制文件。
    NPTL 是一个为 Linux 设计的新的线程子系统，它很大程度上增加了它的性能和灵活性。通过允许线程变得更加 具有伸缩性和增加新的 pthread APIs 。NPTL 使得 Linux 在多线程世界中和 Windows 具有了竞争性。不幸的是它也破坏了进程中的一些 Wine 设定的前提（和其它一些应用程序一样，如 Sun JVM 和 RealPlayer）。
    然而也有一些好消息。NPTL 使得 Linux 线程足够强大以至于 win32 线程现在可以像其它一般的应用程序一样通过 pthreads 实现。将 win32 -kthreads 和由外部库生成的 pthreads 混合也不再有问题，不必覆盖 glibc 里面的了。你通过看loader/kthread.c 和 loader/pthread.c 文件的大小可以知道，代码复杂程度的差异是很大的。NPTL 也为如信号传递等东西做了一些其它语义的改变，在 Wine 的许多不同地方都需要这些改变。
在非 Linux 系统上，线程接口不够强大足以复制 win32 应用程序要求的语义，所以使用 pthread 重载的 kthreads 。

        8.4.3. The Win32 thread environment        Win32 线程环境            
    所有的 win32 代码，无论是来自native的Exe/DLL还是Wine 本身，都要求在它的环境中以特定的结构出现。这部分介绍这些结构包括什么， Wine 是如何建立它们的。缺少环境使得很难直接使用来自标准的 Linux 应用程序 Wine 代码-为了和 win32代码交互， Wine 首先必须获得一个线程。
    win32 代码需要的第一样东西是 TEB 就是 Thread Environment Block 线程环境块。这是一个内部的（无文档的）  Windows 结构，它包含了与每个线程有关的一系列东西，如 TLS slots，指向线程信息队列的指针，上一个错误代码等等。你可以在 inclulde/thread.h 中看到 TEB 的定义，或者至少我们现在知道它是什么了。作为内部的且易变的结构，TEB 的布局从开始就必须是通过逆向工程的。
    指向 TEB 的指针保存在 %fs 寄存器中，可以在 Wine 代码中通过 NtCurrentTeb() 获得，%fs 实际上保存了一个选择器，因此设置它需要修改进程本地描述符表(LDT)，处理这部分的代码在 lib/wine/ldt.c 中。
    如同任一个 wineserver RPC 要使用它一样，几乎所有运行在 Wine 环境中的 Win32 代码都需要使用 TEB，这也意味着任何可能发生阻塞的代都需要使用它，例如使用临界区的代码。TEB 也将 SEH 异常处理链作为第一部分，所以如果反汇编，你会看到这样的代码：
        movl %esp，%fs：0
    然后你可以看到程序建立一个 SEH 处理框架。所有的线程必须至少有一个 SEH 入口，它通常指向 backstop handler，它最终弹出那个著名的消息“This program has perforemed an illegal operation and will be terminated”。Wine 中我们只是直接抛给调试器。完整的关于 SEH 的描述超出了这部分的范围，然而如果你有兴趣的话，在 MSJ 中有一些不错的文章。
    所有的 win32 线程必须有一个 wineserver 连接。许多不同的 APIs 需要和 wineserver 交流的能力。反之，  wineserver 必须能够意识到 win32 线程以便能够准确的向程序的其它部分报告信息，做一些如传递线程间信息，发布 APCs() 的事情。因此线程初始化的一部分工作是初始化线程服务器端。这样做的结果不只是纠正了服务器的信息，而且线程可以使用一系列文件描述符和服务器进行交流，请求 fd，回应 fd 和等待 fd（用于阻塞）。

        8.5. Structured Exception Handling        结构化异常
    结构化异常处理（或叫 SEH）是 Windows 核心中异常处理的实现。它允许不同语言编写的代码跨过 DLL 边界抛出异常，通过抛出异常 Windows 会报告各种错误如非法访问。这部分会介绍它是如何工作的，在 Wine 又是如何实现它的。

        8.5.1. How SEH works        结构化异常是如何工作的
    SEH 基于嵌入在栈中的 EXCEPTION_REGISTRATION_RECORD 结构。它们形成一个链接着的列表，位于 TEB 中偏移量为 0 的地方（如果你不知道这是什么的话，见线程部分内容）。一个注册记录指向一个处理函数，当一个异常被抛出，处理函数就依次的执行。每个处理函数都会返回一个代码，它们能够选择是继续处理链还是处理异常然后重启程序。这个通常称作展开堆栈。每个处理函数在被调用后，从处理链中被弹出。
    在系统开始展开堆栈之前，它运行向量化处理。在 Windows xp 中这是 SEH 的扩展，允许注册的函数获得机会观察或处理在整个程序里从任一个线程中抛出的异常。
    抛出的异常以 EXCEPTION_RECORD 结构组织。它包含一个代码，标志，地址和不定数量的 DWORD 参数。运行语言可以使用这些参数将语言定义的信息和异常结合起来。
    很多东西可以触发异常。使用 RaiseException API 可以显式的抛出异常，也可以通过崩溃触发（也就是翻译自一个信号）。它们也可以通过被运行语言内在的调用来实现执行该语言定义的异常。也可以通过 DCOM 连接被抛出。
    Visual C++ 实现了各种 SEH 扩展，堆栈展开时对象析构，抛出任意类型的能力。这部分代码见 dlls/msvcrt/except.c 。

        8.5.2. Translating signals to exceptions        将信号转换成异常
    在 Windows 中，要求编译器使用系统异常接口，核心自己使用同样的接口动态的将异常插入到正在运行的程序中。相反的在 Linux上，异常 ABI（应用程序二进制接口）在编译器层次上（在 GCC 和链接器中）实现，核心通过发送信号告诉线程有异常事件发生。运行语言可以将这些信号翻译成本地异常，也可以不翻译，核心并不关心这些。
    你可能这样以为，如果一个应用程序崩溃，它就结束了，它就不关心 Wine 如何处理它了。这可能是来自你直觉的猜想，但是你错了。&&事实上一些 Windows 程序期望能够结束它们自己，然后不需通知用户就重新启动，一些程序包含来自第三方的buggy 二进制组件并使用 SEH 来收回崩溃，还有一些程序运行特殊的（kernel 层次上）命令并期望它工作。实际上，至少一组 APIs（IsBad*Ptr() 系列）只能通过执行可能崩溃的操作才能被触发，如果崩溃了，会返回 TRUE，如果没有崩溃，返回 FALSE。因此我们不只是需要实现 SEH 的基础结构而且需要将 Unix 信号翻译成 SEH 异常。
    将信号翻译成异常的代码在 NTDLL 中，可以在 dlls/ntdll/signal_i386.c 中找到。在 Wine 启动时该文件建立了针对各种信号的处理函数，对于那些表示异常条件的则将它们翻译成异常。一些信号 Wine 在内部使用它们，和 SEH 没有关系。
    Wine 中的异常处理运行在它们自己的栈上。每个线程都有它自己的信号栈，位于 TEB 后 4K 的地方。这样做有两个重要的原因。一，因为无法保证信号触发的应用程序线程有足够的栈空间供 Wine 信号处理代码使用。在 Windows 中，如果一个线程达到了它栈的边界，它会在栈保护页上出发一个错误。如果运行语言想的话，可以使用这个来增加栈的长度。然而，因为保护页验证对于 kernel 来说只是一个普通的段错误，它将导致一个嵌套的信号处理，会得到杂乱无章的信息，所以在 Wine 中我们不允许这样处理。第二，建立异常以抛出需要修改触发它的线程的栈，很难在运行它的时候做到。
    Windows 异常比 Unix 标准 API 包含更多的信息。例如，一个 STATUS_ACCESS_VIOLATION 异常(0xC0000005)结构包含错误地址，然而一个标准的 Unix SIGSEGV 只告诉应用软件它崩溃了。通常这个信息作为额外的参数传递给信号处理函数，然而它的位置和内容在不同的 kernel 中可能不同（BSD，Solaris等）。该数据在 SIGCONTEXT 结构中提供，在信号被发送之前，在信号处理函数的入口上，它包含 CPU 的寄存器状态。修改它将导致在重启线程前 kernel 调整内容。

        8.6. File management        文件管理
    随着时间流逝，Windows API 越来越像老的 Unix 模式的“每个东西都是文件”。因此，这整章关于文件管理的内容将包含文件管理，还有其它一些对象如文件夹，设备（Windows 中以一种相当耦合的方式操作它的）。后面我们将会在这章节里或多或少的看到一些其它的对象（管道或者控制台）。
    首先， Wine 在执行来自 Windows 的文件接口时，需要将文件名（在 Windows 上表现的）映射成 Unix 的文件名。这包含了几个部分，如何映射文件名，如何映射访问权限（包括文件和文件夹），如何映射物理设备（硬盘，其它设备，串口，并口，甚至是 VxDs）

        8.6.1. Various Windows formats for file names        各种 Windows 格式的文件名
    让我们先回顾一下各种 Windows 的文件名吧。

        8.6.1.1. The DOS inheritance        DOS 遗留下来的文件名
    首先是 DOS ，每个文件都必须位于一个由单个字母表示的驱动器上。从文件夹或文件名来区分驱动器的名称，一个 ":" 在这个字母后面，这样给出了著名的C：驱动器称谓。另一个著名的发明是使用固定的名称来访问设备，不只是这些名称固定，你不能改变它们，而且对于你在哪里使用它们也并不在乎。例如，众所周知 COM1 标识第一个串口，但是 c:\foo\bar\com1 也可以标识第一个串口。到今天也是这样，在 XP 上你不能给一个文件命名 COM1 不管它的文件夹是什么。
    后来(Windows 95)，微软决定克服一些文件名的小细节，这包括能够摆脱 8+3 格式（8个字母用于名称，3个字母用于扩展名），这样可以使用长文件名（这是正式的命名，如你所想的，8+3 格式是短文件名），而且也可以在文件名中使用比较奇怪的字符（例如空格，甚至 '.'）。这样你可以给一个文件命名 My File V0.1.txt 而不是 myfile01.txt 。有趣的是，多年以来，使用了一些技巧的别名技术来保存的长文件名，在磁盘上实际存储的文件名格式已经变成了短文件名。当较新的磁盘文件系统（随 NT 而来的 NTFS）引入时，代替了旧的 FAT 系统（自从 DOS 出现以来没有太大的发展），长文件名才成为真正的命名规则而短文件名处于了别名地位。
    Windows 也开始支持挂载网络共享，将它们看作本地磁盘（通过定义驱动器字母的方式）。这些年以来，这种方式已经发生了变化，所以我们就不再讨论这方面的细节了（特别是 DOS 和 win9x）。

        8.6.1.2. The NT way        NT 方式
    NT 的引入使得以前 DOS 下处理设备的方式发生了很大程度上的变化。
        * 不再是一组 DOS 驱动器字母集合（尽管“分配”是在该集合中创建符号链接的一种方式），而是一个独立的体系。
        * 该体系包括一些独特的部分。例如  \Device\Hardisk0\Partition0 表示第一块物理硬盘的第一个扇区。
        * 该体系包括了不只是文件和驱动器相关的对象，还有系统的一些对象。在这我们只谈及文件相关的部分。
        * 该体系不能直接通过 Win32 API 访问，只能通过 NTDLL API 。Win32 API只允许操作该体系的部分内容（其它部分不允许Win32 API访问）。当然这部分从 Win32 API 看来和 DOS 提供的非常相似。
        * 挂载磁盘通过在该体系中从 \Global??\C: 创建一个符号链接实现（这个名字是从 win32 API角度看到的）到 \Device\Harddiskvolume1 ，后者决定了物理磁盘上的分区，也就是 C: 在哪可以看到。
        * 网络共享也可以通过符号链接访问。然而这种情况下，从 \Global??\UNC\host\share\（在机器 host上的共享 share）到网络重定向符创建一个符号链接，它考虑两点：一，到远程共享的连接，二，处理远程共享的其余路径（在 server 名字后面，也就是在服务器上共享内容的名字）。
        注意：在 NT 命名惯例中，\Global?? 也可以简单的叫做 \?? 。
    所有这些东西，使得 NT 系统相当的灵活（如果你想，你可以添加新类型的文件系统）。你可以为所有对象提供一个独一无二的名字空间，大多数操作归结为在不同对象之间创建联系。

        8.6.1.3. Wrap up        总结
    让我们通过复习文件名字的不同格式来结束这章关于 Windows 文件。
        * c:\foo\bar 是全路径
        * \foo\bar 是绝对路径，全路径是在前面加一个默认驱动器（当前文件夹所在的驱动器）
        * bar 是相对路径，全路径是在前面添加当前目录
        * c:bar 是一个驱动器相对路径，注意，在 c: 是当前路径的情况下，处理是很简单的；就按照下一条方式（相对路径）处理。下面讨论驱动器的相对路径不是驱动器的默认路径的情况。解释成路径全名的方法在不同 Windows 版本下是不同的，还要依赖一些参数。用一些时间看看各种不同的情况。在 Windows 9x （也包括 DOS ），系统保存每一个驱动器的默认路径。因此在这种情况下，c:bar 被解释成 c:/ ＋ c 盘默认路径 ＋ bar 。当然，每个驱动器的默认路径是实时更新的。在 Windows NT 系统上有一点不同。因为 NT 实现了一个文件的名字空间用于保存每一个驱动器的默认路径，它相当于一个树（而不是 26 个驱动器），这么做有些笨拙。因此，Windows NT 默认的状况是所有的驱动器只有一个默认路径（实际上，当前路径保存在全局树中）－当然这个路径关联到指定的进程－c:bar 按下面的方法被解释：
            * 如果 c: 是驱动器的默认路径，结果是默认路径加上 bar
            * 否则被解释成 c:\bar
            * 为了弥合两种实现的差异（Windows 9x 和 NT），NT 在第二种情况下添加了有点复杂的处理。如果定义了 =C: 环境变量，默认路径就是这个值。这么做是很容易的，例如要写一个 DOS shell ，里面的驱动器当前路径也会被处理，在 NT 下也一样。这个机制（环境变量）在 CMD.EXE 中实现了，通过 cd 来改变环境变量。因为环境变量在创建进程的时候可以被继承，当前路径的设置也会被子进程继承，这都是仿效了旧的 DOS shell 的行为。利用环境变量处理当前路径问题不是新的机制，这只是一种辅助的处理方法。
        Wine 实现所有的这些方法（通过版本标记来区分 Windows 9x 和 NT）。
        * \\host\share 是 UNC（Universal Naming Convention 通用命名惯例）路径，代表在远程共享上的一个文件
        * \\.\device 表明安装在系统（正如从 win32 子系统看到的）上的物理设备。一个标准的 NT 系统会将它映射成 /??/device NT 路径。然后作为标准的配置，\??\device 很可能作为到物理设备的链接挂在 \Device\ 树中，并由详细说明。例如，COM1 是 \Device\Serial0 的链接。
        * 在各种 Windows 版本中，路径长度的限制为 MAX_PATH 个字符。为了避开这个限制，微软允许路径使用 32767 个字符，这种情况下，路径以 Unicode 形式表现（而不是 Ansi），路径前面会加上 \\?\. 。这个转换适用于以上提到的各种情况。
    为了总结我们上面提到的内容，将它们做成了一个简单的表格：
        表格
Table 8-1. DOS, Win32 and NT paths equivalences
Type of path    Win32 example    NT equivalent    Rule to construct
Full path       c:\foo\bar.txt   \Global??\C:\foo\bar.txt    Simple concatenation
Absolute path   \foo\bar.txt     \Global??\J:\foo\bar.txt    Simple concatenation using the drive of the default directory (here J:)
Relative path   gee\bar.txt      \Global??\J:\mydir\mysubdir\gee\bar.txt       Simple concatenation using the default directory (here J:\mydir\mysubdir)
Drive relative path    j:gee\bar.txt    *On Windows 9x (and DOS), J:\toto\gee\bar.txt.    *On Windows NT (and DOS), \toto is the default directory on drive J:.
                                        *On Windows NT, J:\gee\bar.txt.                   *On Windows NT, if =J: isn't set.
                                        *On Windows NT, J:\tata\titi\bar.txt.             *On Windows NT, if =J: is set to J:\tata\titi.
UNC (Uniform Naming Convention) path    \\host\share\foo\bar.txt     \Global??\UNC\host\share\foo\bar.txt       Simple concatenation.
Long paths    \\?\...          With this prefix, paths can take up to 32,767 characters, instead of MAX_PATH
                               for all the others). Once the prefix stripped, to be handled like one of the
                               previous ones, just providing internal buffers large enough).

        8.6.2. Wine implementation        Wine 的文件管理
    这部分我们主要介绍当提供一个 Windows 文件名时， Wine 如何打开该文件（从 Unix意义上来说）。这包括映射 Windows 路径到Unix 路径（包括设备的情况），处理访问权限，共享属性等。

        8.6.2.1. Mapping a Windows path into an absolute Windows path        映射 Windows 路径到 Windows 绝对路径
    首先前面章节我们介绍将任一种路径转换为绝对路径的方法。为了做到这个， Wine 实现了前面提到的所有算法。注意，是基于进程的本地信息（默认目录，环境变量等）进行的这种转换。我们假设在本章其余部分，所有的路径都被转换成了绝对路径。

        8.6.2.2. Mapping a Windows （absolute） path onto a Unix path        映射 Windows 绝对路径到 Unix 路径
    当 Wine 被请求映射路径名时（DOS 模式下，带有驱动器字符，如 c:\foo\bar\myfile.txt），Wine 将它转换成下面的 Unix 路径$(WINEPREFIX)/dosdevices/c:/foo/bar/myfile.txt 。Wine 配置进程负责将 $(WINEPREFIX)/dosdevices/c: 设置为指向 Unix 体系中一个目录的符号链接，而该目录在 DOS 驱动器中对应着 C 盘。
    这个方案允许：
        * 一个很简单的算法来映射 DOS 路径到 Unix 路径（不需要 wineserver 调用）
        * 一个可配置的实现：很容易的改变驱动器的映射
        * 一个很具有可读性的配置：不需要复杂的工具来读驱动的映射，ls -l $(WINEPREFIX)/dosdevices 足矣
    该方案也用于实现 UNC 路径名。例如：Wine 将 \\host\share\foo\bar\MyRemoteFile.txt 映射为 $(WINEPREFIX)/dosdevices/unc/host/share/foo/bar/MyRemoteFile.txt。然后由用户决定 $(WINEPREFIX)/dosdevices/unc/host/share指向什么地方。例如，它可以是本地机器上某文件夹的一个符号链接（只是为了模拟的目的），或者时一个远程磁盘挂载点的符号链接（通过 Samba 或 NFS），或是一个真正的挂载点。Wine 不会在在这做任何检查，在挂载远程驱动器时也不会提供帮助。
    我们已经看到了 Wine 如何将驱动器字母或一个 UNC 路径映射到 Unix 体系中，现在我们必须关注在这个体系中找到的文件名。主要问题是大小写敏感性。下面是一张表列出了各种文件系统的属性。

    Table 8-2 文件系统属性
    * 文件系统名         长度              大小写敏感性（磁盘上） 查找的大小写敏感性
    * FAT, FAT16, FAT32 短文件名(8+3)        保存大写字母            不敏感
    * VFAT              短文件名(8+3)        短文件名保存为大写字母  不敏感
    * .                 +长文件名做别名      长文件名保存大小写保留
    * NTFS              长文件名              长文件名保存大小写保留  不敏感
    * .                 +短文件名(8+3)做别名 短文件名大写
    * Linux FS (ext2fs, 长文件名              大小写保留               敏感
    * ext3fs，reiserfs)  
        大小写敏感性和存储的关系：我们说 NT 中大多数系统是大小写不敏感的，这必须理解为在查找文件时，匹配方式大小写不敏感。这和 VFAT 或 NTFS 的大小写敏感机制是不同的，尽管进行大小写不敏感匹配，它保存文件名称还是和在创建文件时给出的名字一样。

    既然 NT 中使用的大多数文件系统是大小写不敏感的而且大多数 Unix 文件系统是大小写敏感的,当发现要查找 Unix 路径时，Wine 进行一个大小写不敏感的查找。着意味着，例如，对于打开 $(WINEPREFIX)/dosdevices/c:/foo/bar/myfile.txt 文件，Wine 将递归的打开该路径的所有目录，并按顺序检查已经存在的目录项（大小写敏感），如果没有找到就进行大小写不敏感的匹配。这就允许在 Win32 file API 中即可以使用 DOS 或者 NT 风格的路径，也可以使用 Unix 风格的路径，在后面的文章中还会继续讨论这个问题。这也意味着处理在相同路径下仅仅大小写不同的两个文件的算法可能是不正确的。具体来说，相同的路径下两个文件名仅仅是大小写不同，Wine 能正确地找到符合大小写敏感匹配的那个文件，但是如果两个文件都不符合大小写敏感的匹配，那么 Wine 将会找到其中任意一个。例如，如果有两个文件：my_neat_file.txt 和 My_Neat_File.txt ，当要打开 MY_neat_FILE.txt 时 Wine 打开的文件不一定是哪一个。
    至于 Windows ，早期不支持目录的符号链接，大多应用程序（一些老的 native DLLs）也没有该特性。它们将目录结构看作一棵树，在浏览由目录组成的森林的时候有很多重要的问题需要关注（例如：从一个目录到另一个目录不能有两条路径，不能有目录环，等等）。为了阻止这些应用程序的错误行为，Wine 设置了一个选项。默认情况下，Wine 不跟踪符号链接。通过改变这个选项可以令 Wine 跟踪符号链接（见 Wine User Guide），但这么做可能是有缺陷的。
    Wine 认为 Unix 的文件使用长文件名。这么做是有原因的；大多数 Unix 在加载 Windows 分区(FAT, FAT32, NTFS)时都是用这个方法。因此，Wine 尝试尽量好的支持短文件。主要有两点：
        * 所期望的目录在 Windows FS (FAT, FAT32, NTFS)，并且操作系统支持短文件名（例如：Linux 支持 FAT, FAT32, VFAT）。在这种情况下，Wine 会充分利用这些信息真实地模拟 Windows 的行为。
        * 如果上述条件不满足（文件系统不真正支持短文件名，或者操作系统不能访问短文件名），Wine 通过长文件名来判定短文件名。不能确定所产生的短文件名同 Windows 的一样。短文件名由长文件名的头几个字符和一个 hash 值组成。这么做有几点好处：
        * 算法简单，代价小。
        * 算法不依赖目录中的其它文件。
    但是，也有一些缺点：
        * 算法与 Windows 不同，意味着程序不能用 Windows 生成的短文件名。这种情况通常发生在复制已经安装好的程序时。
        * 两个长文件名可能对应同一个短文件名。这种情况 Windows 无法处理，Wine 依靠 hash 算法尽量降低发生的概率。
    Wine 允许在大多数 file API 使用 Unix 文件名。这便于从命令行启动 Wine 或 Winelib 程序，而不需要转换成 Windows 形式。
    补充一点。Unix 系统没有广泛的支持 Unicode interface 的文件名，而 Windows 实现了（甚至在 NTFS 的物理层，FAT 是用 ANSI），Wine 需要做恰当的映射。启动时，Wine 定义 Unix Code Page ，它是 Unix 内核引用字符串的代码页。Wine 使用这个代码页在 Unicode (Windows) 路径和 Ansi (Unix) 路径之间建立映射。注：通过加载给定的参数可以改变代码页。
    下面讨论 Windows 驱动器是如何映射成为 Unix 驱动器的。在开始之前需要了解一些基本的操作。

        8.6.2.3. Access rights and file attributes        访问权限和文件属性
    现在我们看看 Wine 是怎么将 Windows 路径名转换成 Unix 路径名的，我们需要了解一下附属于文件或文件夹的各种元数据。
    在 Windows 中，访问权限是很简单的：一个文件可以是只读的或是可读写的。如果文件没有设置 Unix 用户写标志，则 Wine 为它设置只读标志。事实上，如果文件不能读则 Wine 无法返回（在 Windows 下不存在这种情况）。文件可以被看到，但是尝试打开它则会返回一个错误。不再有 Unix 的 exec 可执行标志。Wine 不使用该信息来限制新进程是否可以运行（而 Unix 使用 exec 可执行标志）。最后但并不是最不重要的一个：隐藏文件。在 Windows 的确存在但是在 Unix 中并不真正的存在。确切的说，在 Windows 中隐藏标志是关联到文件或文件夹上的一个元数据；在 Unix 中，它只是基于文件名（是否以  "."  开头）的语法规则。Wine 实现两种影响以  "."  开头的文件名和文件夹名的行为（取决于设置）。第一种模式（ShowDotFile 为 FALSE），每个以 "." 开头的文件和文件夹的隐藏标志都被打开。在 Unix 上这是很自然的行为（例如 ls 或文件浏览器）。第二种模式（ShowDotFile 为 TRUE）, Wine 从不设置隐藏标志，这样每个文件都是可见的。
    最后但并不是最不重要的,在打开一个文件之前，Windows 使用共享属性来检查文件是否能被打开；例如，系统中第一个打开某指定文件的进程，在它还保持打开文件的时候，就能够限制另一个想要打开文件进行写访问的进程，而赋予第二个进程读访问权限。从系统全局角度考虑将所有检查移到 wineserver 中，该功能在 Wine 中就得到了完全的支持。也要注意到被移到 wineserver 中的是在文件被打开时，实现 Windows 共享语义的检查。更多的对文件操作（如读和写）需要 server 的支持并不多。
    另一个将打开文件的代码放在 server 中的原因是 Windows 中被打开的文件是通过一个 handle 管理的，而 handles 在 wineserver 中才能创建出来。
    对于文件夹属性需要注意一点：我们可以轻易的将 Windows 的 FILE_ATTRIBUTE_READONLY 映射到文件上，就文件夹我们却做不到这一点。 Windows 的语义（当设置这个标志时）意味着不能删除该文件夹，而 w 属性在 Unix 中意思是即不能写也不能删除。因此在这里 Wine 使用了不对称的映射：如果文件夹（Unix 中的）不是可写的，那么 Wine 就报告 FILE_ATTRIBUTE_READONLY 属性,另一方面，当要求用 FILE_ATTRIBUTE_READONLY 属性设置文件夹时，Wine 什么也不做。

        8.6.2.4. Operations on file        文件操作

        8.6.2.4.1. Reading and writing        读写文件
    读写是基本的文件操作。当然 Wine 实现了该功能，是基于客户端调用 Unix 中的等价物（如 read() 或 write()）实现的。注意，由于 Wine 需要将对文件的 Windows-handle 转换成能够传给任意 Unix 文件函数的 Unix 文件描述符，所以任何读写操作都涉及到了 wineserver 。

        8.6.2.4.2. Getting a Unix fd        得到 Unix 文件描述符
    在任何一个文件相关的操作中，该操作是较重要的。基本上，每个被打开的文件（在 Windows 层面上），首先在 wineserver 中被打开，将 fd 存在其中。然后，Wine（客户端）使用 recvmsg() 将 fd 从 wineserver 进程传到客户进程。由于该操作费时较长，Wine 通过了一些缓存机制实现只发送一次，但是从一个文件（或其它能够通过文件描述符操作的 Unix 对象）上的 handle 获得一个 fd 仍然需要到 wineserver 往返一次。

        8.6.2.4.3. Locking        文件锁定
    Windows 提供文件锁定能力。当一个锁被设置时（锁可以设置在文件的任一个相连的区域上），它控制系统中其它进程如何访问该文件的这个区域。由于文件的锁定区域是定义在系统范围上，所以它的实现在 wineserver 中。它试图通过 fcntl() 和 F_SETLK 命令来使用 Unix 文件锁定（如果底层操作系统和文件所在的被挂载的磁盘支持该特性）。如果它不被支持，则 wineserver 只是假装它在工作。

        8.6.2.4.4. I/O control        I/O 控制
    到现在还没有必要实现对 DeviceIoControl() 的支持（对文件和文件夹），尽管 Windows 提供该支持，它是对非常特别的需要（如压缩管理，或者文件系统相关的信息）。对于设备（包括磁盘）而言就不是这样了，后面关于硬件的章节我们会讲到。

        8.6.2.4.5. Buffering        缓存
    对于文件访问 Wine 不做任何缓冲处理，而是（在需要的时候）依赖于底层 Unix kernel 。采用这种方案是因为对于一个文件在核心层而不是 Wine 层次上实现多种访问更容易。在同一文件上进行很多次小的读操作会导致性能下降，因为每个读操作为了获得文件描述符（见上面）都需要往返访问一次 sever。

        8.6.2.4.6. Overlapped I/O        Overlapped I/O
    Windows 引入了 overlapped I/O 的概念。基本上，它的意思是，一个 I/O 操作（如 read/write）将不会等待其完成就尽可能快的返回到调用者处，让调用者来处理等待操作并决定什么时候数据准备完毕（对于 read 操作）或是已经发送完毕（对于 write 操作）。注意 overlapped 操作是被链接到一个特定的线程的。
    有几点较重要：无需多线程技术 server 就可以处理多个客户端：你可以很容易的处理事件驱动模型（就是如何在漫长的 read() 操作等待中适当的杀死一个 server）。
    注意，微软对于这个特性的支持是随着 Windows 的各个版本发展起来的。例如，Windows 95/98 只支持串并口的 overlapped I/O，而 NT 还会支持文件，磁盘，sockets，管道，或 mailslots（邮件槽）。
    Wine 实现了 overlapped I/O 操作。这主要是当一些东西的当前状态发生改变时（如 read 操作的数据得到满足）一个在 server 中排队的请求被触发。通知调用处理准备就绪是通过将一特殊的 APC 排入队列中实现的，这样在线程下一个等待的操作中这个 APC 会被调用。然后这个特殊的 APC 来完成 I/O 操作的困难工作。这个方案允许合理放置一个等待机制，用来在状态改变时挂接一个将被调用的任务（在线程上下文上）。然而这不是百分之百的完美。当在调用线程中进行繁重的操作时，如果这些操作是漫长的，将会对调用线程造成影响，特别是它的 latency 传输时间。为了给 overlapped I/O 操作提供有效的支持，我们需要依赖 Unix kernel 特性（异步输入输出 AIO 就不错）。

        8.6.2.5. Devices & volume management        设备与卷管理
    到目前我们见识了文件名被映射到 Unix 路径的方法。对于设备还有必要说说。作为一般文件，在 Windows 中设备可以进行读/写操作，也可以实现控制机制（速度或串行序列的奇偶性；硬盘的卷名等）。因为 Linux 中也支持这些功能，就由必要在给出一个 Windows 设备名时打开（Unix 意义上的）设备。这部分适用于 DOS 设备名，它们在 NT 中作为其它设备的别名。
首先，Wine 实现上述的从 Win32 到NT的映射，这样，每个设备路径（NT 意义上的）就是以下的形式: /??/devicename/(or DosDevices/devicename)。由于 Windows 设备名是大小写不敏感的，Wine 在操作之前要把它们转换成小写字母。然后 Wine 做的第一个操作就是检查是否存在 $(WINEPREFIX)/dosdevices/devicename 路径。如果存在，用它作为设备的最终 Unix 路径。配置进程负责创建，例如，在 $(WINEPREFIX)/dosdevices/PhysicalDrive0 和 /dev/hda0 之间建立符号链接。如果这样的链接不存在，而设备名看起来像一个 DOS 磁盘名, Wine 首先从路径 $(WINEPREFIX)/dosdevices/c: 获得 Unix 设备（也就是符号链接的被挂载的目标设备）；如果没有给出一个 Unix 设备，Wine 会查找是否存在 $(WINEPREFIX)/dosdevices/c::，如果存在，说明有一个链接指向真正的 Unix 设备。例如，对于一个 CDRom , $(WINEPREFIX)/dosdevices/e:: 可能是 /dev/cdrom 的符号链接。如果不存在的话（依然用 C: 的形式），Wine 会尝试从系统信息（/etc/mtab 和 /etc/fstab）获得 Unix 设备。这种方法不适用于所有情况，因为我们不能保证真能找到文件夹。一种可能的情况，例如，如果只想用 CDRom 来作为音频 CD 播放器（也就是不用挂载），这样就得不到设备的任何信息。如果这些也不能工作，那么要做一些检查：如果设备名为 NULL，那么返回 /dev/null 。如果设备名是一个默认的串口名（COM1 到 COM9；打印机名 LPT1 到 LPT9），那么 Wine 尝试打开系统中的第 N 个串口（打印机）。另外，还有对一些基本的旧 DOS 名的支持，AUX 被变换成 COM1 而 PRN 变换成 LPT1，整个进程会使用新的名字。
总结一下：
Table 8-3. Mapping of Windows device names into Unix device names
Win95 device name  NT device name  Mapping to Unix device name
<any_path>AUX      \Global??\AUX   Treated as an alias to COM1
<any_path>PRN      \Global??\PRN   Treated as an alias to LPT1
<any_path>COM1     \Global??\COM1  $(WINEPREFIX)/dosdevices/com1 (if the symbol link exists) or the Nth serial line in the system (on Linux, /dev/ttyS0).
<any_path>LPT1     \Global??\LPT1  $(WINEPREFIX)/dosdevices/lpt1 (if the symbol link exists) or the Nth printer in the system (on Linux, /dev/lp0).
<any_path>NUL      \Global??\NUL   /dev/null
\\.\E:             \Global??\E:    $(WINEPREFIX)/dosdevices/e:: (if the symbolic link exists) or guessing the device from /etc/mtab or /etc/fstab.
\\.\<device_name>  \Global??\<device_name>    $(WINEPREFIX)/dosdevices/<device_name> (if the symbol link exists).

    现在对于一个指定的 Windows 设备我们知道应该打开哪个 Unix 设备了，让我们看看对设备的操作。这些操作包括读/写，IO 控制以及其它。
    在以下条件满足时，真实的硬盘和 CDROM 设备上可以支持读写操作。
        * 第一，当 ReadFile() 和 WriteFile() 调用被映射为 Unix 的 read() 和 write() 调用，用户（从运行 Wine 可执行程序的 Unix 角度来讲）必须有读（写）设备的访问权限。让用户直接写硬盘可不是明智的选择。
        * 读写块的大小也就是物理块的大小（硬盘通常是 512），还有偏移，也就是块的倍数。
    用户可以通过 Wine 读取（如果上面的第一个条件关于访问权限满足）硬盘或 CDROM 的卷信息。
    Wine 还可以识别 VxD 。但是这些 VxD 必须是 Wine builtin的（Wine 从不允许载入 native VxD）。它们以符号链接的方式被配置在 $(WINEPREFIX)/dosdevices/ 中，指向真正的 buildtin DLL 。这个 DLL 导出了一个单独的入口点，当使用打开 VxD 的句柄进行 DeviceIoControl 调用时 Wine 会使用该入口点。允许为旧的 Win9x 应用程序提供一些兼容性功能，仍然直接访问 VxD 。而在 Windows NT 中不再得到支持，最新的程序几乎不再使用该特性，所以我们不在这个方面做开发了，即使它还在用。也要注意到 Wine 对 native VxD 不提供支持。

        8.7. NTDLL module
     NTDLL 提供你期望从 kernel 得到的大多数服务。很多情况下，kernel 32 APIs 只是 NTDLL APIs 的封装。然而在 APIs 中也有一些区别（NTDLL 比对应的 kernel 32 包含一些更宽的语义）。这章开始提到的很多详细的函数实际上都是在 NTDLL 中实现的，加上很多我们还没有列出的。

        8.8. kernel 32 Module
    如前所述，kernel 32 中相当一部分的 APIs 是映射到 NTDLL 中。然而也有一些东西是直接在 kernel 32中处理的。我们看看这部分。
        8.8.1. Console        控制台

        8.8.1.1. NT implementation        NT 的实现
    Windows 仅在 Win32 子系统中实现控制台。在 NT 下，真正的实现部分使用了一个专门设计的子系统 csrss.exe(Client/Server Run-time SubSytem)，它而不是别的什么负责 animating 控制台。animating 包括如在同一控制台上处理多个进程（写操作必须是原子的atomic，但是在终端键入一个字符也必须由一个独立的进程读取），或者将一些信息传回给进程（改变大小或控制台的属性，关闭控制台）。在附属于控制台的每个进程和 csrss.exe 子系统之间 Windows NT 使用一个专门的（基于RPC）协议，它控制着系统每个控制台的 UI 部分。

        8.8.1.2.  Wine  implementation        Wine 的实现
    Wine 尽可能试图整合到 Unix 控制台中，但是整体情况不是很理想。基本来说，Wine 实现了三种控制台。
    * 第一种是 Unix 控制台到 Windows 环境的直接映射。从 Windows 程序角度来看，它不能运行在 Windows 控制台中，但是它将帮它的标准输入和输出流重定位文件中；这些文件分别被挂到 Unix 控制台的输出和输入流中。对于从 Unix 命令行运行程序（以及使用程序的结果就象它是一个 Unix 程序）这样做很方便，但是它缺少 Windows 控制台的所有语义。
    * 第二和第三种尽管和 NT 的有所不同，但与 NT 的设计相近。wineserver 起着 csrss.exe 子系统的作用（所有的请求都发送到它这），然后被发送给一个专门的 Wine 进程，它叫做 wineconsole ，管理控制台的 UI 。系统的每个控制台都有一个运行着的 wineconsole  的实例。这种设计的两种方式已经实现：它们在 wineconsole  的后端交替。第一个，就叫 user，创建一个真实的 GUI 窗口（因此 USER 命名）然后在窗口中将控制台呈现出来。第二种，使用 (n)curses 库来完全控制已经存在的 Unix 控制台，当然和其它 Unix 程序交互不如第一种方法流畅。
    下表介绍了三种方法实现的区别（表8-4）
Function                Bare streams                         wineconsole  & user backend                 wineconsole  &curses backend
作为 Win32 对象                 这种情况下不使用 Win32 对象        在server中实现，一个特殊的 Winelib 程序        同左
（和相关的句柄）                 用于 Win32 流操作的句柄实际上是        （ wineconsole ）负责管理呈现过程和用户输
                        对应 Unix 流的"裸句柄"。更多的        入。模式操作函数行为如同预期。
                        操作函数（GetConsoleMode()/
                        SetConsoleMode()）并不支持。
继承（包括在有                 不支持。一个进程的每个子进程        完全支持（由新的 USER32 窗口来处理                 完全支持,除了新控制台的
CREATE_DETACHED,        将继承 Unix 流，所以也继承了        每个新的控制台创建）                         创建，控制台将和前一个
 CREATE_NEW_CONSOLE 标志         Win32 标准流                                                                 一样在相同的 Unix 终端上
的CreateProcess                                                                                        呈现，&&导致了不可
中的处理）                                                                                         预料的结果&&
ReadFile()/                完全支持                         同左                                         同左
WriteFile()
操作
屏幕-缓冲区操作                 不支持                                 完全支持                                 同左
（创建、删除、改变
大小..）
读/写屏幕-缓冲区、        不支持                                 完全支持                                 同左
鼠标位置的APIs
操作呈现窗口大小的        不支持                                 完全支持                                 部分支持（&&不如我们不控
APIs                                                                                                制底层 Unix 终端的大小时
                                                                                                工作的那么好&&）
信号处理（特别的，        意味着Ctrl-C产生                 部分支持（Ctrl-C,和预想的行为一样        同左
Ctrl-C处理）                 一个结束程序的信号                 然而其它 Win32 C UI 信号不被执行）
                        SIGI NT .

    在以下情况下控制台后可以创建 Win32 对象：
    * 当程序从 wineconsole 开始时，从 wineconsole 启动的进程可以创建一个新的控制台对象并使用它。
    * 当程序没有附在控制台上时，调用 AllocConsole(), Wine 会启动 wineconsole ,然后将当前的程序附在这个控制台上。在这种方式下，由于 Wine 不能判断 Unix 控制台的状态，所以总是选择 USER32 模式。
    请注意，使用 CREATE_NEW_CONSOLE 标志开始一个子进程，将会在子进程中结束 AllocConsole() 的调用，这样在 USER32 后端创建 wineconsole 。
    另一个有趣的一点是 Windows 只在 kernel32 DLL 中实现对控制台对象（输入和屏幕缓冲区）的句柄，这些不能从 NTDLL 层面被发送出来也不能从 NTDLL 层面看到，例如，控制台输入等待。这怎么可能呢？ Windows NT 在这使用了一点技巧。通常的句柄有一个有趣的属性：它们的整数值总是一个四的倍数（就象是表开头的偏移量）。控制台句柄却不一样，不是四的倍数，而是两个低位设置 1（四的倍数意味着较低两位置为 0 ）。当 kernel32 看到一个低两位设置为 1 的句柄，它就知道这是一个控制台的句柄,并采取相应的措施。例如，在各种 kernel32!WaitFor*() 函数中，它向一个用于目标控制台的专门的等待事件传送控制台句柄（输入和输出-终端屏幕缓冲区有足够的句柄）。在你需要时 kernel32 的一个函数 GetConsoleInputWaitHandle() 可以将句柄返回给事件。在这些控制台句柄中另一个有趣的地方是在 ReadFile() 中 (WriteFile()) 将控制台句柄传送给 ReadConsole（WriteConsole()）。注意总是 ANSI 版本的 ReadConsole()/WriteConsole() 被调用，因此使用默认的控制台代码页。还有其它有关的几点，你可以在 dlls/kernel 中找到它们。所有这些都在 Wine 中实现。
    Wine 还实现了和 Windows 一样的用于存储控制台首选项的注册表信息。这些设置可以被全局的定义，或者基于每个进程名。对修改哪部分（全局，或是当前运行着的程序）的注册表设置 wineconsole 向用户提供选择的机会。
    Table 8-5. Console registry settings        控制台注册表设置
    （对理解 Wine 的架构没有任何作用，略过）

        8.8.2. Win16 processes support        Win16 多进程支持

        8.8.2.1. Starting a NE(Win16) process        启动一个 NE 进程
    Wine 也可以运行 16 位的进程，但是这个特性只在 Intel IA-32 结构下被支持。
    当 Wine 被请求运行一个NE（Win16 进程），它实际上将它的执行移交给一个特定的可执行程序 winevdm 。VDM 表示 Virtual DOS Machine。winevdm 是一个 Winelib 应用程序，但是它建立了正确的 16 位环境来运行可执行程序。后面我们将详细介绍这是什么意思。
该可执行程序（或它的子程序）创建的任何一个 16 位进程将运行在同一个 winevdm 实例中。在每个实例中，一些功能被提供给这些 16 位的进程，包括合作多任务，共享相同的地址空间，管理代码、数据和栈需要的 16 位段的选择器。
    注意，多个 winevdm 实例可以运行在相同的 Wine 会话中，但是上述功能只在一个实例中共享，而不是在所有实例中。winevdm 作为 Winelib 应用程序被编译，因此可以访问一个 32 位应用程序的任何部分。
    每个 Win16 应用程序在 winevdm 中作为一个 Win32 线程被实现。然后 winevdm 实现它自己的预定计划部分（这部分代码在krnl386.exe DLL 中）。由于被需要的 Win16 预定计划是非抢占式的，这不需要任何底层 OS 核心支持。

        8.8.2.2. SysLevels
    SysLevels 是未公开的 Windows 内部的线程安全系统，专门用于 16 位的应用程序（或是直接或间接调用 16 位代码的 32 位应用程序）。它们是临界区必须以特定的顺序被使用。这是很普通的机制，总是有三个 syslevels:
    * level 1 是 Win16 互斥标记
    * level 2 是 USER 互斥标记
    * level 3 是 GDI 互斥标记
    当进入一个 syslevel，代码（在 dlls/kernel/syslevel.c 中）将检查是否持有较高的 syslevel，如果是则生成一个错误。这是因为当持有 level 3 时是不能进入 level 2 的－首先，你必须离开 level 3 。
    你可以看到对 _ConfirmSysLevel() 和 _CheckNotSysLevel() 的调用贯穿在代码中。这些函数是对 syslevel 状态的基本断言，可以用来检查有没有意外的违背规则的情况。如果发生了，解决办法是，沿着 Wine 进入到非法状态的线路，阅读 Wine 函数调用的代码，进行回溯找到。

        8.8.2.3. Memory management        内存管理
    每个 Win16 地址以选择器：偏移量的方式表达。选择器是 LDT（局部段描述表）的入口，但是是 16 位的入口，它限制了偏移量要小于 64kB 。因此，一个 Win16 进程的最大可用内存为 512MB 。注意，处理器运行在保护模式下，但是使用 16 位的选择器。
    Windows 对于一个 16 位的进程，定义了一些选择器用来访问 DOS 提供的真实的内存。基本来说，Wine 也提供该区域的内存。

        8.8.3. DOS processes support        对 DOS 进程的支持
    刚刚提到的行为也适用于 DOS 可执行程序，也是由 winevdm 以同样的方式进行处理。这也仅在 Intel IA-32 结构上得到支持。
    在一个 Wine 特定的 DLL(winedos)中 Wine 也实现了绝大多数的 DOS 支持。这个 DLL 在一些特定情况下被调用，如：
    * 在 winevdm 中，试图启动一个 DOS 应用程序（.EXE 或.COM，.PIF）
    * 在 kernel32 中，二进制代码尝试调用一些 DOS 或 BIOS 中断（例如 Int 21h）
    当 winevdm 运行一个 DOS 程序，它运行在实模式下（从 IA-32 角度看就是 V86 模式）
    Wine 也支持部分 DPMI（ DOS 保护模式接口）
    Wine 在运行一个 DOS 程序时，需要映射 1MB 的虚拟内存到真实的内存上（如同 DOS 程序看到的那样）。当不能做到这一点时（如一些其它的东西已经在使用该区域），就不会有 DOS 支持了。不只是这样，对线性地址 0 的访问也是允许的（因为它也是事实模式的地址 0 ，这是有效的地址）。因此空指针引用错误就不能被捕获到了。

        Chapter 9. Graphical modules        第九章 图形模块

        9.1. GDI Module        图形模块

        9.1.1. X Windows System interface        X Windows System 接口
    如果多个线程并发地同时访问同一个 display ，用于实现 X 客户端的 X 库就不能正常工作了。可以编译 X 库来执行它们自己的同步（通过调用 XInitThreads()）。然而，Wine 没有使用这种方法。Wine 使用 wine_tsx11_lock()/wine_tsx11_unlock() 函数来执行它自己的同步。该锁定通过临界区保护库访问，并安排一些事情使得不用 -D_REENTRANT 编译的 X 库能和 Wine 一起工作。
    过去，所有对 X 的调用都通过一个叫做 TSX...()（表示 Thread Safe X...）的包装。它现在仍在代码中使用，当锁定被潜在的取得和释放时它是没有什么效率的。现在的代码应该显式的获取锁。

        Chapter 10. Windowing system        第十章 窗口系统

        10.1. USER Module
     USER 实现了窗口和消息子系统。它还包含用于一般控制和各种东西（矩形，剪贴板，WNet等）的代码。Wine USER 代码位于 Windows/controls/ 和 misc/ 目录中。

        10. Windowing System        窗口系统

        10.1. USER Module
    USER 模块实现了窗口子系统和消息子系统，也包括通用控件和其它各种功能（多边形，剪切板，WNet 等）。Wine USER 的代码位于 windows/, controls/, misc/ 目录里面。

        10.1.1. 窗口子系统
    windows/win.c
    windows/winpos.c
    所有的窗口以共同的祖先（桌面窗口）的父/子层级方式排列。每个窗口结构包含一个指针指向直接的祖先（父窗口如果 WS_CHILD 位被设置），一个指针指向同属（通过 GetWindow(...,GW_NEXT) 得到）,一个指针指向拥有者窗口（仅用于通过合法的 hwndParent 参数创建的弹出窗口）,和一个指向第一个子窗口的指针（通过 GetWindow(...,GW_CHILD)得到）。因此所有的弹出窗口和非子窗口位于层级关系的顶层，它们的祖先指向桌面窗口（wnd-> parent）。

    Desktop window            - root window
       |     \    `-.
       |      \      `-.
      popup -> wnd1 -> wnd2       - top level  Windows    
       |       \  `-.      `-.
       |        \    `-.      `-.
      child1 child2 -> child3 child4  - child  Windows

    水平箭头表示同属窗口关系，垂直的线表示－祖先/后代关系。简而言之，有同一个直接祖先的所有的窗口都是同属窗口，所有直接祖先不是桌面的窗口都是子窗口。弹出窗口的行为和顶层最高的窗口基本相同，除了它们是有拥有者的。这种情况下唯一的要求是在顶层同属窗口中它们必须排在它们的拥有者（它们不是最高的）之前。子窗口被限制在它们的父窗口的客户区域内（客户区域就是窗口自己进行绘制的地方，非客户区域包括标题，菜单，边框，原有的滚动条，和最大/最小 /关闭/帮助按钮）。
    另一个很重要的概念是 z-order ，它来自于祖先/后代层级关系，用来决定上层/下层关系，例如上面的例子，z-order 就是
    child1->popup->child2->child3->wnd1->child4->wnd2->desktop
    当前活动的窗口（在 Win32 中的前端窗口）被移动到 z-order 的前面除非它的顶层祖先拥有弹出窗口。
    所有这些内容在 windows/winpos.c 中，SetWindowPos() 作为窗口管理器的首要接口。
    Wine 定义：在默认的被管理的模式下，以桌面窗口作为一个基本的 stub ，每个顶端窗口获得它的 X 对应部分。然而，在桌面模式下，只有桌面窗口有一个 X 窗口。并且，无需其它方式，SetWindowPos() 最终应该借助 Begin/End/DeferWindowPos() 调用来实现。

        10.1.1.1. Visible region, clipping region and update region        可视区域，剪切区域和刷新区域
    windows/dce.c
    windows/winpos.c
    windows/painting.c
     ______________________
    |-------.              |  A 和 B 都是 C 的子窗口
    |   A   |-------.      |
    |       |       |      |
    |~~~~|~~   B    |      |
    |    |          |      |
    |     ~~~~~~~~~~   C   |
    |______________________|

    可视区域检测窗口的哪些部分被其他窗口遮盖。如果窗口设置了 WS_CLIPCHILDREN 属性子窗口下面的所有区域都被认为是不可见的。同样，如果设置 WS_CLIPSIBLINGS 属性，被兄弟窗口覆盖的所有区域都被认为是不可见的。子窗口总是被父窗口的边界裁减掉。
    B 设置了 WS_CLIPSIBLINGS 属性
    .         _______
    :        |       |
    |   _____|       |
    |  |         B   |    B 的可视区域
    :  |             |
    .   ~~~~~~~~~~~~~

    当程序申请一个 DC（display context 显示设备上下文）时，它可以指定一个剪切区域，在这个区域内的图形才能被显示。这个区域是可视区域和剪切区域的交集。
    程序申请了带剪切区域的 DC ：
            ______
        .--|--.   |         .--.
     ,--+--'  |   |      +--`  |
    |   |  B  |   |  =>  |     |  在 DC 区域内部的才是可见的
    |   |     |   |      |     |
    `---|-----|---'      `-----`
        `-----`

    当窗口管理器检测到窗口某些部分成为可视的，它就把这块区域变成窗口的更新区域，然后产生一个 WM_ERASEBKGND 和 WM_PAINT 消息。另外，当未覆盖的区域和窗口的非客户区相交时，WM_NCPAINT 消息被发送出来。应用软件必须通过调用 BeginPaint() 和 EndPaint() 这对函数响应 WM_PAINT 消息。BeginPaint() 返回一个 DC ，它用于累加所有的更新区域。这个操作清理了不必要的刷新，并且窗口不会收到多余的 WM_PAINT ，直到窗口管理器产生了一个新的更新区域。

    ________________________       ...          / C 更新区域
    |______                  |     :      .___ /
    | A    |_________        |  => |   ...|___|..
    |      |         |       |     |   :  |   |
    |------'         |       |     |   :  '---'
    |   |      B     |       |     |   :      \
    |   |            |       |     :            \
    |   `------------'       |                    B 更新区域
    |                   C    |
    `------------------------'

    Windows 维护着一个 DC cache ，它包含本身，隶属于哪个窗口，和可选的剪切区域（可视区域存储在 DC 自身里）。当一个改变窗口状态的函数被调用时，窗口管理器遍历 DC cache 计算这个操作涉及哪些窗口。DCE (DC entries) 可以属于窗口实例，也可以属于窗口类，还可能共享于所有的窗口（Windows 3.1 限制共享的 DCE 小于 5）。

        10.1.2. Messaging subsystem        消息子系统
    windows/queue.c
    windows/message.c
    每个 Windows 任务/线程都有它自己的消息序列－它从这里获得消息。消息可能是：
    1.实时生成的（WM_PAINT , WM_NCPAINT , WM_TIMER）
    2.系统创建的（硬件消息）
    3.其它任务/线程邮寄的（PostMessage）
    4.其它任务/线程发送的（SendMessage）
    消息优先权：
    系统首先查找的是发送的消息，然后是邮寄的消息，然后是硬件消息，然后检查是否序列的 dirty window 位被设置了，最终，它检查到时定时器。见 windows/message.c 。
    对于所有的消息，只有邮寄的消息直接进入私有的消息序列。系统消息（甚至是在 win95 中）是首先被收集到系统消息序列中，然后它们或者在那等待 Get/PeekMessage 来处理它们，或者如同在 win 95中，如果系统序列被毁掉了，一个特殊的线程（原始输入线程 "raw input thread" ）会将它分配给私有序列。发送信息被分别的排列，发送者睡眠直到它得到一个应答。特殊信息的实时创建取决于窗口/序列状态。如果窗口更新区域是非空的，系统在序列中设置 QS_PAINT 位，最终这个窗口会收到一个 WM_PAINT 信息（如果更新区域和非客户区域交互，则是 WM_NCPAINT ）。当时钟序列中的一个到期时一个时钟事件被触发。依赖于时钟参数 DispatchMessage 或者调用回调函数，或者调用窗口过程。如果没有消息，任务/线程会睡眠直到消息出现。
    有几个技巧性的因素
    * 系统消息的响应和传递要在正确的任务／线程上文中完成。因此当 Get/PeekMessage 遇到未分配的系统消息，并且这个消息不是当前任务／线程的，它将被跳过（或者把它移动到任务／进程私有消息队列中，AFAIK），以后再处理，系统转换到一个正确的上下文（Win16）。在第一种情况我们失去了正确的消息顺序，第二种情况我们使用声名狼藉的同步系统消息队列。

    * 任务间／线程间的 SendMessage 。系统必须通知目标队列即将来到的消息，然后切换上下文并等待直到成功返回。Win16 保存必要的参数在队列结构中并调用 DirectedYield() 。然而，在 Win32 中可能有几个高优先级的执行中的线程发出要被处理的消息，在这种情况 SendMessage 必须为要发送的消息建立一个顺序。另一种情况是当它在自己的 SendMessage 中阻塞时，把它送回发送者。

        10.1.3. Accelerators        加速键
    有三种不同大小的 accelerator 结构对于用户是可见的：
    1. NE 资源中的 Accelerators 。在 Windows95 和 Wine 中这也是内部的全局句柄 HACCEL 。通过 Win16/32 API 提供给用户的是作为 Win16 全局句柄的 HACCEL16 和 HACCEL32 。总共 5 个字节：
        BYTE fVirt;
        WORD key;
        WORD cmd;
    2. PE 资源中的 Accelerators 。只有通过直接访问 PE 资源用户才能使用。共 8 个字节：
        BYTE fVirt;
        BYTE pad0;
        WORD key;
        WORD cmd;
        WORD pad1;
    3. Win32 API 中的 Accelerators 。在 Win32 API 中，CopyAcceleratorTable 和 CreateAcceleratorTable 函数提供给用户。共 6 字节：
        BYTE fVirt;
        BYTE pad0;
        WORD key;
        WORD cmd;
    为什么在 Win32 API 中有两个类型的 accelerators ？我们只能猜测，最有可能的是编译器不能对资源进行字节对齐操作。Win32 ACCEL 使用 #pragma(2) 定义，但是对 RC 不能做任何字节对齐，因此它认定是 #pragma(4)。

        10.2. X Windows System interface        X 窗口系统接口

        10.2.1. Keyboard mapping        键盘映射
    Wine 需要知道你的键盘布局。这个需求来至于很多应用程序需要正确的键盘扫描码。因为它们直接读，而不是从 X server 获得。这意味着 Wine 需要从 X 映射键盘扫描码给应用程序。启动时，Wine 通过查看是否有匹配的已经定义的表，识别出激活的 X 布局。如果有，那就都就绪了，如果没有需要定义一个。
    要做到这些，打开文件 dlls/x11drv/keyboard.c 并且查看已经存在的表。做一个备份，特别是没有用 CVS 时。
    你要做的是找到要生成的扫描码。在 main_key_scan 表中，应该是这个样子的：
    static const int main_key_scan[MAIN_LEN] =
    {
        /* this is my (102-key) keyboard layout, sorry if it doesn’t quite match yours */
        0x29,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,
        0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,
        0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x2B,
        0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32,0x33,0x34,0x35,
        0x56 /* the 102nd key (actually to the right of l-shift) */
    };
    下一步，给每一个键盘上的按键分配扫描码。在 keyboard.c 中已经有了一个 US 101 键盘的。如果没有第 102 个键，那就跳过。
    然而，对于大多数国际化的 102 键盘，做法很简单。键盘扫描码已经在 main_key_scan 完美的匹配物理键盘了，所以你要做的是看一遍是不是选择了恰当的表。唯一需要注意的是 102 键盘的最后一行的第一个键，通常是 Z ，它必须单独放在最后一行。
    如果完成了这样一张表，现在需要加入 main_key_tab[] 布局索引表中。应该是这个样子的：
    static struct
    {
        WORD lang, ansi_codepage, oem_codepage;
        const char (*key)[MAIN_LEN][4];
    } main_key_tab[]=
    {
        ...
        ...
        {MAKELANGID(LANG_NORWEGIAN,SUBLANG_DEFAULT), 1252, 865, &main_key_NO},
        ...

    添加了之后，重新编译 Wine 检查它是否工作。如果检查失败，试着执行
        WINEDEBUG=+key,+keyboard wine > key.log 2>&1
    检测 key.log 找到错误消息。
    注意 LANG_* 和 SUBLANG_* 的定义是不是在 include/winnls.h 中，需要找出分配给它语言编号，再检查 WINEDEBUG 的输出。这个数字应该是 (SUBLANG * 0x400 + LANG), 因此，例如，LANG_NORWEGIAN (0x14) 和 SUBLANG_DEFAULT (0x1)will be (in hex) 14 + 1*400 = 414，因为我是挪威人，应该在 WINEDEBUG 输出中找 0414 ，这就是为什么我的键盘没能检测到。

        Chapter 11. COM in Wine
        （对理解 Wine 的架构没有实质性的作用，略过）

        Chapter 12.  Wine and OpenGL        Wine 与 OpenGL

        12.1. What is needed to have OpenGL support in Wine        为什么 Wine 要支持 OpenGL
    基本来说，如果你的计算机上安装了一个 Linux OpenGL ABI compliant libGL (http://oss.sgi.com/projects/ogl-sample/ABI/),所需的东西你就都有了。
    清楚起见，我将一步步的介绍配置脚本检查了哪些东西。
    如果当 Wine 编译后 OpenGL 支持没有被编译，你可以检查一下以下几点哪里有问题。

        12.1.1. Header files        头文件
    Wine 中实现 OpenGL 支持所需的头文件有:
    gl.h :  所有 OpenGL 核心功能、类型和枚举量的定义
    glx.h : OpenGL 如何在 X  Window 环境中集成
    glext.h :被注册的 OpenGL 扩展的列表
    现在最后的文件(glext.h)对于 Wine 没有什么用处。但是由于该文件很容易从SGI(http://oss.sgi.com/projects/ogl- sample/ABI/glext.h)中获得，所有的 OpenGL 都会提供它，我决定此处保留。

        12.1.2.  OpenGL library itself
        OpenGL 库本身
    检查系统上是否装了 libGL ，脚本会检查是否定义了 glXCreateContext 函数

        12.1.3.  glXGetProcAddressARB function
    Wine 的 OpenGL 实现的核心（至少是对于所有的扩展）是 glXGetProcAddressARB 函数，定义了这个函数 Wine 才能支持 OpenGL 。

        12.2. How it all works        它们如何工作
    在 Windows 和 Linux 之间核心的 OpenGL 函数调用是相同的。所以 Wine 中支持 OpenGL 的难点是什么呢？有两个不同的问题。
    1.在不同 OS 中的对于窗口系统的接口是不同的。对于 Linux 叫做 GLX（即相对于 X Window）而对于 Windows 叫做 wgl 。这样首先需要用 GLX 模拟一个 wgl 。
    2. Windows 的（Pascal 或 stdcall）调用习惯和 Linux 中不同（ C 或 cdecl ）。这意味着每个对 OpenGL 函数的调用必须先被翻译，而不能直接的被 Windows 程序使用。
    补充这几个头疼的问题后（在不建立上下文或删除三次相同的上下文的情况下使用 GL 调用）你还有事情要做哦 :-)

        12.2.1. The Windowing system integration        窗口系统整合
    在两个层面上进行集成
    1. 在 GDI 层上对所有 pixel 格式选择任务（选择是否想要一个 depth/alpha 缓冲区，这些缓冲区的大小，等）并在两个缓冲区模式中进行页翻转。这部分在 dlls/x11drv/opengl.c 中实现（所有这些函数都是 Wine 图形驱动函数指针表的一部分，这样如果 Wine 使用 X 外的一个窗口系统，它们可以重新实现）。
    2. 在 OpenGL32.DLL 实现其它函数（上下文创建/删除，查找扩展函数等）。这部分在 dlls/opengl32/wgl.c 中。

        12.2.2. The thunks        替换程序（转换层）
    Wine 的转换层进行调用习惯的翻译，它们通过一个 Perl 脚本自动生成。在 Wine 的 CVS 树中，这些转换代码已经生成。现在如果你想自己处理……
    该脚本在 dlls/opengl32 中叫做 make_opengl 。它需要有 Perl5 才能工作，有两个参数：
    1.第一个是 OpenGL registry 的路径。现在你会问什么是 OpenGL registry？它是 OpenGL 示例实现源码树。(http://oss.sgi.com/projects/ogl-sample/)
    2.第二个是要模拟的 OpenGL 的版本。它固定了 Windows 应用程序可以直接链接的函数列表，而无需从 OpenGL 驱动中查询。当前，Windows 基于 OpenGL 1.1，但是 CVS 树中的转换代码是为 OpenGL 1.2 生成的。
    这个选项可以有三个值：1.0，1.1 和 1.2 。
    该脚本生成三个文件：
    1. opengl32.spec 向 Wine 的链接器提供 OpenGL32.DLL 库中所有函数的签名，这样应用程序可以链接它们。只有核心的函数会列出。
    2. opengl_norm.c 为核心函数提供所有的转换。你的 OpenGL 库必须提供该文件中使用的所有的函数，因为它们在运行时不能被查询。
    3. opengl_ext.c 包含不属于核心函数的所有函数。与 opengl_norm.c 的转换相反，这些函数完全不依赖于你的 libGL 。
    事实上，在使用这些转换层时，Windows 程序首先需要查询函数指针。就这一点，相应的转换是没有用处的。但是由于我们首先在 libGL 中查询相同的函数并在转换层中保存返回的函数指针，后者就变得有用了。

        12.3. Known problems        已知问题

        12.3.1. When running an OpenGL application, the screen flickers        运行 OpenGL 应用程序时屏幕闪烁
    要突破 OpenGL 上下文的限制（在 Windows 中不存在），如果要阻止运行 OpenGL 应用程序（所有的游戏都使用双缓冲上下文）时屏幕闪烁，应该在 ~/.wine/config 文件中的 [x11drv] 段，设置 DesktopDoubleBuffered = Y ，并且使 Wine 运行于桌面模式。


        12.3.2. Unknown extension error message:        未知的扩展错误消息
    扩展定义在 OpenGL 库中，而不是 opengl_ext.c , Please report (lionel.ulmer@free.fr) !
    也就是说，在 Linux 上应用程序使用的扩展功能在 libGL 中（例如调用 glXGetProcAddressARB 返回非空指针），但是这个函数在 Wine 的扩展注册中找不到。
    可能由于下面的两个原因导致的。
    1. 文件 opengl_ext.c 太陈旧了需要重新生成。
    2. 使用被废弃的扩展功能，它们不再被 SGI 支持了或者是“私有的”扩展没有注册。以前有一个例子，在 Quake2 中使用 glMTexCoord2fSGIS 和 glSelectTextureSGIS （在旧版本的 Half Life 中也用）。如果有这些函数的文档，它们就会被加到 Wine 的扩展中。
    如果遇到了这些问题，运行：WINEDEBUG=+opengland 并把 TRACE 发送给 <lionel.ulmer@free.fr> 。

        12.3.3. libopengl32.so is built but it is still not working        libopengl32.so 已经创建了但还是不能用
    这个问题可能是因为 opengl_norm.c 缺少一些函数，Linux OpenGL 库没有提供。
    可以这样检查：
    1. create a dummy.c file :
        int main(void)
        {
            return 0;
        }

    2. 编译，然后同 libwine 和 libopengl32 一起链接。
        gcc dummy.c -L/usr/local/lib -lwine -lopengl32

    3. 如果它能工作，问题可能在其他地方。如果不能，应该重新生成 OpenGL 1.1 的转换层。

        Chapter 13. Outline of DirectDraw Architecture        DirectDraw 架构的概况
    关于体系结构的摘要。忽略了多数细节，还希望能有帮助。

        13.1. DirectDraw inheritance tree        DirectDraw 层次关系树
    Main
     |
    User
     |-----------\
    XVidMode    DGA2

    大多数的 DirectDraw 功能在一个通用的基类中实现。继承的类负责提供显示方式功能（Enum, Set, Restore），GetDevice 检验和内部函数用来创建主平面和后台缓冲平面。
    User 为基于绘图的 DirectDraw 功能提供必要条件。它使用 User DirectDrawSurface 来实现主平面和后台缓冲平面。
    XVidMode 使用 XFree86 VidMode 扩展来设置显示分辨率以便和 SetDisplayMode 相匹配。
    如果使用独占全屏协作模式显示，DGA2 使用 XFree86 DGA2.x 扩展来设置显示分辨率和对帧缓冲进行直接访问。如果不是，它只使用 User 实现。

        13.2. DirectDrawSurface inheritance tree        DirectDrawSurface 层次关系树
    Main
     |--------------\
     |              |
    DIB        Fake Z-Buffer
     |
     |------\---------\
     |      |         |
    User   DGA2   DIBTexture

    Main 提供一个很简单的基类，它没有任何与图形相关函数的实现。因此，它对于如何保存平面数据没有任何限制。
    DIB 在一个 DIB 区域保存平面数据，它被 Main DirectDraw 驱动用来生成离屏（off-screen 也有译作下屏，去屏）平面。
    User 位User DirectDraw 实现了主平面和后台缓冲平面。如果是主平面，会尝试保持和窗口同步。
    DGA2 平面要了一块适当的帧缓冲空间，让 DIB 建立在它上面。
    假的 Z-Buffer 平面被 Direct3D 用来表示一个有 z-buffer 的主平面。作为第一个实现，它只是一个占位符，不需要保存任何图形数据。
    （实际上在主平面，后缓冲区和 z-buffers 上 3D 程序很少使用 Lock 或 GetDC，所以我们需要为 DIB 区的分配作安排）

        13.3. Interface Thunks        接口替换程序（转换层）
    仅最近版本的接口需要实现。有转换层的其它版本转换它们的数据和调用 root 版本。
    不是所有版本的接口都有转换层。一些版本可以结合起来，因为它们的参数是兼容的。例如，如果一个结构改变但是该结构有一个 dwSize 域，使用该结构的方法就是可兼容的，只要实现部分记得考虑 dwSize 。
    由于不同版本间的样式发生了变化 Direct3D 的接口转换非常复杂。

        13.4. Logical Object Layout        逻辑对象层
    对象被分为两种，一种是普通的（本质上是为 Main 设计的域）,另一种是私有的。这是必要的，因为一些对象可以用 CoCreateInstance 来创建，然后再初始化。仅在初始化时我们才知道使用了哪个类。除了 Main 的每个类都声明了一个 Part 局部结构体并将它们添加到类的实现中。
    例如，DIBTexture DirectDrawSurface 实现会是如下样子:
        struct DIBTexture_DirectDrawSurfaceImpl_Part
        {
                union DIBTexture_data data; /*declared in the real header*/
        };
        typedef struct
        {
                struct DIB_DirectDrawSurfaceImpl_Part dib;
                struct DIBTexture_DirectDrawSurfaceImpl_Part dibtexture;
        } DIBTexture_DirectDrawSurfaceImpl;

    这样 DIBTexture 平面类就从 DIB 平面类中衍生出来，它添加了一点数据，一个 union。
    Main 没有 Part 局部结构体。它的域保存在 IDirectDrawImpl/IDirectDrawSurfaceImpl 。
    想要访问私有数据，可以这样：
        DIBTexture_DirectDrawSurfaceImpl* priv = This->private;
        do_something_with（priv->dibtexture.data）;

        13.5. Creating Objects        创建对象
    类有两个和创建对象相关的函数，Create 和 Construct 。为了创建新对象，类的 Create 函数被调用。它为 IDirectDrawImpl 或 IDirectDrawSurfaceImpl 还有用于衍生类的私有数据分配足够的内存空间，然后调用 Construct 。
    每个类的 Construct 函数都调用基类的 Construct 函数，然后进行必要的初始化。
    例如，用 user ddraw 驱动创建一个主平面调用 User_DirectDrawSurface_Create 函数，该函数为对象分配内存空间，并调用 User_DirectDrawSurface_Construct 对对象进行初始化。User_DirectDrawSurface_Construct 调用 DIB_DirectDrawSurface_Construct，而 DIB_DirectDrawSurface_Construct 会调用 Main_DirectDrawSurface_Construct 。

        Chapter 14. Wine and Multimedia        Wine 与多媒体
    下文描述 Wine 的多媒体层的实现。
    代码在 dlls/winmm/ 目录（及其子目录）中，也有一些在 dlls/msacm/ （音频编码解码管理）和 dlls/msvideo/ （视频编码解码管理）目录中。

        14.1. Overview        概况
    多媒体处理分为 3 个层次。底层（设备驱动），中层（MCI 媒体控制接口指令）和顶层抽象层。最底层还有一些辅助 DLLs (MSACM/MSACM32 and MSVIDEO/MSVFW32 pairs)。
    所有的这些组件都是一个个的 DLL 。
    最底层依赖硬件和 OS 服务（例如 OSS）。它使用最好的模块(audio/midi streams...)支持回放／记录。
    中层（MCI）和顶层必须是硬件和 OS 服务无关的。
    MCI 层比较基础的操作（例如，回放 Midi ，播放视频）。

        14.2. Multimedia architecture        多媒体的架构

        14.2.1. Windows 95 multimedia architecture        Win95 多媒体的架构

                |                  Client applications
                |
                |           | |         ^ ^       | |          | |
                |        16>| |<32   16>| |<32 16>| |<32    16>| |<32
       Kernel   |           | v         | |       | v          | v
       space    |      +----|-----------|---------|------------|-------+
                |      |    |           |         |            |       |  WinMM.dll 32 bit
                |      +----|-----------|---------|------------|-------+
                |           | |         | ^       | |          |
                |  +------+ | |<16      | |       | |<16       |
                |  |   16>| | |         | |       | |          |
                |  |      v v v         | |       v v          v
                |  |   +---------------+---+-------------+-------------+
                |  |   | waveInXXX     |   | mciXXX      | *playSound* |
                |  |   | waveOutXXX    |   |             | mmioXXX     |
                |  |   | midiInXXX     |   |             | timeXXX     |
                |  |   | midiOutXXX    |   |             | driverXXX   |
                |  |   | midiStreamXXX |   |             |             |  MMSystem.dll
                |  |   | mixerXXX      |   |             |             |     16 bit
    +--------+  |  |   | auxXXX    +---+   +---+ mmThread|             |
    |MMDEVLDR|<------->| joyXXX    | Call back | mmTask  |             |
    +--------+  |  |   +-----------+-----------+---------+-------------+
        ^       |  |          |      ^    ^       | ^
        |       |  |       16>|      |<16>|    16>| |<16
        v       |  |          v      |    |       v |
    +--------+  |  |   +-------------+    +----------+
    |  VxD   |<------->|    *.drv    |    | mci*.drv |
    +--------+  |  |   +--------------+   +-----------+
                |  |    |  msacm.drv  |    | mciwave  |
                |  |    +--------------+   +-----------+
                |  |     | midimap.drv |    | mcimidi  |
                |  |     +-------------+    +-----------+
                |  |    Low-level drivers    |    ...   | MCI drivers
                |  |                         +----------+
                |  |                               |<16
                |  +-------------------------------+

    有重要的两点要注意：
    * 所有的驱动程序（和多数核心代码）都是 16 位的。
    * 所有的硬件（或者其中大多数）依赖内核中的代码（别惊讶）。

        14.2.2. Windows NT multimedia architecture        Windows NT 多媒体架构
    Win98 混合了 95 和 NT 的架构，因此当讨论 Windows 95 的架构时，指的是架构的类型而不是实现。比如，Windows 98 就实现两种架构。
    有几点要注意（相对于 Windows 95 的架构）：
    * 驱动程序（底层，MCIs ...）是 32 位的，并且是 Unicode 的。
    * 核心和用户驱动程序之间的接口变化了，但是这没有影响 Wine 。这些变化带来一些好处（例如核心混音，不同的应用程序可以共享音频硬件）当然也有坏处（例如核心混音，它增加了延时）。

        14.2.3. Wine multimedia architecture        Wine 多媒体架构

    Kernel space |                    Client applications
                 |
                 |           | |         ^ ^       | |          | |
                 |        16>| |<32   16>| |<32 16>| |<32    16>| |<32
                 |           | |         | |       | |          | |
                 |  +------+ | |         | |       | |          | |
                 |  |32/16>| | |         | |       | |          | |
                 |  |      v v v         | |       v v          v v
                 |  |   +---------------+---+-------------+-------------+
                 |  |   | waveInXXX     |   | mciXXX      | *playSound* |
                 |  |   | waveOutXXX    |   |             | mmioXXX     | WinMM.dll
                 |  |   | midiInXXX     |   |             | timeXXX     |   32 bit
                 |  |   | midi