深入探究视窗及保护模式

>>>  名人論史——近當代作家的史學觀點  >>> 簡體     傳統

发表日期 : 1994.01
 

在老大哥已经占稳位子的情况下,
Windows Internals 这本书如何和 Undocumented Windows 区隔 ?
DOS and Windows Protected Mode 比起 Extending DOS 又如何 ?

另外,为免杂志期刊方面有遗珠之憾,
我补介绍了 WDDJ。

嗨老朋友,我回来了。正如去年一样,我们在开春见面讨个好采头。年关将届,趁阮囊不那麽羞涩之际买两本书读读,最能保值。来年人家晒棉被,我们也可以大剌剌地挺着个肚皮说 : 我哂书。

向来出现於此专栏的书总是褒多於贬。书评而不言人之短,可不是失了风骨吗 ? 非也非也,事实上烂书上不了这个版面,篇幅宝贝的紧。我总是希望选择一些重量级好书甚至经典之作,推荐给读者。

这个园地非常欢迎朋友们一起来发表对书籍的看法。一般我们总认为比人家好才敢评人家,可是部会首长天天有人被民意代表骂的满头满脸,也未见有曾质疑骂人者能力是否比被骂者好。我想批评与德高望重牵扯不上关系,大家借本园地分享读书心得罢了,心里不必有压力,放轻松向前行。对书籍的欣赏范围可以从初学入门到专家等级,从应用软体到系统程式,从网路到多媒体到电脑语言到绘图模拟...。有读书心得的人快来。

今天介绍给各位的是两本书和一本杂志。书籍方面是偏系统层面的Windows Internals 和 DOS and Windows Protected Mode,杂志则是补去年五月份的遗珠之憾 : Windows/DOS Developer's Journal。

背景资料 :
书名 Windows Internals
作者 Matt Pietrek
出版 Addison Wesley
页数 8 章,525 页
售价 US$ 29.95 (无磁片)

1. The Big Bang : Starting Up and Shutting Down Windows
2. Windows Memory Management
3. Starting a Process : Modules and Tasks
4. The Windowing System
5. The Graphics Device Driver Interface (GDI)
6. The Windows Scheduler
7. The Windows Messaging System
8. Dynamic Linking

windows-internals.jpg (20146 bytes)

任何人看到这本书,再看到作者名字,恐怕都和我一样产生疑惑 : 此书和 Undocumented Windows 有何区别又有何关系 ? 作者以及编者 (Andrew Schulman) 都面对这疑惑提出了本书的目标设定 : Undocumented Windows 一书探讨的是没有出现在正式说明文件上的资料,本书探讨的则是 Windows 内部详细的工作情况。

诚然,Windows 是如此巨大的产品与复杂的工程,Microsoft 不可能在其文件中交待清楚所有细节。一个设计良好的程式介面原本应该是一个不必让程式员担心的黑盒子 (只有自许为 Kernel-Guy 的人,黑盒子才不能够满足其求知欲),但Windows API 设计还不够好到成为一个让你全然放心的黑盒子。如果程式员不知道详细的内部构造,恐怕不容易在黑盒子上耍枪弄棒。时光渐渐过去,程式员渐渐成长,我们开始对 How 感到不足而想知道 Why 了,这就是本书要给我们的东西 : Windows 内部如何运作。它不谈 Windows 手册上已有的资讯,它谈「新资讯」。如何才能获得手册上没有记载的资讯 ? 当然最好的方法就是去看原始码,流行在 hacker 之间有一句话叫做 UTSL : "Use The Source,Luke" (注)。

注 : UTSL 是「星际大战」影片流传下来的双关语,片中老者对主角Luke 说 : "Use the Force, Luke"。

至於什麽是 hacker,微电脑传真 1993/03 洗镜光先生在「漫谈Micro」专栏中自 The New Hacker's Dictionary 一书节录数种定义。洗先生译得非常传神 :

o. 喜欢探究系统细节以加强系统能力而且乐此不疲的人
o. 能够欣赏「投入精力进行似乎毫无用处之工作」的人
o. 写程式速度很快的人
o. 某个系统的专家
o. 任何类型的热心者或专家
o. 喜欢用创意克服挑战的人
o. 带有恶意,到处探索以发现敏感资料的人

顺带一提,如果您已看过本书的序 (您应该看过的,本书读者如果没有看书先看序的习惯,我会非常非常惊讶),当然您也就看到了hacker 之外的另一个名词 : bogus。以下我再摘录洗先生译自The New Hacker's Dictionary 的精采定义 :

o. 没有效用的,比如说「你对程式的修正是 bogus」。
o. 没有用,比如说「ABC 是个 bogus 系统」。
o. 错的,「你的论证根本是 bogus」。
o. 不正确的,「你的演算法是 bogus」。
o. 无法致信的,「你证明了 Halfing Problem ? 那根本就是 bogus。
o. 愚蠢的,「请不要再写这些 bogus 的故事好不好 ?」

 

看原始码当然是不错,问题是 Windows 的原始码刻正锁在美国 WA, Redmond (Microsoft 总部所在地) 的保险库里吧 (搞不好就在Bill Gate 的桌下)。虽然大家已经在热烈讨论 Windows NT 原始码应不应仿效UNIX 那样开放出来,这并未波及 Windows 3.x (桌上级和工作站级的东西引起的关注毕竟还是不同)。我们唯一获得的 Windows 原始码大概就只是 SDK 磁片上的 defwnd.c 和 defdlg.c (这是 DefWindowProc() 和 DefDlgProc() 的原始码),以及 DDK 磁片中的一大堆驱动程式原始码。

那麽作者如何获得比你我更多的资讯呢 ?

