循序渐进 步步为营

时间:2022-07-26 10:02:02

自从有了兼容内核的想法之后,得到了很多开源业界的支持,也听到了很多怀疑的声音。项目组所要做的就是站在巨人的肩上,结合兼容内核的特点,制定出自己的开发策略和执行路线,有了思想,了解掌握可以借鉴的技术,兼容内核的目标也不会久远了。

站在巨人的肩上

兼容内核并不是在一个一穷二白的基础上开始的,而是在此之前已经有了大量可以借鉴的开源项目,对于他们,首先应该看清其优点,分析其不足,然后取其精华去其糟粕,甚至有些地方直接就是拿来主义,避免重复制造轮子。首先让我们认识一下可以借鉴的“巨人”。

1.Wine

Wine是一个运行在Linux上的开源软件,在Windows程序和Linux内核之间增加了一个中间层,一方面为Windows程序提供运行时环境,提供各种动态链接库(DLL),使得各种Win32的系统调用得以落实,另一方面将应用软件和动态连接库原本对Windows内核所作的系统调用翻译成对Linux内核的系统调用,转嫁到Linux内核上。

对于Windows软件而言,Wine可以理解为“WIN Emulator”,这也正是这种软件能够在Linux上运行的条件与原因。不过这种仿真并不是对CPU指令的仿真,而是对Win32 API函数调用的仿真。

但是Wine不同于兼容内核。其所有的操作不触及Linux的内核,都是在用户空间内完成。这样使得有些在Linux内核态中很容易实现的功能,Wine却要在用户态花费很多的时间,最后效果还不能够保障,甚至有些操作是根本无法实现的。

不过,虽然Wine本身并不是一个很好的解决方案,但对于兼容内核却有着特殊的重要性。这是因为内核并非一个操作系统的全部,在内核外面还需要许多系统软件、特别是动态连接库的配合,Wine都提供了很好的借鉴实例。

2.NdisWrapper

NdisWrapper是在Linux系统的开源项目,旨在帮助用户可以在Linux平台上使用Windows的驱动程序驱动无线网卡。NdisWrapper遵循Windows定义的“网络驱动程序接口规格”,在Linux内核中营造了一个NDIS设备驱动框架。从某种意义上NdisWrapper实际上就是ndis.sys的移植,从Windows内核到Linux内核的移植。

NdisWrapper对于兼容内核的意义很明显,一方面作为对Windows设备驱动框架的扩充,NDIS本来就是必须的,否则就无法利用数量众多的Windows网卡驱动模块,而NdisWrapper的代码只要稍加修改就可以跟Windows设备驱动框架对接。另一方面,也提供了一些本该属于Windows设备驱动框架的函数,可在实现Windows设备驱动框架时加以利用或借鉴。

3.ReactOS

ReactOS项目是一群Windows“发烧友”创立的。目的在于提供完全兼容Microsoft Windows XP 的操作系统。ReactOS旨在通过提供与NT、XP操作系统类似的构架和完全的公共接口,实现在二进制下的应用程序和驱动设备的兼容。

与兼容内核一样,ReactOS也只是个内核,跟完整的操作系统相比,中间还缺少以DLL为主的系统软件层。其缺陷对兼容内核是无关轻重的,我们关注的只是ReactOS提供了一个可资参考的样本。

Windows虽然不是开源的,但ReactOS是开源的,可以参考其对Windows的理解,学为己用。ReactOS已经相当的全面,兼容内核主要关注的是里面的设备驱动的框架及IO子系统的实现,这些内容对我们有着极其重要的作用,有些代码甚至可以直接使用。

此外,ReactOS的系统调用界面、进程的调度和管理等方面也是需要大力学习的。但是兼容内核毕竟是在Linux内核的基础上开发的,所以要认清两者的异同,这样才能更好的为兼容内核所用。

正视困难

Wine和NdisWrapper的存在和发展恰恰为兼容内核技术上的可行性提供了参考。诚然,Wine在某些方面很不理想,但那正是因为要避开内核而导致的后果,许多在用户空间很难解决的问题一到了内核里面就可以豁然开朗。或者Wine在用户空间都可以基本解决的问题,到了内核里面就更好解决了。

当然,涉及内核的设计和编程比之用户空间在难度(复杂度)上要高很多,但是“难”是个相对的概念,谁能说清到底难到什么程度就根本不能做了呢?再说这也毕竟不是登月、不是哥德巴赫猜想。

另一方面,Wine所提供的许多高层DLL为兼容内核的实际使用提供了条件,甚至是解决了后顾之忧。如果说Wine毕竟不涉及内核,那么NdisWrapper可确实为在内核中构建设备驱动框架和支撑界面提供了参考,实质上也是一个可行性证明。

