我们开发Linux兼容内核的目标是让Windows应用软件可以直接在这个内核上运行,更确切地说是在以这个内核为核心的操作系统上运行。同时, 还要让为Windows而开发的一些设备驱动模块也能装入这个内核运行。这一方面是因为针对Windows开发的设备驱动模块在数量和品种上都远远多于 Linux;另一方面是因为有愈来愈多的应用软件需要跟专用的设备驱动模块配套运行,缺了这设备驱动模块就运行不起来。只有同时实现了这两个目标,才能说 Linux的内核兼容了Windows内核。那么,为了要达到这两个目标,我们必须对Linux内核作些什么修改和扩充呢?本文就是要对此作一分析,并回 答这个问题。
首先,一段封闭的程序、如果既不调用库程序、也不作系统调用,是跟操作系统无关的,只要所在机器的CPU跟这段程序编译时的目标CPU相符就可以 了。这是因为,以PC机为例,这段程序编译后所生成的所有指令全都是x86机器指令,而且所有这些指令全都可以正常运行,它的所有跳转指令和子程序调用指 令的目标都不会越出这段程序的边界。其次,即使需要调用库程序,只要不直接或间接进行系统调用,就也跟操作系统无关,因为调用库程序在效果上就相当于扩大 了这段程序的地盘、推进了它的边界。所以,只要不作直接或间接的系统调用,应用程序的运行就跟操作系统无关,因而可以在任何操作系统上运行。然而,一般而 言,这样的程序是不能作为一个独立的应用、独立的进程运行的,因为独立的进程至少总得要输入/输出,这就离不开系统调用了。
那么应用软件怎样进行系统调用呢?大家都知道,这是通过陷阱指令实现的,在x86处理器上是int指令。事实上,Linux内核采用“int 0x80”实现系统调用,而Windows内核采用“int 0x2e”。这首先就使得Windows应用软件不能在Linux内核上运行。试想,Windows应用软件企图通过“int 0x2e”进行系统调用,而Linux内核认为这是非法陷阱指令。由此可见,首先就得使Linux内核接受“int 0x2e”作为系统调用的手段之一。我们真该感谢两个内核的设计者,要是他们当初都选择了0x80、或者都选择了0x2e,那我们现在就不好办了。幸好这 二者互不冲突。
由于所有的系统调用都通过同一条陷阱指令进入内核,就需要通过参数“调用号”来区分所要求的具体操作和功能。于是,在通过什么指令进行系统调用以 外,还要考虑一共使用了多少个调用号,每个调用号对应着什么操作和功能,需要一些什么参数,结果是什么、怎么返回,有什么副作用等等。所有这些因素合在一 起,就构成了一个系统调用界面。当然,Windows的系统调用界面与Linux的不同。而要使Windows软件能在Linux内核上直接运行,就必须 让 Linux内核也提供一个Windows系统调用界面,否则就只好像Wine那样把Windows软件“嫁接”到Linux系统调用上来。在“嫁接”方 面,Wine已经做得很好了,但是我们知道效果还是不理想。
我们说Windows系统调用界面,一方面当然是说它的表现,就是Windows应用软件在内核外面所看到的各个具体系统调用的调用方式、参数、效 果、返回的结果、以及副作用。但是,另一方面,更重要的其实是这个界面背后的东西,即这些系统调用的实现,我们在这方面的工作量主要也正在于此。
据“Undocumented Windows 2000 Secrets”介绍,Win2k共有248个常规的系统调用(有逆向工程的结果为证)。其中有的系统调用很简单,或者几乎可以直接用相应的Linux系 统调用替代,有的却非常复杂。显然,要从零开始开发出这些系统调用是不大现实的(尽管ReactOS确实在这样做),我们要做的是尽量利用Linux内核 中的低层函数来实现这些Windows系统调用。在某种意义上,Wine是在内核外面将Windows应用嫁接到Linux系统调用上,而我们的兼容内核 则要在内核里面将Windows系统调用嫁接到Linux的诸多内核函数上。之所以在内核里面嫁接比在内核外面嫁接更好,一方面是因为内核函数的“粒度” 较小,内核函数与系统调用之间的关系有点像汇编语言与高级语言之间的关系。另一方面是因为内核空间是统一的,可以提供全局的视野。例如,在内核中可以看到 所有进程的进程控制块(PCB);而一出内核,到了用户空间,进程之间就互相隔离了。所以,凡是要求有全局视野的操作,就应该在内核中实现。
除这些常规的系统调用外,Win2k还有639个图形界面(GUI)系统调用。这等于是把X11搬到了内核里面。在WinNT 4.0以前,Windows也像Linux一样,由用户空间的图形服务进程来实现对图形和视窗的操作和管理,那样当然效率比较低。从WinNT 4.0开始,就把图形操作和视窗管理移到了内核里面,成为一个可安装模块win32k.sys。这对于提高图形操作的速度很有意义,否则游戏软件的运行就 不会有那么流畅(当然,后来又有了DirectX,进一步提高了图像/图形操作的速度,那是后话)。所以,严格地说,所谓Windows系统调用界面也包 括这些图形操作调用。不过实现这些调用的“原材料”不再是Linux内核函数,而是X11中的相关函数。当然,事情有轻重缓急,完全没有必要在一开始就把 这六百多个调用也一锅煮。实际上,即使是248个常规系统调用的实现,也没有必要一步到位。
过了系统调用界面这道关,许多系统调用就可以实现了,例如创建进程、取系统时间等等这些,在Linux内核里面就可以完整地实现。但是,一旦涉及输 入/输出,涉及设备驱动,就又可能有问题了。问题不在于常规的设备驱动,例如键盘、鼠标、硬盘之类常用设备的驱动Linux内核中本来就有,只要把系统调 用嫁接过去就行了。问题在于一些比较特殊的设备,特别是新出现的设备。这些设备的制造商往往只为Windows提供设备驱动模块、即.sys文件。如果不 能把这样的.sys模块装入Linux内核并让它们正常运行,对Windows应用的兼容就不可能是完整的。
将.sys模块装入Linux内核不难,因为Linux的动态安装模块与之并无本质的不同。难的是使其在Linux内核中正常运行。要达到这个目 标,一是要将装入的模块往上跟有关的系统调用衔接,往下(可能)跟中断响应机制挂上钩;二是要为模块的运行提供一个特定的环境。事实上,.sys模块之所 以可以被装入Windows内核并正常运行,正是因为Windows内核满足了这两个条件。显然,现有的Linux内核不具备这样的条件,它所满足的是 Linux 动态安装模块的运行条件,这跟两个系统调用界面的差别是相似的。所以这也成了兼容内核开发的一个重要组成部分,也代表着相当的工作量。
Linux兼容Win32程序,一个框架、两个界面
内容版权声明:除非注明,否则皆为本站原创文章。