电脑软体的律法告诉我们 : 任何人有权对其购买的软体产品进行反组译逆向工程(但是我要声明我不知道获得的结果可以拿来怎麽用),作者 Pietrek 在这一领域是个中翘楚。本书的工具是一个他自己开发的 WINDIS 反组译器,把获得的结果再以 C 虚拟码 (pseudocode) 表现出来。我们在书中看到许许多多 Windows API 函数的虚拟码都是这麽来的。Pietrek 还有一个产品叫做 Bound Checker,放在 SOFT-ICE/W 中 (功能强大的Windows Debugger,以企鹅为形象,很有名);本书编辑 Schulman 也有一个产品Windows Sourcer,专做反组译工作。所以这一组人马为何有功力写出Undocumented Windows、Windows Internals、Undocumented DOS、DOS Internals 等书也就令人恍然大悟了。

本书主要探讨 Windows 3.1 386 加强模式,必要时也会特别提及标准模式以及 Windows 3.0。作者对於许许多多的 Windows 版本和你我一样深感困扰,像是 Windows 3.0/3.1,Windows For Workgroups,Win32s,标准模式/加强模式,英文版/远东版 ...,所幸 Windows 的核心在各种版本中差不多一致。本书没有涵盖虚拟驱动程式、虚拟机器、网路 API、多媒体能力、DDE/OLE、dialog/control 等主题,从目录我们可以发现,主题集中在 Windows 启动程序、记忆体管理系统、视窗管理系统、讯息管理系统、排程管理系统、绘图系统等最底层的系统上。

本书对读者有三大要求 :

○ 对 Intel 保护模式、分段体制 (segmentation)、selector 已有基本认识。

○ 拥有 Windows SDK 手册 (Microsoft 或 Borland 的都可以)。

○ 对作业系统有基础观念 (例如什麽是多工,什麽是虚拟记忆体 ...)。

第一章就给一个大炸弹。我想 Big Bang 引喻 "WIN" 敲下之後整部 PC 灵魂 (作业系统) 的重大体质变化是很恰当的。这的确是事实,丑小鸭变天鹅,若不从本质上改头换面何能若此 ? 从敲下 WIN 到 Program Manager 画面出现,作者把其中每一个环节都细细的剖析了,十分精采。WIN.COM 是当你安装 Windows 时由三个小档案组合而成的程式,这三个小档案是 WIN.CNF,VGALOGO.RLE,VGALOGOL.LGO (如果你的显示卡是 VGA 的话);早有许多玩家在 PC-Magazine 上讨论如何在启动画面上动手脚。WIN.COM 中的火线主角 WIN.CNF 虽然拥有奇怪的副档名,其实是 DOS 的 COM 档,它决定 CPU 类别、记忆体管理系统是谁 (有没有支援 XMS、是不是 386 EMM 如 QEMM386 或 386MAX 之流)、Windows 是否已经执行起来了等等。检查完毕後 WIN.CNF 最重要的工作就是载入 DPMI host。Windows 3.0 的 WIN.CNF 并不检查 Windows 是否已经执行,所以你可以在其 DOS Box 中再跑一个真实模式

说起来,能把 DPMI host 和 DOS Extender 划清界线的人不是很多。DPMI 是一组服务常式,提供给 DOS Extender 使用。DPMI host 照顾的是记忆体管理、中断、exception 处理、DOS Extender 的真实模式/保护模式转换动作;DOS Extender 则是在 CPU 真实模式/保护模式之间转换,以便把资料交给 DOS 处理,并提供一个保护模式 INT 21h 中断服务常式。Windows 的 DOSX.EXE 和WIN386.EXE 两档案就是分别提供标准模式和 386 加强模式服务的 DPMI host,书上对这两个 hosts 有详细比较。基本上 DOSX 除了没有把虚拟记忆体功能含入之外,可说是 DPMI 0.9 的完整实作。这两个 DPMI hosts 大小差了近 500K (DOSX 是 32682 位元组,WIN386 是 544789 位元组),其中不全是因为虚拟记忆体,WIN386 还多了 VxD 和 VMM。

去年一位 Intel 朋友告诉我,现今不流行 DPMI 了。但我认为只要 DOS 还有影响力的一天,DPMI 就不会式微。其实这是一体两面,我也可以说只要 DPMI 还被接受,DOS 就具有强大的影响力。不是吗,只要 Windows、OS/2、Windows NT、UNIX 都还留有 DOS Box,DOS 软体灭绝的忧虑就不至於那麽火烧屁股,作业系统改朝换代也将因此比任何人想像的都慢。

DPMI host 载入之後的重头戏是 KERNEL 模组的载入。KERNEL 模组 (不论 KRNL286 或KRNL386) 在真实模式中启动,那麽似乎山穷水尽疑无路,因为 WIN386 刚刚已经把真实模式切换为保护模式了,不可能让 CPU 在 KRNLx86 载入之前又切回真实模式吧 ? V86 模式这时候登场 ! WIN386 中的 VMM (虚拟机器管理器) 产生一个 VM (称为 System VM),它当然是 (必须是) V86 模式,但和真实模式没有两样,KRNLx86 就在里头使用一些真实模式码和一些 DPMI 服务项目建立起 Windows 环境。所以说穿了这程序是 :

WIN.COM ---> WIN386 ----> KRNLx86 ---> System VM
(真实模式)  (保护模式)     (V86 模式)     (保护模式)

接下来 KERNEL、USER 模组的载入与初始化我就不在这里提了,书中详细的很。

第二章讲记忆体管理,是考验读者基础功力的地方。各章节中以本章最厚,高达 134 页,其中许多是 Windows API 函式的虚拟码。几点关於 Windows 记忆体的迷思,书中都有解释。如果程式配置 200K 记忆体,其实获得的是 64+64+64+8 的组合,因为骨子里 Windows 还是 16 位元作业环境,它以自己的一套 tiling scheme 串连这些 64K 节区。这足以解释为什麽记忆体区块超过128K 之後,需特别注意不让单一资料项跨越 64K 边界。这一章也解释 handle 与 selector 的关系 (Windows 3.0 和 Windows 3.1 作法不同,令人气结),FIXED 记忆体的不可能性 (除非在 DLL 中),以及约略点了一下 LocalInit() 函式,它使你制作 Subsegment Allocation 时方便的多。Subsegment Allocation 是作业系统把己身责任推诿给应用程式的最佳例子,然而时过境迁,8192 的限制已由 C Runtime 函式库中的 malloc() 为我们解决掉了。