认为难度太大的人还有一个顾虑,就是Windows的代码是不公开的,藏在黑盒子中。这种顾虑当然也有一些道理,但是ReactOS又在这方面提供了参考。ReactOS以零为起点从头开发,Wine只在内核外面做文章,尚且都能在一定程度上达到设计目标,而兼容内核站在它们的肩膀上,又有Linux内核作为原材料,至少条件上好了许多。

开发策略

开发兼容内核不能采取一步到位、而应采取逐步逼近的策略。以系统调用界面为例,完全可以先搁置那些用于GUI、即win32k.sys的扩充系统调用,即便是对于248个常规系统调用也可以分期分批地实现。实际上,甚至并无必要追求一个完整的实现。

工程上有一个所谓20/80原理,说是20%的工作量往往可以实现80%的功能,而剩下的20%功能却往往需要80%的工作量才能实现。如果兼容内核可以支持80%的Windows应用,剩下的20%慢慢从长计议也无不可。

Windows本身也在发展,今天还是“完整”的实现,明天就可能是不完整的了。所以,可以采取在一定距离后面跟进的策略。只要Windows还存在、还在发展,这样的跟进就永远不会完。这种后发跟进、逐步逼近的策略决定了我们的开发必定是一个螺旋式的渐进开发过程。

也就是从不同成分之间的关系看,是螺旋式的发展;从同一成分内部看,则是渐进的发展。

那么,这个螺旋式渐进开发过程的起点是什么呢?起点就是Linux+Wine。随着开发的进行,Linux的内核逐渐变成兼容内核,以Linux+表示;而Wine则逐渐演变成一个按Windows系统调用界面定制并且优化了的Wine,称之为Wine'。所以整个开发过程就是“(Linux + Wine) => ... => ... => (Linux+ + Wine')”

起点Linux+Wine显然是可以运行的,开发过程中的每一步都实现一组有限的目标,每一步的结果都应该是一个可以运行的、更逼近Windows的、可以发行的版本。

对Linux内核的修改原则上以动态安装模块的形式实现,尽可能不改变Linux内核原有的代码,必要时当然也可以补丁形式。

兼容内核开发的主体是一个框架、两个界面。如果按在内核中的位置从上到下排序,那就是系统调用界面,设备驱动框架,以及设备驱动支撑界面。下面分别加以讨论。

1.一个框架

一个框架指的是设备驱动框架。基本的设备驱动框架对应着Win2k内核中的I/O管理,以及电源管理、即插即用等机制,也涉及部分对象管理、系统配置、和安全管理方面的功能。其中最主要的是WDM层次式设备驱动机制的实现。

这个框架上面与有关文件操作的系统调用(open()、close()、read()、write()和ioctl()等)相衔接,中间实现基于“IO请求包”IRP(IO Request Packet)的设备驱动机制,下面则融入Linux内核的中断响应/服务机制、包括“软中断”即bh函数的执行机制。主要包括:

设备驱动程序的动态装入和连接。

IRP的生成和传递及设备驱动程序的启动、同步和终结。

将设备驱动程序的中断服务登记嫁接到Linux的中断机制,将设备驱动程序所关心的Windows内核运行状态映射到Linux内核的运行状态上。

将设备驱动程序的DPC请求嫁接到Linux的bh函数机制上。

基本设备驱动框架的实现是个“有”与“无”的问题,一旦这个框架成了形,这方面剩下的工作就不很多了。所以,设备驱动框架的实现“门槛”比较高,技术上的难度也相对较大。不过,有ReactOS的代码和NdisWrapper的部分代码可资借鉴。

此外,网络设备(网卡)、即NDIS设备的驱动既有其特殊性,又应该看作是Windows设备驱动的一部分,所以设备驱动框架的实现应该包含NDIS的实现,而NdisWrapper的代码基本上可以直接加以利用。

设备驱动框架的开发与系统调用界面的开发并不一定是顺序的。在系统调用界面上有关文件操作的系统调用尚未实现之前,不妨先借用Linux的有关系统调用作为对特殊设备或/proc节点的驱动,就像在大桥尚未造好之前先架一座便桥一样。

基本的设备驱动框架到位以后,如果单纯从技术角度而言,所有的.sys文件(即Windows的设备驱动模块)就都可以装入Linux内核运行。但是,有些.sys模块是由微软连同其操作系统捆绑发行的,微软拥有这些.sys文件的版权,Linux的用户不能直接使用。

