|
楼主 |
发表于 2009-3-24 15:47:22
|
显示全部楼层
开发Linux兼容内核的策略与路线
毛德操
笔者自从提出Linux兼容内核的构想以后,听到了不少反响。其中支持者固然不乏其人,如开源软件推进联盟陆首群先生、倪光南院士、还有OSDL的平野正信先生,都是支持的。但是无庸讳言,对此不以为然的意见也有不少。有趣的是,这些不以为然的意见往往分成两个极端。一个极端说,Linux兼容内核要达到的目标已经达到了,因此根本就不用多此一举。说已经达到,是因为在用户空间我们已经有了Wine,而NdisWrapper则已经解决了把Windows设备驱动装入Linux内核的问题。另一个极端呢,则是说兼容内核的难度太大,根本就不可能成功,所以只是一个“梦”。显然,在谈论开发路线图之前首先应该回答这两种意见。幸好它们正好互相构成回答。
首先,正如笔者在另几篇文章中所说,Wine只是在逻辑上、功能上基本解决了用Linux内核来模拟/仿真Windows内核的问题,但是性能上是无法令人满意的,而且说基本上解决其实也很勉强。至于NdisWrapper,它所解决的是一些网络设备(网卡)的驱动,而不是普遍意义上的设备驱动。更何况NdisWrapper设备驱动的上层只能是Linux的Socket,而不能与诸多文件操作的系统调用挂上钩。实际上,退一步说,即使NdisWrapper和Wine真的已经分别解决了Windows设备驱动和应用软件在Linux内核上的跨平台运行,也还需要有人把这二者整合起来。有许多公司只是把一些开源软件收集(而不是整合)拢来,做成发行版,不是就号称成了“高科技公司”吗?所以,要使Windows的应用软件和设备驱动能在Linux上高效地运行,从而真正为广大用户所接受,兼容内核的开发应该说是很有必要,而且也不是那么轻而易举。
至于说难度太大,那么Wine和NdisWrapper的存在和发展恰恰为兼容内核技术上的可行性提供了参考。诚然,Wine在某些方面很不理想,但那正是因为要避开内核而导致的后果,许多在用户空间很难解决的问题一到了内核里面就可以豁然开朗。或者可以反过来说,Wine在用户空间都可以基本解决的问题,到了内核里面就更好解决了。当然,涉及内核的设计和编程比之用户空间在难度(复杂度)上要高得多,但是“难”是个相对的概念,谁能说清到底难到什么程度就根本不能做了呢?再说这也毕竟不是登月、不是哥德巴赫猜想。另一方面,Wine所提供的许多高层DLL为兼容内核的实际使用提供了条件,或者可以说是解决了后顾之忧。如果说Wine毕竟不涉及内核,因此还不足为凭,那么NdisWrapper可确实为我们在内核中构建设备驱动框架和支撑界面提供了参考,实质上也是一个可行性证明。
认为难度太大的人还有一个顾虑,就是Windows的代码是不公开的,藏在黑盒子中,光凭几本书能得到多少信息、如何就能开发出跟Windows兼容的内核?这种顾虑当然也有一些道理,但是ReactOS又在这方面给我们提供了参考。ReactOS以零为起点从头开发,Wine只在内核外面做文章,尚且都能在一定程度上达到设计目标,而我们站在它们的肩膀上,又有Linux内核作为原材料,至少条件比他们好多了。
当然,把Wine、NdisWrapper和ReactOS作为参考意味着我们需要吃透、或者至少基本上理解它们的代码。为此笔者将陆续写一些分析文章在本网站上推出,起个抛转引玉的作用。
总之,兼容内核的开发既不是唾手可得,也不是难于上青天,既不能一蹴而就,也不至于遥遥无期。说起来还是那句老话:战略上藐视困难,战术上重视困难。
后发跟进、逐步逼近
我们开发兼容内核不能采取一步到位、而应采取逐步逼近的策略。以系统调用界面为例,我们完全可以先搁置那些用于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内核原有的代码,必要时当然也可以打一下补丁。
兼容内核开发的主体是一个框架、两个界面。如果按它们在内核中的位置从上到下排序,那就是:系统调用界面,设备驱动框架,以及设备驱动支撑界面。下面分别加以讨论。
系统调用界面的开发
系统调用界面的实现有个“门槛”,那就是内核的进入/退出机制,即系统调用时的空间切换机制,不跨过这道门槛就谈不上系统调用界面。不过这个机制的实现并不复杂,因为我们要实现的本质上是Linux内核上的系统调用,从而这实际上就是Linux系统调用的空间切换机制,所不同的只是:
l Linux原有的系统调用都是通过指令“int 0x80”进入内核的;现在需要为Windows系统调用添上通过指令“int 0x2e”进入内核。
l Linux原来有个系统调用跳转表,这是一个以Linux系统调用号为下标的函数指针数组;现在需要添上一个Windows的系统调用跳转表,就是以Windows系统调用号为下标的函数指针数组;以后还要再添上一个用于GUI的扩充系统调用跳转表。
可见,这道门槛的实现并不困难。
有了(系统调用时)内核的进入/退出机制,就可以来考虑系统调用界面本身、即系统调用内核函数的集合了。对于Win2k,这就是“Windows NT/2000 Native API Reference”等参考书中所述的二百多个函数。这些函数的实现实际上构成了Windows的文件系统、I/O子系统、进程管理子系统、内存管理子系统,等等。
这里的工作量可就大了,但是可以(也应该)分期分批地予以实现。
l 248个系统调用,数量上与Linux相当。
l 系统空间的进入/退出可以重用Linux的相应代码。
l 进程/线程管理。需要融合两个系统的进程/线程管理机制。
l 进程间通信(包括LPC)。基本上可以嫁接到Linux的相应机制上。
l 存储管理。需要扩充Linux的内存管理介面。
l 文件系统。基本上可以嫁接到Linux的文件系统上。
l 设备驱动。连接到WDM设备驱动框架。
不光是分期分批,就是同一个系统调用也可能需要分几次来完成。有的系统调用有很多可选项,其中有些可选项实际上很少用到。对这样的系统调用,我们就可以先实现其基本功能,然后慢慢完善。
怎样实现这些系统调用内核函数呢?
l 有些系统调用可以嫁接到相应的Linux系统调用。
l 有些系统调用可以部分地重用相应Linux系统调用的代码。
l 有些系统调用在Linux中没有对应物,需要借助Linux内核中的低层函数予以实现。
l 可以借鉴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服务进程移植到内核中来,对此我们可以暂时搁置,留待将来再来讨论和实现。
设备驱动框架的开发
基本的设备驱动框架对应着Win2k内核中的I/O管理,以及电源管理、即插即用等机制,也涉及部分对象管理、系统配置、和安全管理方面的功能。其中最主要的是WDM层次式设备驱动机制的实现。这个框架上面与有关文件操作的系统调用(open(),close(),read(),write(),ioctl()等)相衔接,中间实现基于“IO请求包”IRP(IO Request Packet)的设备驱动机制,下面则融入Linux内核的中断响应/服务机制、包括“软中断”即bh函数的执行机制。主要包括:
l 设备驱动程序的动态装入和连接。
l IRP的生成和传递、以及设备驱动程序的启动、同步、和终结。
l 将设备驱动程序的中断服务登记嫁接到Linux的中断机制上,将设备驱动程序所关心的Windows内核运行状态映射到Linux内核的运行状态上。
l 将设备驱动程序的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内核中找不到对应物,那就需要仿制了。不过这方面的工作没有必要在一开始就进行,而可以推迟到实现了基本的设备驱动框架,并且部分地实现了两个界面以后再回过来渐进地开发。
设备驱动支撑界面的开发
根据微软提供的WinXP DDK,内核中可供设备驱动程序调用的函数(以及全局变量)大约有1000个 (比Win2000多)。在这1000个左右的内核函数中,有一些属于“IO管理器”IoManager和“对象管理器”ObManager,因而属于设备驱动框架,其余的则属于设备驱动界面。
l 有些资源的调用/引用可以映射(重定向)到Linux内核中的对应物上。
l 有些资源的调用/引用可以嫁接(适配)到Linux内核中的对应物上。
l 有些资源的调用/引用需要另加实现。
设备驱动支撑界面的实现也有个起码的门槛。例如分配缓冲区的函数以及相当于spinlock()的函数就是必须的。幸运的是ReactOS和NdisWrapper已经为我们提供了一个基本的支撑函数集合,只是ReactOS的代码不是建立在Linux内核基础上的,所以需要进行一些适配和优化的工作。在这个基本集合的基础上,每多实现一组函数,兼容内核的覆盖面就加大了一些。
从功能的角度看,多数设备驱动界面函数(以及数据结构)在Linux内核中都有对应物,例如,上述用于分配缓冲区的函数以及spinlock()就是,需要做的就是把所需的支撑函数和数据结构落实到相应的Linux内核函数和数据结构上,这里面当然有些适配的工作。也有些函数在Linux内核中没有较为接近的对应物,那就要用Linux内核中的各种素材加以搭建。
从开发的时序看,支撑界面的开发应该在设备驱动框架到位以后,或者至少是二者同步开发。这是因为,离开了设备驱动框架,支撑界面的存在就失去了意义,同时也失去了测试的手段。
其它开发
除一个框架、两个界面以外,还有一些辅助性的开发要做,例如:
l 对Wine的修改
l 某些.sys模块的仿制
l 与注册表的交互
l Unicode在字符串和文件名中的使用
l 从Windows风格的文件路径名到Linux路径名的映射
l 从Windows风格的设备名到Linux路径名的映射
还可以举出不少。这些工作量合在一起也不容小看,但是从总体上看、从难度和技术含量看、毕竟不是主体。 |
|