过去大家都说 Large 模式不好,不外乎因为记忆体是 FIXED 而影响系统效率,以及只能跑一个 instance 的缺点。造成这种情况一部份是 Windows loader 的责任,一部份是 C 编译器的责任。现在在 Windows 3.1 之下,使用 Microsoft C/C++ 7.0 (或 Visual C++) 以及 Borland C++ 3.x 编译器,都可以不再有前述包袱。Pietrek 把原由解释的非常好,是我看过最好的。这是本章最具实用性的一节讨论。

智力测验 : 自由记忆体还有 4MB,FSR 还有 60%,但就是没办法执行新的程式,为什麽 ? 原因可能是执行中的程式用了过多的 1MB 内的记忆体 (以GlobalDosAlloc() 取得)。一个 EXE 档案要载入成为一个行程时,KERNEL 需为它准备一个 TDB,这个 200h 位元组的结构必须是 FIXED,位在 1MB 之内 (因为 TDB 中含有 PDB,必须能够被 DOS 处理)。所以如果 System VM 的 1MB 记忆体被用的太凶,会影响行程的多寡。

第三章非常详细地介绍了 Module 和 Task。我个人认为这非常非常非常的重要。从 MDB (Module DataBase) 和 TDB (Task DataBase) 甚至 PDB (Process DataBase) 的结构栏位,我们才能了解 Windows 对行程的管理方法。本章最後并有一点点对Win32s 的介绍,但不是很深入。如果你对 Win32s 的运作 (在 16 位元环境中执行 32 位元程式) 感觉兴趣,At Last - Write Bona Fide 32-bit Programs that Run on Windows 3.1 Using Win32s (Andrew Schulman, MSJ, 1993/04) 是一篇非常好的文章。了解 DOS Extender 的人将很容易接受这种 Windows Extender 的观念与设计。

第四章介绍视窗系统。这一章最让我们受益的是可以完全了解 parent/child 视窗关系,owner/owned 视窗关系,parent/owner 视窗的区别,以及 focus/active 视窗的定义。视窗依属性可分为 OVERLAP、POPUP 和 CHILD 三大类 (其中的 CHILD 和parent/child 中的 child 是两回事),前述的 parent 视窗和 owner 视窗则是视窗之间的从属关系。USER 视窗系统可以想像是一个树状管理结构,最根源是Desktop,这是 Windows 产生的第一个视窗,Desktop 视窗上的图画就是 wallpaper (壁纸),你可以在 Control Panel 的 Desktop 中设定。

parent/child 是双向关系,一个视窗可以知道它的 parent 是谁,它的 child 是谁;owner/owned 是单向关系,视窗只知其 owner 是谁,却不知它自己是谁的owner。parent/child 定义的是视窗树中的视窗前後关系 (当然也就牵连到视窗的 Z-order),owner 视窗则是「可以接受 "owned 视窗" 之 notification 讯息」者。所以 parent 视窗和 owner 视窗根本上是风马牛不相及。OS/2 Presentation Manager 允许你在产生视窗时不但指定 parent 也指定 owner,但 Windows 并不维护owner/owner 的关系 (没有这种 API),这是很奇怪的事。如果你杀掉一个视窗,该视窗所拥有 (own) 的视窗也都要被 Windows 杀掉,Windows 的作法是巡访整个视窗树,将每一个视窗的 owner 与「被杀掉之视窗」的 handle 比对,以决定现在这一视窗是不是被杀视窗的 owner。

以下是各式各样视窗定义的一个整理 :

WS_STYLE parent owner
WS_OVERLAPPED
WS_POPUP
WS_CHILD
HWndDesktop
HWndDesktop
hWndParent
hWndParent
hWndParent
0 (notification 讯息
      传给 hWndParent)

(hWndParent 是 CreateWindow() 时设定的一项栏位)

 

可以看出 Microsoft 对於资料或结构栏位的命名根本是乱七八糟,要是能够从Windows 手册中把这些视窗的定义与关系搞清楚才真是奇怪。

什麽又是 focus 视窗呢 ? 可以接受键盘讯息的就是。好,那什麽是 active 视窗 ? 「focus 视窗就是 active 视窗」!? 这是绝大多数人的答案。对一半 !! 事实上active 视窗必须是 top-level 视窗,所以 active 视窗若不是 focus 视窗,就是其 parent。试想想这个 : 在 [File Open] 对话盒中接受档名的 EDIT 是 focus 视窗,但它并不是 active 视窗,active 视窗是 File Open 对话盒本身。所以,按下 Alt-PrtScn 取到的画面是整个对话盒而不只是 EDIT control。

既然提到 ownership,我们不禁要问,在 DLL 中产生的 object (例如以 CreateWindow() 获得的视窗、以 GlobalAlloc() 获得的记忆体) 究归谁属 ? DLL 的角色和一般静态联结函式库其实并无二致,它是一组没有自主生命力的码,必须由 task 呼叫起来才拥有生命。当 task A 呼叫 DLL 时,这 DLL 可视为属於 task A 的一部份码。那麽前面问题的答案就昭然若揭了,显然 DLL 产生的任何 object 都属於当时呼叫 DLL 的那个 task 所有。不少朋友问我为什麽不同的程式透过 DLL 处理档案或记忆体 handle 时得不到预期结果,请想想上项因素。

我不曾在别的地方看过像这一章把视窗的关系说的这麽完整的,我非常喜欢这一章。