这一类.sys模块基本上都是用于一些标准的、基本的、常用的外部设备,总体可分成几个大类(class),例如磁盘、USB、图形设备、网络设备等,它们的调用界面既有公共的部分,又各有其特殊之处。

一般而言,Linux实际上已经具备相应的功能,只是需要将Linux内核(包括设备驱动模块)中的这些函数和数据结构与具体.sys的调用界面之间架起桥梁。这一部分工作在形式上与系统调用界面和设备驱动界面的实现有相似之处,并且也像系统调用界面的实现那样可以从Wine+Linux开始,例如Wine结合内核中HPFS和NTFS的实现实际上就是把Linux的磁盘文件驱动适配到了Windows的磁盘文件访问。

但是也可能有一些微软的.sys模块在Linux内核中找不到对应物,那就需要仿制了。不过这方面的工作没有必要在一开始就进行,而可以推迟到实现了基本的设备驱动框架,并且部分地实现了两个界面以后再回过来渐进地开发。

2.两个界面

系统调用界面的实现有个“门槛”,那就是内核的进入/退出机制,即系统调用时的空间切换机制,不跨过这道门槛就谈不上系统调用界面。不过这个机制的实现并不复杂,因为我们要实现的本质上是Linux内核上的系统调用,从而这实际上就是Linux系统调用的空间切换机制,所不同的只是:

Linux原有的系统调用都是通过指令“int 0x80”进入内核的,现在需要为Windows系统调用添上通过指令“int 0x2e”进入内核。

Linux原来有个系统调用跳转表,这是一个以Linux系统调用号为下标的函数指针数组。现在需要添上一个Windows的系统调用跳转表,就是以Windows系统调用号为下标的函数指针数组,以后还要再添上一个用于GUI的扩充系统调用跳转表。

有了(系统调用时)内核的进入/退出机制,就可以考虑系统调用界面本身、即系统调用内核函数的集合。对于Win2k,也就是“Windows NT/2000 Native API Reference”等参考书中所述的二百多个函数。这些函数的实现实际上构成了Windows的文件系统、I/O子系统、进程管理子系统和内存管理子系统等。可以分期分批地予以实现。

248个系统调用,数量上与Linux相当;

系统空间的进入/退出可以重用Linux的相应代码;

进程/线程管理,需要融合两个系统的进程/线程管理机制;

进程间通信(包括LPC),基本上可以嫁接到Linux的相应机制上;

存储管理,需要扩充Linux的内存管理介面;

文件系统,基本上可以嫁接到Linux的文件系统上;

设备驱动,连接到WDM设备驱动框架。

不仅是分期分批,就是同一个系统调用也可能需要分几次完成。有的系统调用有很多可选项,其中有些可选项实际上很少用到。对这样的系统调用,可以先实现其基本功能,然后慢慢完善。

有些系统调用可以嫁接到相应的Linux系统调用;

有些系统调用可以部分地重用相应Linux系统调用的代码;

有些系统调用在Linux中没有对应物,需要借助Linux内核中的低层函数予以实现。

可以借鉴Wine的代码,在一定程度上是把Wine的部分代码移入内核并加以优化。显然,这个系统调用界面上的文件系统应该嫁接到Linux的文件系统,I/O子系统属于要实现的Windows设备驱动框架,进程管理子系统应该嫁接到Linux的进程管理,内存管理子系统应该嫁接到Linux的内存管理。

Wine中的底层四大件,即kernel32.dll、user32.dll、gdi32.dll及ntdll.dll,原来把所有的系统调用都引向Linux系统调用。随着开发的进展,每实现一个Windows系统调用,就应该在Wine这一层将原先的系统调用“重定向”到这个Windows系统调用上来。

为此,可以对DLL的装入/连接机制加以扩充,以实现“虚拟连接”、即重定向的功能。例如,可以在装入/连接下层DLL时对于需要从下层DLL引入的每一个函数都先检查一个映射文件,看是否需要把这个函数重定向到另一个DLL文件中的另一个函数名。

这样,每实现一个Windows系统调用以后,只要修改这个映射文件,并提供另一组底层DLL就可以。发展到最后,Wine原有的底层DLL就为新的(同名)DLL所取代。这时候,Wine就变成了Wine'。

如前所述,这些系统调用函数可以分期分批实现,其中首先需要实现的是与文件系统和设备驱动有关的系统调用,以便在此基础上搭建Windows设备驱动框架。

除常规的系统调用外,还有一个专用于图形界面(GUI)的扩充系统调用界面,这是因为微软把原先在用户空间实现的GUI支持(类似于X11)移到了内核中,成为一个动态安装模块win32k.sys。

