64 位 Windows 的 32 位用户态

2010/08/05

上周做了一个 64 位 Windows 的培训。其中一部分是讲 WOW64 。用户态的 32 位代码在即将进入内核态之前会从 x86-64 的 compatibility mode 切换到 long mode(刚刚从内核态返回之后会进行相反的切换)。因为切换是在用户态完成的(通过从 32 位代码段直接 jmp 到 64 位代码段),所以不可能修改 cr3(首级页表的首地址)。

培训的时候没有深究这个问题,后来发觉理解得很模糊。32 位保护模式的 MMU 内存映射是两级,每级页表是 1024 项,每项 32 位。64 位 long mode 的 MMU 映射是四级,每级页表 512 项,每项 64 位。如此如何不修改 cr3 就能做 64 位和 32 位的切换?是在进入内核之后又修改了一次 cr3 ?还是 compatibility mode 可以直接使用 64 位的四级页表?

这次我发现 Google 还是有局限性的。两天里 Google 了十几次也没找到答案,可能是做内核的人觉得这个问题太简单不值一提吧。最后还是老老实实的查了 Intel 的 Architectures Software Developer’s Manual, System Programming Guide :(9.8.5.3 64-bit Mode and Compatibility Mode Operation)

In compatibility mode, the following system-level mechanisms continue to operate using the IA-32e-mode architectural semantics:

  • Linear-to-physical address translation uses the 64-bit mode extended page-translation mechanism.
  • Interrupts and exceptions are handled using the 64-bit mode mechanisms.
  • System calls (calls through call gates and SYSENTER/SYSEXIT) are handled using the IA-32e mode mechanisms.

这里的 64-bit 模式也就是 AMD 术语里的 long mode ,而 IA-32e mode 是指 CPU 处于 long mode 或者 compatibility 之中任何一种模式。

所以,compatibility mode 的执行环境有很大一部分是借用 long mode 的模式,因此第一次进入 compatibility mode 之前必须按照 long mode 进行必要的设置(这个设置是在纯 32 位保护模式下关闭 paging 的时候完成的,所以首次进入 compatibility mode 的四级页表只能在低 4G 内存,因为这时只能操作 cr3 的低 32 位)。因为第一次进入 IA-32e 模式一定是在 32 位代码段里进行的,所以 CPU 总是会首先进入 compatibility mode 。

拿到了 CS5

2010/07/15

最近去欧洲玩了一圈。回来之后是一堆工作和生活上的变动。一直没时间写新东西。

今天拿到了 Adobe Creative Suite 5 。

永远的页

2010/06/15

最近看到有人质疑在今天的电子设备上阅读是否还需要保留『页』这个概念。他们认为『页』是印刷品时代的产物,在电子出版时代除了怀旧没有任何意义,电子出版物已经有了『页』的完美替代品:竖直滚动条。

对我来说,竖直滚动条平滑移动功能的直接后果之一是,在阅读的时候,你会情不自禁地试图避免阅读窗口(或者设备屏幕)边缘处的文字 —— 确实,它们太边缘了,因为没有页只有滚动条的内容没有上下留白,而对这种缺乏留白的逃避会延伸到你正在阅读的那四五行以外的一切文字,从而会无时无刻不让你希望把自己正在看的句子调整到窗口(屏幕)的正中间。于是窗口(屏幕)的大小失去了意义,读者的视界实际上被压窄到四五行。阅读的动作成了以句子为频度的『读 —— 滚动 —— 读』(甚至不是有阅读器还发明了自动卷动功能么)。竖直滚动条成了一个分散注意力的障碍而非方便的阅读工具。更糟糕的是,有了滚动条,文章的编辑者更难把握读者会看到什么样的页面布局,所以很多时候他们也就放弃了这样的思考。结果就是我经常在阅读的时候实在搞不好把一幅插图放到窗口的什么位置最协调(通常我发现最好的位置是赶紧把这个图片滚出窗口)。这实在是比『页』糟糕很多的阅读体验。

我很欣赏 Kindle for Mac 的做法:根据当前窗口大小把窗口的内容区域作为一页,不提供竖直滚动条,只提供翻页操作。因为没有滚动条,这是让我分心最少的一款阅读器。同时根据窗口大小动态的定义页的大小,也避免了在电子设备上照搬印刷品布局的古怪体验。我猜想在 iPad 和 iPhone 上的 Kindle 和 iBooks 也是类似的操作,因为没有窗口分割,所以页面的大小能更简单的固定为设备屏幕的大小 —— 但是 Kindle 没有进入 China App Store 所以我没有用过其 iPhone 版,也没有机会深入使用 iPad。