第五章介绍 GDI 系统。一开始作者就把系统程式员分为两个阵营,他说一组人马全然享受业馀性质的深度,在 loader,schedules,memory management 等等等中钻研。有时候他们的态度就像是『嗨,我们从磁碟中取出一个档案并为它产生一个 process,我们给了你时程系统 (scheduler) 和记忆体管理,以及把字元放到萤幕上的能力,你还要什麽 ?』。这组程式员给我们的产品就像 UNIX 或 MS-DOS。第二组人马在第一组的肩膀上工作,他们做出像 UNIX 的X-Windows,OS/2 的 Presentation Manager 以及像 Windows 的 USER、GDI 这样的东西。

和 OS/2 不同的是,Windows 不会让软体开发者有不注意图形人机介面的机会。除了极少数例外,你在 Windows 中玩的任何把戏都将很快与作业系统之间有绘图方面的接触。GDI 就是个与设备无关的绘图子系统。和 KERNEL 比起来,GDI 很大。KERNEL 是75490 位元组,GDI 是 220800 位元组。KERNEL 大部份是组合语言码,GDI 大部份是 C 语言码。GDI 的复杂性一大部份是因为设计者考虑到并非所有绘图周边驱动程式都能够实作特殊的绘图函式,换句话说 GDI 必须准备一些其实可能在 Didplay 和 Printer 驱动程式中已经准备好的高阶绘图功能 (如 BitBlt),毕竟 GDI 得做最坏打算。这些高阶功能在聪明的驱动程式代劳之下根本就派不上用场,倒显得GDI 虚胖了。这一章对 GDI 与驱动程式间的关系说的不错,如果读者曾经看过或甚至写过 Windows 驱动程式,读起来当能得心应手。

第六章介绍 Windows 的时程 (Scheduler) 系统,是各章中最迷你的部份,只 24 页。Windows 其实有两个时程系统,一个是 WIN386 中的强制性多工时程系统,一个是 KERNEL 模组中的非强制性多工时程系统,本章讨论的是後者。只有当 System VM 获得了 WIN386 给予的一个 time slice,KERNEL 的时程系统才会开始运作。这一章对於什麽时候可以释放控制权 (或说程式执行权) 有一些观念上的厘清。我们通常认为程式没有讯息时才可能释放控制权,而由於一般程式是以 GetMessage/TranslateMessage 的隐藏形式完成非强制性多工,所以上述观念可以成立。但如果直接使用 Yield() 函式,即使 App Queue 中有讯息,依然可以强制释放控制权。Pietrek 写了一个小程式做实验,饶富趣味。

我向来喜欢把写常驻程式的人称为走钢丝的人,他们要在很危险的环境中保持冷静力图平衡,小心翼翼地不要在三角关系中失控。许多常驻程式员喜欢使用 DOS 的idle interrupt (INT 28h),但很多人不知道 Windows 的时程系统也有 idle loop,会产生 INT 2Fh/1689h。如果你以 PeekMessage() 取代 GetMessage(),抓间隙时间处理背景工作,那麽 idle loop 就会被抑制。

第七章介绍 Windows 的讯息系统。作者剖析讯息的资料结构,讯息的来源分类,讯息的贮存空间 (App Queue 和 System Queue),以及 GetMessage/PeekMessage/TranslateMessage 等几个常用的处理讯息的 API 函式。对於 WM_PAINT 和 WM_TIMER 这两个行径特异的讯息,也对其产生时机和内部 flag 设定花了不少篇幅解释。最後并对 Windows 的输入系统做了一些批判,解释 Windows Debugger 的先天限制以及 Win32 在输入系统上的改良。

第八章介绍动态联结的观念。Petzold 以及 Yao 两位先生对於动态联结所需的instance thunk、reload thunk 和 MakeProcInstance() 等都有很好的讨论(他们的书在 1993/01 本专栏介绍过),本章则更从编译器、联结器的 fixup 开始说明,相当好。如果我们能了解 OBJ 档和 EXE 档的详细格式,读起来一定更能旁徵博引左右逢源,这些资讯可以从 MS-DOS Encyclopedia (Microsoft Press) 书中获得。

作者常借用物件导向的观念解释 Windows,如果你懂 C++ 语言,知道 class/object,知道 member-function、data-member 的意义与其精神,对他的比喻当能心领神会。Pietrek 在第四章以物件导向观念解释 Subclassing,在第五章再以物件导向观念解释 GDI 的 logical device 和 physical device block。第七章以人体循环系统比喻 Windows 的讯息系统,也是出色的说法。

对系统感兴趣的人 (Kernel-Guy),本书一定让你如鱼得水,只怕你唯一的抱怨是 : 一大堆的 API 函式虚拟码令人心烦气燥。而文字瀚海图片沙漠的情形也一再考验读者的定力与耐力。然而小瑕不掩大瑜。我向来认为酿了一瓶好酒的人不必声嘶力竭地广告它,这本书就是一瓶好酒。

作者 Pietrek 自 1993/10 起已登上 Microsoft Systems Journal 的 Windows Q&A 主持人宝座,没两把刷子的人上这位子可是如坐针毡。至於他是因为 Windows Internals 一书引起注意而被重金礼聘或是因为 MSJ 长期培训而有写 Windows Internals 的功力,我不知道 (也不在意)。

朋友们在书店选书的方式如何 ? 看不看序 ? 看不看前言 ? 别抓起书像数钞票般一页页流览,漫无目的的跳跃。从序中可以看出作者的创作心路历程,作者的抱负理想,还可以看出作者的文笔斤两。有些作者的风格如跳梁小丑,哈哈一笑嗤之以鼻之馀可省下买书钱。书序,好看的很呢。大抵你可以从外文书的 Preface 或 Acknowledge 或 Introduction 或 Foreword 看到些类似「序」的东西。Pietrek 在本书最前面的 Introduction 部份提到他感谢的人,其中对於编辑有这麽一段感性谈话 :