这个扩充系统调用界面与常规界面合用同一个进入/退出机制,只是系统调用号全都大于1000。为此,内核中另外需要一个函数跳转表。至于具体系统调用函数的实现,则基本上就是把X11服务进程移植到内核中来,对此可以暂时搁置,留待将来再来讨论和实现。

3.设备驱动支撑界面的开发

根据微软提供的WinXP DDK,内核中可供设备驱动程序调用的函数及全局变量大约有1000个。在这1000个左右的内核函数中,有一些属于“IO管理器”IoManager和“对象管理器”ObManager,因而属于设备驱动框架,其余的则属于设备驱动界面。

有些资源的调用/引用可以映射(重定向)到Linux内核中的对应物上;有些资源的调用/引用可以嫁接(适配)到Linux内核中的对应物上;有些资源的调用/引用需要另加实现。

设备驱动支撑界面的实现也有个起码的门槛。例如分配缓冲区的函数及相当于spinlock()的函数就是必须的。幸运的是ReactOS和NdisWrapper已经提供了一个基本的支撑函数集合,只是ReactOS的代码不是建立在Linux内核基础上的,所以需要进行一些适配和优化的工作。在这个基本集合的基础上,每多实现一组函数,兼容内核的覆盖面就加大了一些。

从功能的角度而言,多数设备驱动界面函数及数据结构在Linux内核中都有对应物,例如,上述用于分配缓冲区的函数及spinlock(),需要做的就是把所需的支撑函数和数据结构落实到相应的Linux内核函数和数据结构上,这里面当然有些适配的工作,也有些函数在Linux内核中没有较为接近的对应物,那就要用Linux内核中的各种素材加以搭建。

从开发的时序而言,支撑界面的开发应该在设备驱动框架到位以后,或者至少是二者同步开发。这是因为,离开了设备驱动框架,支撑界面的存在就失去了意义,同时也失去了测试的手段。

开发路线

首先是准备阶段。在这个阶段中储备必须的知识,对Windows与Linux的异同作一些介绍和比较,对Wine、NdisWrapper、ReactOS的代码作一些介绍和分析,旨在引起广大Linux爱好者的参与和讨论。

这样,经过一段时间的探讨,对于要做的事情应该可以了然于胸。同时,对于开发的团队,也需要有个形成和组织的过程,网站也需要一段时间来逐步完善。此外,还要制定出一个编码标准和一套代码管理办法(包括代码的提交、评审、录用、存档、管理、奖励等各方面的办法),为具体的代码编写做好准备。这么一个准备阶段显然是很有必要的,估计这个阶段将延续大约四五个月。

开始具体的开发以后,第一件事就是要实现系统调用界面的进入/退出机制,接下来就是设备驱动框架。前者是不可分割的,是有或无的问题。后者可以先开发个最低限度的基本框架,再慢慢充实。如果把准备阶段作为整个项目的第一阶段,那么这就是第二阶段。

有了这些以后,下面的第三阶段就可以渐进开发了,所以图中把具体系统调用和设备驱动支撑函数的开发放在一个循环体内。如前所述,这里的每一轮循环都实现一组有限的目标,每一轮的结果都应该是一个可以运行的、更逼近Windows的、可以发行的版本。这个阶段也许永远不会完,永远要循环下去,其结果是愈来愈逼近Windows。

另一方面,兼容内核是Linux兼容内核,而Linux也在发展。于是,兼容内核将一边跟随Linux“水涨船高”地发展,一边又愈来愈准确、愈来愈广泛地兼容Windows应用软件。

兼容内核的开发目标是在一年之内要进入第三阶段并至少完成第一轮循环,即基本实现若干最重要的系统调用和设备驱动支撑函数,取得一些可见的效果。这样,一年内就能有一个可以发行的原型。这个原型又进一步构成整个项目的可行性证明,可以进一步增强开发人员和公众对项目的信心。至于更具体的安排,则有待于准备阶段中进一步的细化。下面是开发的流程,如图1所示。

任重道远

兼容内核的开发既不是唾手可得,也不是难于上青天,既不能一蹴而就,也不至于遥遥无期。需要在战略上藐视困难,战术上重视困难。

兼容内核已经有了理论的指导,有了广大Linux爱好者的帮助,有了国家的大力支持,兼容内核已经不只是在思想上的产物了。就目前,已经基本完成了第一二期的任务,下面正在蓄势准备第三个阶段的开发。相信第一个版本就要在不远的将来和广大的开源爱好者见面。

上一篇:微软已计划埋葬XML开发人员? 下一篇:政策性银行换心宝典