博客首页 注册 建议与交流 排行榜 加入友情链接
推荐 投诉 搜索: 帮助

自由梦想,自由飞翔

Free fly,Free sky!
rizi00.cublog.cn


Wine技术
Wine 架构 文档 中文翻译

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 。
下面让我们更进一步的了解一下这些组成部分。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#2
发表于 2006-7-26 01:14 PM 资料 短消息
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/ 子目录中。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#3
发表于 2006-7-26 01:15 PM 资料 短消息
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 可以取消它们的映射关系,留出了空间我们稍后可以进行初始化。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#4
发表于 2006-7-26 01:17 PM 资料 短消息
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 也用来执行这些任务。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#5
发表于 2006-7-26 01:17 PM 资料 短消息
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(用于阻塞)。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#6
发表于 2006-7-26 01:19 PM 资料 短消息
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 系统相当的灵活(如果你想,你可以添加新类型的文件系统)。你可以为所有对象提供一个独一无二的名字空间,大多数操作归结为在不同对象之间创建联系。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#7
发表于 2006-7-26 01:21 PM 资料 短消息
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 驱动器的。在开始之前需要了解一些基本的操作。
顶部
limengzhuo
新手上路
Rank: 1



UID 59
精华 0
积分 6
帖子 88
阅读权限 10
注册 2004-10-29
状态 离线

#8
发表于 2006-7-26 01:22 PM 资料 短消息
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 什么也

 原文地址 http://www.manniu.net/article/show.php?itemid=28
发表于: 2007-12-02 ,修改于: 2007-12-02 21:44,已浏览272次,有评论0条 推荐 投诉


网友评论

发表评论