回顾一下,其实『页』这个概念还真的不一定就是印刷品时代仅仅因为技术局限而留下的遗产。古代人类不就使用过所谓的『卷轴』么?倘若假以改进,『卷轴』印刷品的便携性和不考虑到阅读注意力的单纯操作性不一定就输给『页』。所以我想,在印刷品时代,『页』并不是没有竞争者的无奈选择,而是在选择中胜出的更优秀方案。到了电子出版时代,人类历史依然会不断掀开新的一页。

Dict Mac 的发音功能

2010/06/08

对于词典软件来说,发音是个必需具备的基本功能,而且 dict.cn 的 Web API 提供了 MP3 格式的单词(短语)发音,所以从开始写 Dict Mac 的起,发音就被列入计划加入的功能。0.02 版完成之后,也就是从 5 月 25 日左右开始准备实现发音功能。

最初的问题是找到合适的 API,花了一个晚上,从 blog 和论坛的帖子堆里发现无数名称,逐一在 Apple 的文档里查找印证,最后终于锁定 Audio Queue Service 这个 framework。有了 framework 的确切名称后就不必再流连于 Stackoverflow 之类的网站,开始专心阅读 Apple 的文档。

花了一个晚上草草看了一下《Audio Queue Service Programming Guide》,感觉不是很好。这个 framework 功能比较强大,设计得比较灵活,相对来说也就没有提供傻瓜化的播放 API,即使是播放一个 MP3 文件也要 400 多行代码。好在这些代码在 programming guide 里都已经直接给出。但是一来我不喜欢这种 copy/paste 代码的方式,二来如果这些代码有 bug(当然 programming guide 里的代码本身有 bug 的可能性不大,但是和 Dict Mac 的 code base 集成的时候很可能引入问题),也要花不少时间解决,而且很可能要重读 programming guide 甚至查阅 API reference。

另外的问题是选择把 dict.cn 的 MP3 文件 download 到本地文件系统之后再打开文件播放的方式,还是直接播放网络的流数据的方式。从尽量减小暴露给用户的复杂度的角度说,稍稍倾向后者。但是考虑到今后还要实现本地缓存,以及尽早实现功能,还是决定先写出把 MP3 文件按照单词作为索引存储在本地文件系统的代码。花了两个晚上写好了这些代码后,第二天白天我脑子里突然冒出一个问题:OS X 会不会有现成的播放 MP3 的命令行工具?从以往的经验看,OS X 的命令行工具还是相当强大的。Google 了一下,果然有一个 afplay。这样一下子把实现发音功能的剩余工作从至少两个晚上变成了一个小时 —— 只要 fork 一个新进程运行 afplay 即可。

其实刚开始写 Dict Mac 的时候就考虑过是不是把必要的 UI 之外的其它功能都做成一个后端独立进程。最后决定当前阶段还是写成一个进程。不过 afplay 至少让发音功能成为了一个独立的进程。一开始我还在抱怨为什么 Mac OS X 不能直接提供一个播放 MP3 的傻瓜接口,其实这样的傻瓜功能应该做成工具而不是 API,像 Audio Queue Service 那样灵活的接口才值得作为 API 的形式提供。这个事情上 OS X 继承了 UNIX 的精髓,也是像 Windows 这样的不管底层功能还是傻瓜功能一股脑的做成 API 的非 UNIX 类的 OS 最缺乏的文化。

Git、P4merge 和 OS X

2010/05/27

Diff 是程序员最重要的工具之一。不过,除了在 mail 里发送 patch,我很难忍受传统 diff 的字符输出。在任何系统上开发软件,第一件重要的准备工作就是寻找趁手的 GUI diff 工具。GUI diff 工具的优势在于 side-by-side 的比较方式即能显示变化的部分,又不影响对每个版本的连续阅读。Linux 上比较好的是 KDE 的 kompare,也是我见到的第一种 GUI diff 工具。因为先入为主的看到 kompare 使用曲线来联系变化的对应关系,所以任何使用空行填充或者直线来显示对应关系的 GUI diff 工具对我来说也是比较勉强的 —— 可以暂时使用,但是仍然会不断寻找替代品。

