代码改变世界

Cosmos的基石:IL2CPU编译器--.net/C#开源操作系统学习系列三

2011-02-23 11:31  Hundre  阅读(4913)  评论(12编辑  收藏  举报

本文的代码包以cosmos-12304.zip为例(从这个包开始,COSMOS的内核算是有了个基本的雏形,就像是一颗大树在出芽前会先长出庞大的根系,现在就要破土长出第一颗芽了)

 

IL2CPU之于COSMOS就相当与GCC之于LINUX,查看COSMOS的源代码,可以发现没有一行汇编代码,就连BOOTLOADER也是使用C#语言来完成的,在COSMOS中很对与硬件相对应的类,如屏幕、键盘等,然后在编译的时候再由IL2CPU识别出这些特定的类并转换成机器指令代码代码。

 

由于是纯C#的开发环境,因此IL2CPU责任重大,需要实现C#语言的各种特性,包括对象的初始化、多态、事件、委托等等,当然到cosmos-12304为止,这些特性还没有全部实现(后面的应该会一步一步的都实现,不过还没有看过后面的源代码,只是猜测而已)。除此之外,IL2CPU还负责处理对.net的运行库(这个运行库并不是.net framework,这个库是COSMOS自己实现的与.net framework类似但要小很多很多的一个库,以后再讲这一部分)

 

cosmos-12304这个版本的代码和现在最新的代码在文件结构上有很大的差别,之所以文章从这个代码包开始,是因为小弟从第一个源代码包开始,一点一点的挪到这里,个人感觉从这里开始终于可以大致的抓住了COSMOS的开发思路,目前的COSMOS应该可以分成两部,一部分是IL2CPU,一部分是操作系统内核。整个COSMOS项目是从IL2CPU开始的(从早期的代码可以看出,全都是在实现的编译器的任务),先是让IL2CPU能把简单的C#程序编译成本地程序之后,就开始实现操作系统内核,随着项目的深入,一遍扩展内核的功能,一遍完善IL2CPU编译器,小弟估计到后期IL2CPU将会支持全部C#语言乃至整个MSIL语言的所有特性。

 

好了,废话到这该来点实质性的东西了,cosmos-12304中先修改启动项目为这个

该项目调用了一段脚本用来编译并启动COSMOS的内核,脚本内容如下:

 

..\..\..\source\il2cpu\bin\debug\il2cpu.exe 
-metal 
-in:..\..\..\source\Cosmos\Cosmos.Shell.Console\bin\debug\Cosmos.Shell.Console.exe 
-plug:..\..\..\source\Cosmos\Cosmos.Kernel.Plugs\bin\debug\Cosmos.Kernel.Plugs.dll 
-out:Files\output.obj -platform:nativex86 -asm:output.asm

脚本位于cosmos-12304\Build\Cosmos\ISO目录下,文件名为Build.bat(其实就是一段批处理程序.........)。设置完后F5启动,整个项目就开始运行了

成功运行的截图如下:

 

运行方式为把内核编译生成ISO文件,然后使用QEMU虚拟机加载(关于运行如何启动可以参考小弟的上一篇文章,使用的工具不太一样,但是流程是相同的)

好了,到这里整个项目算是运行完成了(对!就只有两行输出,雏形......雏形,大家见谅,就相当于可以输出一个“hello world”了)

 

接下来看看IL2CPU是如何把C#代码转换成可以独立运行的操作系统内核的,痛苦的历程开始了(小弟编译原理和操作系统原理没学好,分析这里的代码是在是太痛苦了)

Build.bat文件中的脚本可以看出,启动的时候运行的是IL2CPU项目,直接来到该项目下,点开,查看,是一个CONSOLE类型的项目,直接来到Main函数里面,分析源代码可以发现,IL2CPU项目根据-in输入参数来确定要编译的程序集--此处输入的项目为Cosmos.Shell.Console项目--Main函数中找到如下代码:

 

using (FileStream fs = new FileStream(AsmFile, FileMode.Create)) 
{
    
using (StreamWriter br = new StreamWriter(fs)) 
        {
        e.Execute(InputFile, TargetPlatform, br, MetalMode, DebugMode, BCLDir, Plugs);
    }
}

 

主要关注这一句:

e.Execute(InputFile, TargetPlatform, br, MetalMode, DebugMode, BCLDir, Plugs);

此处e为一个ENGINE类型的对象在上边有实例化的代码,跳到Execute方法的定义处,函数内容如下........额,太长了就不列出来,有兴趣的朋友可以自己查看下源代码J

可以看到IL2CPU先获取了Cosmos.Shell.Console程序集的信息,然后存到一个本地变量中,

mCrawledAssembly = AssemblyFactory.GetAssembly(aAssembly);

然后就可以从这个变量中获取程序集都另外引用了哪些程序集,再把引用到的程序集都添加到mCrawledAssembly变量内,在mCrawledAssembly内部使用一个List<AssemblyDefinition>类型的变量来存储这些程序集,留到后面生成汇编代码的时候使用(注:此处使用了MONO项目中的一些类,如AssemblyFactoryAssemblyDefinition,求一份MONO的类库文档,英文的也行),除此之外还加载了Plugs(插件?),目前看来,Plugs就相当于COSMOS库,COSMOS中对外部硬件的操作都通过Plugs(库)来实现,目前只实现了CONSOLE这个类,也就是说,COSMOS内核中使用的CONSOLE类不是.net framework中的CONSOLE类,而是由CONSOLE内核重新实现了的,在CONSOLE内部调用特定的对象(以后分析)来实现输出功能,当然CONSOLE实现的方法和属性都与.net framework中的一样,这样在编程的时候只需直接使用.net framework来完成操作即可,等到了编译阶段IL2CPU会自动把相应的代码转换成COSMOS的内部实现从而脱离对.net framework库的依赖。

个人猜测以后Plugs会扩展成一个像.net framework一样的类库,不过.net framework的类库这么庞大……恐怕……………

 

完成以上这些操作后,IL2CPU还会加入一些初始化运行时的函数:

代码
mMethods.Add(RuntimeEngineRefs.InitializeApplicationRef, new QueuedMethodInformation() {
                            Processed 
= false,
                            Index 
= mMethods.Count
                        });
                        mMethods.Add(RuntimeEngineRefs.FinalizeApplicationRef, 
new QueuedMethodInformation() {
                            Processed 
= false,
                            Index 
= mMethods.Count
                        });

 

和虚表(用来实现多态)的实现(对多态的底层实现不是很了解,这里只知其然并不知其所以然,惭愧):
代码
if (!aInMetalMode) {
                            mMethods.Add(VTablesImplRefs.LoadTypeTableRef, 
new QueuedMethodInformation() {
                                Processed 
= false,
                                Index 
= mMethods.Count
                            });
                            mMethods.Add(VTablesImplRefs.SetMethodInfoRef, 
new QueuedMethodInformation() {
                                Processed 
= false,
                                Index 
= mMethods.Count
                            });
                            mMethods.Add(VTablesImplRefs.SetTypeInfoRef, 
new QueuedMethodInformation() {
                                Processed 
= false,
                                Index 
= mMethods.Count
                            });
                            mMethods.Add(VTablesImplRefs.GetMethodAddressForTypeRef, 
new QueuedMethodInformation() {
                                Processed 
= false,
                                Index 
= mMethods.Count
                            });
                        }

 


完了之后会传入内核程序的入口点,这就是为什么很多语言的程序都是以Main函数作为程序入口点的原因,因为编译器内部定义的就是把程序跳到Main函数开始执行,如果想让别的函数作为开始函数,则在编译器中就得做相应的修改,有时候这部分内容也交由连接器来处理(有关连接器和编译器的作用在此推荐本书《程序员的自我修养连接、装载与库》)

mMethods.Add(mCrawledAssembly.EntryPoint, new QueuedMethodInformation() {
                            Processed 
= false,
                            Index 
= mMethods.Count
                        });

 

之后就会调用ProcessAllMethods()方法把以上传入的所有程序集里面的方法转换成汇编代码,然后还会有些清理作用的函数,最后是调用ProcessAllStaticFields()处理一些全局的静态变量

有兴趣的朋友可以跟进代码里面研究一下转换的过程,小弟实在是顶不住了,看得眼花花、头晕晕,大家知道是这么一个流程就好,以后的编译过程基本上就是走的这么一个流程,下面是小弟总结出来的IL2CPU的编译流程图,欢迎大家拍砖:

 

 

从上图中没有看到传统编译器中还需要进行的语法、词法、文法分析这几步,嘿嘿,其实这些步骤在加载之前就都已经完成了,这是因为IL2CPU中加载的需要编译的文件都是程序集文件,而.net中,可以通过反射来查看程序集中的所有定义的函数、变量之类的内容,相当于.net已经帮我们做完了语法、词法、文法分析,并把分析的结果分门别类的存放在程序集里了,我们只需要通过反射功能来查看程序集中已经分类好的信息即可。其实,上图中除了分析和汇编的操作,其他的都是把各个需要用到的程序集中的函数、变量这些东西取出来,再补充上一些额外的初始化操作。而分析和汇编的任务就是把从各个程序集中取出函数、变量翻译成本地CPU代码。在IL2CPU内部,是通过逐条翻译MSIL指令来完成的。

可以点开看一下Indy.IL2CPU.IL.X86项目(最新的代码包中好像已经被更名为Cosmos.Complier.IL2CPU.IL.X86

 

里面对每一调MSIL指令都写了一个与其对应的类,每一个类就是专门负责把其自己翻译成X86的指令。

上面的这些函数小弟跟进去看了一下,在此以目前的水平就展开分析,展开了也说不清,但里面的东西确实都是精华,虽然复杂了点,但是对于学习面向对象程序的结构和编译器实现的朋友来说是一个很好的例子,在此推荐

最后,IL2CPU再使用第三方工具,把汇编出来的汇编指令文件转换成本地CPU识别的二进制文件,代码如下:

 

代码
ProcessStartInfo xFasmStartInfo = new ProcessStartInfo();
                    xFasmStartInfo.FileName 
= FasmFileName;
                    xFasmStartInfo.Arguments 
= String.Format("\"{0}\" \"{1}\"", AsmFile, OutputFile);
                    xFasmStartInfo.UseShellExecute 
= false;
                    xFasmStartInfo.RedirectStandardError 
= true;
                    xFasmStartInfo.RedirectStandardOutput 
= true;
                    Console.WriteLine(
"fasm = '{0}'", FasmFileName);
                    Process xFasm 
= Process.Start(xFasmStartInfo);
                    xFasm.Start();
                    
if (!xFasm.WaitForExit(60 * 1000|| xFasm.ExitCode != 0) {
                        Console.WriteLine(
"Error while running FASM!");
                        Console.Write(xFasm.StandardOutput.ReadToEnd());
                        Console.Write(xFasm.StandardError.ReadToEnd());
                        
return 3;
                    }

 

最后,在通过脚本,讲二进制文件转换成ISO文件供虚拟机加载运行:

@REM ----------- Build ISO

del cosmos.iso

attrib files\boot\grub\stage2_eltorito -r

..\..\..\Tools\mkisofs\mkisofs -R -b boot/grub/stage2_eltorito -no-emul-boot -boot-load-size 4 -boot-info-table -o Cosmos.iso files

pause

 

@REM ----------- Start QEMU

cd ..\..\..\tools\qemu\

qemu.exe -L . -cdrom ..\..\build\Cosmos\ISO\Cosmos.iso -boot d

pause

 

最后附上一个comos-12304.zipIL2CPU相关项目的说明(当然也是个人学习的总结,可能有错,仅当参考,欢迎拍砖)

 

 

IL2CPUIL2CPU的启动项目

IL2CPU.Tests:用来测试对MSIL的指令的翻译是否正确的测试项目

IL2CPUGUI:暂时无用

Indy.IL2CPUIL2CPU的编译引擎主要的活动都发生在这里面同时封装了用来实现MSIL特性的类,如垃圾收集器、多态等

Indy.IL2CPU.Assembler实现汇编器的一些基类和一些通用的指令操作

Indy.IL2CPU.Assembler.X86X86汇编器,封装X86指令的实现

Indy.IL2CPU.Assembler.X86.NativeX86汇编器的一些扩展,应该是用来封装一些平台特有的指令

Indy.IL2CPU.Assembler.X86.Win32X86汇编器的扩展用来翻译支持WIN32环境下的汇编程序

Indy.IL2CPU.IL:封装MSIL指令的一些基类和一些通用的操作

Indy.IL2CPU.IL.X86Indy.IL2CPU.Assembler.X86项目配合,把MSIL指令翻译成X86指令

Indy.IL2CPU.IL.X86.NativeIndy.IL2CPU.Assembler.X86.Native项目配合,把MSIL指令翻译成一些平台支持的特有一些特性的指令

Indy.IL2CPU.IL.X86.Win32与上面两个类似翻译成Win32环境下的汇编指令

Indy.IL2CPU.Plugs:封装一些用来在编译的时候处理Plugs的类和操作

 

最新的代码包中,以上项目的名字已经被该动,不过也只是前缀进行了改变,面的部分基本上都一样。同时个人觉得IL2CPU也还是很不错的编译器,学习编译原理的朋友门,小弟在次推荐一下可以研究一下IL2CPU的源代码,也可以写些文章出来帮小弟解惑一下,呵呵J

好了,先写到这里吧,水平有限,写得不是很好,留下的一些问题特别是关于Plugs的内容小弟也会在以后对源码的学习中慢慢解开

如果觉得本文有点帮助的朋友楼主是很乐意接收鼓励的J,可以到楼主的淘宝充值店光顾下(欢迎收藏小店……广告….广告,大家懂的~~~~或者点击下博客里面的广告,呵呵

有大虾路过的话楼主也愿意接受鸡蛋和板砖。