首先我要谢谢的,当然是我的编辑 Andrew Schulman。没有他这本书几乎不可能完成。当我们开始为这本书筑梦时,它看起来是那麽令人畏缩可怖。只因为我知道他可以助我一臂之力我才有勇气进行下去。几乎我所写的每一笔资料他都有令人惊讶的丰富知识,而且他也注意不让太多细节扼杀了想像空间。每次当我认为我已经巨细靡遗地涵盖了一整章细部讨论,他会以数百个毫不夸张的意见把我推回原点,促使我完成更详细的讨论。我不能够想像是否还有更好的编辑如他了。

我把这段文字翻译出来,用在提醒国内电脑书籍出版业者,一本技术书籍影响学子之甚超乎想像,出书不能不慎,建立 "peer review" (同僚覆审) 制度非常有必要。许多我所知道的国内电脑书籍出书过程,会让读者骇然。电影有制片人,唱片有制作人,为什麽电脑书籍没有 ? 如何能够叫我们信服一个作者可以「精通」DOS、Windows、PEII、AutoCAD、dBase、Word、Excel ... 十数种大大小小的作业系统与应用软体 ? 如何能够叫我们信服一本五天之内连写带编到出版的书 (说者犹面带得色,真个是为了抢市场先机而践踏书籍尊严) ? 又如何能够叫我们认同竟然可以对同一个主题出几十本书的作者 ? 哪一本是他的心得 ? 哪一本是他的力作 ?

我多麽期盼国内出现审稿制度以及群力制作单位,期盼像 Schulman 这样的专家级编辑。少年当立凌云志,我虽不再年少却也多麽期许自己的学养能够成为那样的编辑。但是话说回来,要一个编辑可不是挂名领钱尸位素餐,如果只为了图作家的知名度,拉来助长声势,那真真是再没意义的事了;如果「名作家」与书的技术层面不搭轧,更是画虎不成反类犬 ! 我已经在国内看到一些这样的产品。

 

背景资料 :
书名 DOS and Windows Protected Mode
作者 Al Williams
出版 Addison Wesley
页数 19 章,503 页
售价 US$ 39.95 (含磁片)

Part I Introduction to DOS Extenders
1. What's a DOS Extender, Anyway ?
2. DOS Extenders : Under the Hood
3. The DOS Extender Tool Box
4. Protected Mode : The Inside Story

Part II DOS Extenders and Windows in Detail
5. Dipping into DPMI
6. Stretching Your Data
7. Segments : Take Them or Leave Them
8. Looking Up and Old Address
9. Interrupts : A Function Call By Any Other Name
10. Return to Real Mode
11. Extending Extenders - Virtual Memory to the Rescue
12. Something Borrowed
13. Handling User Exceptions - Expect the Unexpected
14. Windows (and DOS) Times 32
15. Protected Performance

Part III Big Problems, Big Solutions
16. Welcome to the Real (Protected) World
17. A DOS Extended Mutant Turtle
18. A 386 Disk Duplicator
19. Windows, DOS, and You

Appendix A Glossary
Appendix B DOS Protected-Mode Interface (DPMI)
Appendix C Manufacturers of DOS Extenders and Related Tools
Appendix D 286|DOS-Extender Lite User's Guide
Bibliography

dos and windows protected mode.jpg (13728 bytes)

去年四月我介绍了一本 Extending DOS,是讨论 Intel CPU 保护模式及相关软体环境的重量级作品。今天我们看看另一本好书。

在作业系统改朝换代烟硝四起的今天,对於 DOS 的未来,许多人有许多种不同的看法,有人认为 DOS 必须死,有人认为 DOS 来日方长 (请叁考 PC Magazine 1992/10/13 的 DOS Lives,中文版 1993/01 的 DOS 运命知多少)。我在去年四月的无责任书评中引述两位专栏名家 Jeff Prosise 和 Charles Petzold 的话饶富趣味。其实这些作家有时候是为了阐述某一种技术观点而发表对市场领域太过侵略的言论,我们抱持着欣赏而非信仰的态度是比较健康的。经目之事犹恐未真,背後之言岂能全信,在一场精譬笔战中筛选出各种论点之成立依据,再佐以自己的判断,才能让我们随之成长。迥异两派人马中,显然本书作者 Williams 是 DOS 保皇党的一员 (至少他在书中的论调是如此)。

Windows 3.0/3.1 之前早就存在 DOS Extender,Windows 的出现只是加速这一保护模式潮流的前进。今天在一波一波好看亮丽的 Windows 应用软体推波助澜下,我们更应了解 Windows 背後的 DOS Extender 保护模式技术。DOS Extender 是允许 DOS 程式配置大量记忆体的一层迷你作业环境,一个 32 位元 DOS Extender 尤其能够打破长久以来 16 位元环境的 64K 记忆体节区的桎梏 (这一点连Windows 3.1 也做不到),不再需要 huge 指标。

看到 DOS Extender 有时候我们不要想它是个产品,要想它代表一种技术。如果 DOS Extender 以产品的形式存在,那麽焦点不在其能力是否比 Windows 强,而在程式发展时的透通性。EMS/XMS 技术可以增强 DOS 程式的记忆体能力,但使用起来不够透通。DOS Extender 提供的是 DOS-Like 的程式写法。除了动态记忆体配置,EXE 档案本身也可以扩充超过 1MB。

本书多达十九章,每一章都十分简短。第一章即以一个需要大量记忆体的程式为例,说明 (1) DOS 版本 (2) EMS 版本 (3) DOS Extender 版本的差异。透通性使程式只要重新编译联结 (甚至只要重新联结),就可以享受大量记忆体。然而如果「完全」透通而不是「大部份」透通,这本书大概可以省去一半篇幅。正因为使用指标以及第一个百万位元组时都需付出特别的关注,所以後续许多章节才会针对不同的 DOS Extender 示范不同的程式写法。