平时工作用 Perforce 管理代码,它有个自带的 GUI diff 工具 P4merge。功能很强大(而且免费,虽然 Perforce 是商业软件,但是它的 client,包括 P4v 和 P4merge 都是免费的,而且 P4merge 可以完全脱离 Perforce server 独立使用),除了普通的比较两个文件(版本)之外还可以 3-way 比较(一般用来做人工的 branch merge,分别比较 branch fork 之前的原始版本,main branch 上的修改版本,和其它 branch 上的修改版本)。和 editor 不同的是,diff 工具操作的不仅仅是 check-out 的 version,而是 version repository 里的任意版本。所以 diff 工具要想和 version control 系统配合,简单的文件操作是不够的,一个成熟的 version control 系统必须在设计中考虑如何与外部的 diff 工具协作。

从上周开始用 Git 管理 Dict Mac 的 code base。用到昨天终于感到缺省的『 git diff 』不好用(类似『 diff -u 』的输出)。简单的 Google 一下就找到了答案:

git difftool …

原本以为只有在 Linux 上才会有缺省配置好的 GUI diff 工具和 Git 协作。在 Mac 上试过发现 OS X 上已经配置好了 opendiff 可以让 Git 直接使用。OS X 的 out-of-box 用户体验确实不是只用来蒙初学者的。连 developer 的工具也能准备齐全。但我还是最习惯 P4merge。于是试着配置:

git config –global diff.tool p4merge
git config –global \
difftool.p4merge.cmd /Applications/p4merge.app/Contents/MacOS/p4merge

然后运行『 git difftool 』,P4merge 是调用起来了,但是没有内容。原来 Git 缺省不会向 diff 工具传递参数,必须写到命令行配置里:

git config –global \
“difftool.p4merge.cmd \
/Applications/p4merge.app/Contents/MacOS/p4merge \”\$LOCAL\” \”\$REMOTE\””

这里还有一个陷阱,在某些 shell 里,『 $ 』是不用加转义的。不过在 OS X 的 bash 里必须加。

P4mergeOnGit.png

Dict Mac 到 Mac OS X 10.5

2010/05/25

把 Dict Mac 介绍给同事们之后遇到最多的回应是『怎么是 10.6 only 的?』我喜欢用最新的东西,加上在家里测试 10.5 也并不方便,所以开发的时候没多想就选了 10.6 SDK。没想到连公司同事这些 Mac 的重量级用户还有不少没升级到 10.6,于是今天中午花了一个小时把最低平台需求降到了 10.5。其间遇到了几个小问题。

第一是 NSApplication 的 delegate 在 10.5 之下没有类型,在 10.6 下则正式定义为 NSApplicationDelegate 接口。不过真正起作用的 selector 都没有变化。把 delegate 的基类从 NSObject <NSApplicationDelegate> 变成 NSObject 之后就解决了问题。第二个问题是 NSTextView 的背景色设置成 [NSColor clearColor] 在 10.6 下工作的很好,到了 10.5 下就变成了黑色,变成 [NSColor windowBackgroundColor] 才行。虽然效果相同,但是从正常思路来说我是希望让控件的背景透明,而不是让它『碰巧』和窗口同色,而且遇到那些背景色可以在运行的时候定制的窗口,非透明的控件还要多写代码来同步颜色。

上面两个问题解决之后,Dict Mac 就可以正常的用 10.5 SDK 编译了,在 OS X 10.6.2 上运行也一切正常。之后遇到的问题最离奇,程序在 OS X 10.5 上启动后就会崩溃,crash log 报告 selector [DictWindow awakeFromNib] 不存在。Google 之后发现遇到类似问题的人不少,唯一找到的 workaround 是把 [DictWindow awakeFromNib] 里的 [super awakeFromNib] 去掉(完全莫名其妙的改法,这是一个人记得刚刚升级到 Xcode 3.2 的时候并没有这个问题,然后从自己的 version control 找到当时的代码才得到的解决方案)。

降级 SDK 总会遇到一些小麻烦。也能从中看到 SDK 的每次升级都确实给开发工作减轻了不少负担,唯一遗憾的是用户们升级得没有那么快,新 SDK 的推出带来的好处要过上一段不短的时间才能让开发者受益。

Dict Mac 发布

2010/05/22