第二章对 DOS Extender 有概略性介绍。第三章介绍 DOS Extender 的相关工具,包括 DOS Extender 产品、32 位元编译器联结器等系统软体的产品名称、厂商名称。其中较知名的软体国内都有得买,不妨注意一下软体银行和软体贸易商、代理商的产品目录。

第四章介绍 Intel 保护模式的内部架构,这是难度比较高的一章。你猜对了,本章从 selector、descriptor 说起,谈到 LDT/GDT/IDT,以及 DPL (Descriptor Privilege Level)。藉着 DPL 系统可收保护之效,但有些DOS Extender 会破坏这一层保护,为的是简化其本身设计。这种 DOS Extender 就不能有效保护系统免受应用程式的不当侵扰。本章也介绍保护模式中的 TSS (Task State Segment),它用来支援多工。TSS Descriptor 指出记忆体中的一块区域,内有各暂存器的影像。一般讨论保护模式的文章中甚少对 TSS 眷顾。本章还提到 gate,如果读者对 call gate 感兴趣,1993/05 的 MSJ 有一篇 : Run Priviledged Code from Your Windows-based Program Using Call Gates (作者 Matt Pietrek 正是 Windows Internals 的作者)。所谓 gate 是 GDT 或 LDT 或 IDT 中特别种类的 descriptor,内含 CS:(E)IP,是即将转换控制权的目的地。亦等於说 gate 是一种特别的 descriptor,定义一个函式。

想深入研究 DOS Extender,当然最好的方法是 UTSL (还记得这个名词吗,看看本文前面的注),有三个管道可以获得 DOS Extender 原始码。第一是 GNU C++,这是一套完整的工具,包括 C/C++ 编译器,组译器,符号除错器,一个支援虚拟记忆体和 VCPI 规格的 DOS Extender,它是一个 ShareWare;第二个来源是作者在他的另一本书 DOS 6 : A Developer's Guide 中的一个例子 (这本书的纸张有够糟糕,在上面轻轻写字,就可以有力透纸背入木三分的效果);第三个来源是 Ray Duncan 於 1991/03/12 发表於 PC Magazine [Power Programming] 专栏中的 Creating a DPMI Based DOS Extender of Your Own 一文所附的例子,这个例子因为有 DPMI 的帮助而非常非常小巧。像我这样的业馀人士只因好奇想一探DOS Extender 实际长个什麽样子,不妨选择第三者。

第五章介绍 DPMI。你可以把 DPMI 的 INT 31h 想像是对保护模式 DOS 的标准介面,就像 INT 21h 是对真实模式 DOS 的标准介面一般。这一章提供一个使用於全书的程式架构。作者写了一个 Windows 空壳程式,使用者按下功能表中的 "Run Test Program" 时固定会去呼叫一个名为 test() 的函式,这个 test() 就是程式员的表演舞台。文中有一段方块特别解释在 Windows 程式中不要使用 Microsoft C 的 int86x() 函式,我自己才疏学浅还不能完全领会 (因为我曾直接用过,好像没什麽问题),读者请多注意。

第六章介绍在 16 位元和 32 位元环境下资料大小的差异。关於这一部份 Charles Petzold 曾在 1992/07 的 MSJ 中有一篇深入讨论 : The Case for 32 Bits,可辅助叁考。

指标大小以及 size_t 大小通常我们会注意,比较为人疏忽的是资料的 alignment。下面这个例子 :