很多天没有更新 Blog 了,其间也有曾经想写些话题,不过大部分时间还是用来动手做被一件很久之前就想做的事情 —— 自己写个小工具。第一个完成的工具是我每天都离不开的 dict.cn 的客户端 Dict Mac。目前刚刚放到网上的只是 0.01 版。功能还不是很多。不过我自己已经完全切换到 Dict Mac 而不再打开 dict.cn 了。

从开发的角度说,这次也是我学习 Cocoa 的第一个实践项目。Cocoa 的 Interface Builder,以及控件对 URL 和 RTF 格式的处理都很好用。

支持 iPhone/iPod touch

2010/05/07

前两天把 WordPress 的 WP-touch 插件配好了( sipoint.wordpress.com 本来就支持这个插件,这次配置的是 techsingular.net )。这样有 iPhone/iPod touch 的朋友阅读就方便多了。

镜面屏的原理

2010/05/04

如今几乎所有用过笔记本电脑的人都能说出镜面屏的缺点和优点。缺点是反光,如果你的座位正后面有一盏灯,那么使用镜面屏是很痛苦的。优点是色彩艳丽。在顺理成章的接受这个优点的时候,很少有人想到色彩艳丽的根源。前些天看到有人在网上问这个问题,我突然想到原来镜面屏的优点就来自它的缺点。

非镜面屏能很好地应对使用者后方的光源,原因在于把光源发出的光通过漫反射平均的分散到各个方向。反过来说,如果你的侧后方有一个光源,那么非镜面屏就会把这个光源的一部分光反射到你的眼中,不管你如何调整角度,只要这个光源还在你的后方,反射到你眼中的光量都不会变化。这就是漫反射的特点。而镜面反射遵循入射角等于反射角的反射定律,所以镜面屏会把正后方光源的光线几乎 100% 的反射到使用者眼中。但是只要稍稍调整角度,让光源处于侧后方,光源的光线就几乎不会通过屏幕进入使用者眼中。所以镜面屏是通过牺牲对正后方环境的容忍度,降低了整个环境对显示效果的干扰。而非镜面屏以牺牲抗环境干扰的整体能力换取对正后方环境的容忍。

技术的暴力美学

2010/04/27

When in doubt, use brute force.
—— Ken Thompson

Keep it simple, stupid!

在『天涯』上看到一个帖子提到米格-25。这个前苏联空军中神秘的家伙当年经常在北约的雷达上以三倍音速一闪而过。在以色列曾经遭遇 F-4 并且被后者占据尾追位置发射了空对空导弹,结果是 F-4 看到米格- 25 在导弹发射之后突然加速离开。以当时北约国家 —— 主要是美国 —— 对航空技术的理解,要达到米格- 25 展示出的能力,苏联非掌握钛合金加工工艺不可 —— 在三马赫速度下产生的高温中,除了钛以外,任何低密度金属都会变成橡皮泥,可是即使是今天人类对钛金属的加工能力仍然很有限,完全无法令其成型为航空器零件。还有,为了完成被赋予的防卫广阔的西伯利亚的任务,西方认为米格- 25 必然拥有精巧的机载电子设备。

当 1976 年一个叛逃的苏联飞行员给西方送来一架米格- 25 的时候,后者迫不及待的要看看心目中尖端的机身工艺和电子技术。结果,发现这个三倍音速飞行的家伙竟然是个大钢锭 —— 几乎完全用不锈钢这种在西方看来连普通飞机都造不了的重金属。整架飞机用可称为『狂暴』的发动机才体现出邪恶的航空特性。那个能蹲下一个成年人的喷口最能说明其狂野,不光巨大,而且固定在最大状态,完全没有任何调节机构 —— 在维持三马赫速度而产生的高温下,任何多余的机构都是巨大的不稳定因素。困扰了西方十几年的关于如何实现三倍音速的机身加工就这样被苏联用暴力一举攻克。

还有西方幻想中先进的机载电子设备,不过是把大量的电子管塞到硕大的机头中。如果在地面打开机头的电子管雷达,能把前方 100 米内的生物烤熟。

北极熊独一无二的风格把技术和暴力美学联系到一起。这是人类在技术有限的前提下追求极至的精神。如果有一天人类认识宇宙的能力走到尽头,还能够依靠这种精神在探索的路上走很远。刘慈欣的《三体II · 黑暗森林》和弗诺 · 文奇的《天渊》幻想的就是这种无奈但又令人神往的境地。只是半个世纪之前那个神秘的红色帝国已经在这个境界肆意驰骋了。