struct {
  short id;
  long type;
  char name[9];
  long info;
  char c1;
namelist[10000];

虽然整个结构只需 20 个位元组,但因在 32 位元环境中编译器预设的 alignment 为DWORD,所以每一个结构实际耗掉 28 个位元组,编译器会对每一个不足 4 位元组的单元补足为 4 位元组 (但你的程式感觉不到)。10000 个元素的阵列将因此浪费 80000 位元组。如果把资料设定为 byte alignment,就不会有任何浪费。不仅仅空间浪费是个问题,如果你以 DOS 中断获得 MCB (Memory Control Block)放到上述这个结构中会出错,因为真正的 MCB 是 20 个位元组,不多也不少。

虽然在 DOS Extender 环境中写程式是透通的,但关於 32 位元编译器带来的特性还是不能不注意。至於如何设定资料的 alignment,视编译器而定。本章详细列出Borland、Microsoft、Intel、WATCOM、MetaWare 等知名厂商的设定方式。这一章也提纲契领地说明了提高程式移植性的设计要领。

第七章讲节区 (segment)。Intel 的节区架构一直是增加程式迷惑性与困难度的大帮凶。Motorola 680x0 那样单纯的线性定址方式大概是 PC 程式员的梦想。DOS Extender 提供的 Flat Mode 就是类似的线性定址方式,这种模式中节区可以成长为4GB,不再需要什麽 near/far/huge 指标,全部都是 32 位元指标。不过 Flat Model 带来的困扰是系统容易被破坏,就好像真实模式中的 DOS 一样,因为它失去了 segmentation 的保护层。Intel 的Code Builder 是这种情况的代表。所以即使一个节区可达 4GB,有些 DOS Extender (如 Phar Lap) 仍然支援 Segmentation,视讯缓冲区和第一个百万位元组等重要区域仍然被区隔到单独的节区中。本章的讨论也涵盖 Windows。

第八章主题是如何使用 1MB 内的记忆体。不同的 DOS Extender 有不同的作法,像 Intel 的 Code Builder 是把 1MB 位址对映到应用程式位址空间中的第一个 1MB,所以 pointer = segment << 4 + offset;Rational 4G/W 则是令 1MB 从 0 开始,令你的程式从4MB 开始 (这里的 4MB 是逻辑位址,机器并不需要真有4MB 才能运作);GNU C++/386 的 GO32 (也是个 DOS Extender) 让你的程式从 0 开始,让 1MB 从 0xE0000000 开始;Phar Lap 的 386 Extender 作法是提供特定的 0x37 节区指向 1MB 位址,因此要获得一个指向视讯缓冲区的指标可以这麽做 :

char _far *ptr;
FP_SEG(ptr) = 0x37;
FP_OFF(ptr) = 0xB8000;

Windows 程式呢 ? 可以使用 KERNEL 预先定义好的一些 selector,或是使用LDT API 如 AllocSelector()、SetSelectorBase()、SetSelectorLimit()。

第九章介绍中断。中断在 PC 中扮演重要角色,PC 使用它支援 I/O 设备,以及系统呼叫。不同的 DOS Extender 和不同的编译器有截然不同的中断处理技术。有一种DOS Extender 是把 CPU 切回真实模式再处理中断,这种我们称为 Switcher Extender;另一种是在 V86 模式中模拟真实模式处理中断,这种我们称为 V86 Extender (例如 Windows)。後者总是在保护模式中处理中断,所以它只需要一套中断向量,在处理过程中再决定要不要呼叫真实模式的中断服务常式。

第十章介绍真实模式的回返动作。通常当你呼叫一个 DOS Extender 不支援的中断(例如 DOS 未公开函式),或是呼叫一个不容易转换为保护模式码的既存函式库(可能是买来的),或是为了增进效率,就会考虑使用真实模式中断服务常式。真实模式码分为常式与中断两种,前者的叁数以堆叠传递,比较麻烦;後者的叁数靠暂存器传递,比较容易些。我们在 Windows 程式中呼叫 int86() 其实是由保护模式的中断服务常式服务,它做好叁数转换之後才 (可能) 呼叫真实模式的中断服务常式。

第十一章以很少的篇幅介绍虚拟记忆体。第十二章介绍 stub 程式以及动态联结函式库。DOS Extender 的 EXE 通常是仿效 Windows 或 OS/2 的 NE 档或 LE 档格式,把保护模式码和真实模式码 (也就是 stub) 放在一个 .EXE 档案中。书中举了一个例子,是在 Rational 4GW 中自行设计 stub,在 stub 中呼叫 DOS Extender 执行起保护模式码,并把原来的命令句叁数加上一个新的叁数,传给保护模式码。实用上这个新叁数可以放一个真实模式位址或是一个中断号码,做为让保护模式码呼叫真实模式常式的依据 (与第十章有关系)。

动态联结函式库是 DOS Extender 除了 stub 之外另外从其他作业系统(主要是 OS/2 和 Windows) 借来的观念。Extender DLL 的观念与Windows DLL 是一样的,制作时必须叁考你手上的编译器手册和 DOS Extender 手册。它们的不同在於多工,Windows 是多工环境而绝大部份的 DOS Extender 不是,所以设计 Extender DLL 时就不必考虑为每一个 client 配置各别空间。

十三章解决 User Exception 和 Critical Error。前者由 Ctrl-C 或 Ctrl-Break 引发,後者与装置有关,会带给使用者 "Abort, Retry, Fail ?" 讯息。这些通通都是exception。一般而言 C 函式库会提供一些处理函式,像是以 signal() 和harderr() 分别设定处理 user exception 和 Critical Error 的新函式,以及以 onexit() 设定一个程式结束前呼叫的函式。某些 DOS Extender 也提供对应的函式,像 Phar Lap 的 286 Extender 就有个类似 onexit() 的 DosExitList()。如果要更低阶处理,也可以直接拦截 0x1B (Control-Break),0x23 (^C),0x24 (critical error) 等中断。前面的方法如 signal() 并不能避免萤幕上出现 ^C 字样,如果要避免它就只好寻求更低阶的手法。书中对这些更低阶的处理有实际程式例。

十四章介绍 32 位元 DOS 与 Windows,但关於 32 位元 Windows 并非介绍Windows NT,而是 Win32s 环境 (或许有那麽一天 Windows NT 会成为执政党,但目前当家主政的是桌上型 Windows)。把 DOS Extender 的观念应用到 Windows 上就成了 Win32s (你可以说它是一个 Windows Extender)。我们不要忘记,Windows 3.x 的本质是 16 位元,使用 Win32s API 的好处只是程式员得享 32 位元API,很方便,实际执行效率并不若单纯的 16 位元程式那麽好,时间会浪费在「转换」(thunk time) 上面。在 thunk 之间转换控制权就像 DOS Extender 切换 CPU 模式一样。Win32s 的缺点当然是它没有全部实作出 Win32 API,但缝隙多少已被其他系统厂商弭补,例如 Phar Lap 的 TNT 32 位元 DOS Extender 就提供了Win32s 所没有的 Win32 Console API。只要多付出一些注意,我们就可以写出一个在 Windows NT、Windows 3.1、DOS (Extender) 环境下都能执行的 32 位元程式。

十五章旨在如何增进 32 位元执行效率。方法包括尽量不要改变节区暂存器、使用正确的 alignment、使用暂存器变数、尽量避免模式转换、以及不要放弃在 386 CPU 中一次处理 4 个位元组资料的权利 (这是386 CPU 最自然最快速的状态)。集掖成裘聚沙成塔,结果是很可观的。

十六章给那些想进入保护模式真正写个软体而不只是玩玩的人一些重点整理,并对後三章的个案做摘要说明。这三个个案分别是十七章的 TURTLE,一个在 Phar Lap 286|DOS-Extender 环境中跑的图形命令语言 (类似 Logo)。由於保护模式提供大量记忆体,Turtle 动用了 11 个图形缓冲区,每个大小是 64K (因为这个程式在 320x200x256 VGA 上跑)。第二个个案是十八章的 CB386,这是 Intel Code Builder 制作出来的程式,可以把目前软碟上的磁片内容全部读到记忆体中,有点类似 2.88 MB 的软碟驱动程式;第三个个案是十九章的 Windows 程式,主要目的是实验DOS 程式与 Windows 程式之间的沟通,让一个名为 WINRUN 的 DOS 程式利用名为WINX 的 Windows 程式机能,在 DOS Box 中启动 (launch) 任何 Windows 程式。由於 Windows 的普及程度,我想第三个个案最能引起你的注意,何况它还探索到虚拟机器之间的通讯。我决定把这个设计架构说清楚一点。

WINX 有两部份,一是 WINX.C,这是 Windows 程式码,一是 WINXDOS.C,这是 DOS 程式码,被放在 WINX.EXE 中做为 stub 程式。你可以在 DOS 提示号下键入 "WINX" 取代键入 "WIN",这时候 WINX.EXE 中的 WINXDOS 首先执行,安装好一个新的 INT2Fh 中断服务常式并维护一块 1MB 之内的记忆体缓冲区,然後以 spawnpl(P_WAIT,...) 执行 win.com。之後 WINX 在 Windows 环境中跑起来,设定计时器,等待记忆体缓冲区随时可能进来的字串 (被视为命令)。另一方面 DOS 程式WINRUN.EXE 在 DOS Box 中被执行时会呼叫 INT 2Fh 把命令列叁数放到缓冲区中,於是被 WINX 取得,改头换面成为 WinExec(...)。结果是 : 使用者可以直接在 DOS Box 的DOS 提示号下执行一个 Windows 程式。另一个根据 WINRUN 改装的程式 RUNSTUB.EXE 则可当做任何一个 Windows 程式的 stub,这样获得的 EXE 程式将可以直接由使用者在DOS Box 中键入档名後获得执行 (当然必须是 WINX 已安装好的情况下)。设计理念十分精采。整组程式以最新的 MSC 8.0 亦可顺利制作完成 (MSC 8.0 即 Visual C++)。

附录B 是关於 DPMI 的详细内容。如果你想要完整的 DPMI 规格,应该去函 Intel,可免费获得一份资料。此处列出每一个DPMI 函式的使用方法,包括呼叫前暂存器要怎麽用,呼叫後的暂存器内容代表什麽意义。对於实用目的而言已是十分齐备。只是...我发现了一个致命错误,图B-1 的 Real Mode Register Structure 少了 EAX 4 个位元组,你若浑然不觉依该图设计程式,一定很惨。这张图将用於 DPMI 0300h 至 0303h 四个函式(DOS 与 Windows 沟通时很重要的四个函式) :

■ DPMI 0300h : Simulate Real Mode Interrupt
■ DPMI 0301h : Call Real Mode Procedure with Far Return Frame
■ DPMI 0302h : Call Real Mode Procedure with Interrupt Return Frame
■ DPMI 0303h : Allocate Real Mode Callback Address

这本书和 Ray Duncan 的 Extending DOS 比起来,比较偏实用上的问题。它告诉我们在不同的 C 编译器下以及不同的 DOS Extender 下可能遭遇的问题、应该如何解决、或是怎麽做会有比较理想的效率。DOS Extender 程式与 DOS 程式虽有相当程度的透通,但凡用到指标或 1MB 内的记忆体直接存取时,仍需审慎了解你手上的 DOS Extender 以及 C 编译器的特性,才能对症下药。如果把写常驻程式形容为走钢丝,那麽设计 DOS Extender 的那些天才所遭遇的风险真是只能以危如累卵来形容。

到底 DOS 与这个世界的潮流要拨河到什麽时候 ? 你问侯捷的态度 ? 我嘛,我是比较喜欢 Windows,但是给一个好理由,我也不会反驳 DOS 长命百岁的说法。(在所谓软体忠诚度上我根本是墙头草,哪一个系统哪一种工具强势我就赶快学习它。我不想螳臂挡车,没这个实力也没这份意兴,家中还有人等着养呢。所有抗拒潮流的人都只因为影响到他个人的生计,而影响生计是因为他不愿意学习新知)。证诸历史 (虽只短短十年),DOS 总是绝地逢生。当我在 DOS 上碍手碍脚时,我认为 DOS 无救了;当我发现 DOS Extender 时,我又觉得 DOS 有希望;当 Windows 3.0 出来,我想 DOS 完了;现今我又把希望留给 32 位元 DOS。也许我是个善变的人,可是别忘记这句话 : 成功总向善变的人送秋波。

 

背景资料 :
期刊 Windows/DOS Developer's Journal
页数 每期大约 95 页。
售价 全年 12 期 US$ 64.0

wddj.jpg (12942 bytes)

去年五月我推荐了三本外文杂志,有读者来信向我要杂志订购单。我非常高兴朋友们有勇气把自己的求知触角更向外伸出。这真的需要一些勇气,怕麻烦如我者,想到订阅手续以及收不到杂志时求助无门的恐惧,多一半会打消念头。使用信用卡订购会方便的多。

今天再介绍一本杂志给各位,这是 Windows/DOS Developer's Journal (WDDJ)。这本期刊比较稀奇,拥有的人似乎很少,工研院和资策会大楼上的资讯图书馆都没有,是一颗尚未被广为欣赏的珍珠。每一期的 WDDJ 都会有一个主题,有时候是 NT,有时候是多媒体,有时候是物件导向。它也有一个 Windows Questions and Answers 专栏,以及一个 Tech Tips 专栏,有时候也有 book review。大部份时候还会选择一项产品做深度报导。和 MSJ 以及 DDJ 不同的是,前二者纯粹是技术文章以及广告,WDDJ 还有一个专门报导新产品的园地。

为让读者大约了解 WDDJ 的文章取材方向,我把上一年的题目整理列表於後。如果你想获得比较低阶的技术,像是虚拟机器,中断,VxD,这本刊物是比较可能的。如果你想补齐过期杂志,也可以,每一期单价 US$ 11.0 (比当期的还贵,老外的观念怎麽尽和咱们不同 ?!)。

表三\ WDDJ 近一年的文章大要

本网站 略


侯捷 2010-07-15 08:32:57

[新一篇] 忽聞海外有仙山(曾銘源)

[舊一篇] 精采絕倫 彩色書與電子書
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表