Cosmos是一个操作系统开发工具包,它使用Visual Studio作为开发环境。尽管名称中包含C#,但可以使用任何基于.NET的语言,包括VB.NET、Fortran、Delphi Prism、IronPython、F#等。Cosmos本身及其内核例程主要是用C#编写的,因此得名Cosmos。除此之外,NOSMOS(.NET开源托管操作系统)听起来很愚蠢。
Cosmos并不是传统意义上的操作系统,而是一个“操作系统工具包”,或者用话来说就是“操作系统乐高”。Cosmos允许像使用Visual Studio和C#创建应用程序一样创建操作系统。大多数用户可以在几分钟内编写并启动他们自己的操作系统,全部使用Visual Studio。Cosmos支持Visual Studio中的集成项目类型,以及集成调试器、断点、监视点等。可以像调试普通的C#或VB.NET应用程序一样调试操作系统。
Cosmos可以通过USB、以太网、DVD甚至真正的硬盘在真实硬件上启动,但大多数用户使用VMWare,因为在开发过程中它更快。给定以下源代码:
public class Program
{
public static void Main()
{
Console.WriteLine("Hello, Cosmos!");
}
}
当按下F5时,代码将被转换成一个完整的操作系统,并在VMWare中启动:
对许多人来说,这可能看起来像魔法。确实有很多代码需要编写才能使这个过程如此无缝。以下是发生的基本步骤。
Visual Studio将C#等.NET语言编译成中间语言(IL),使用名为IL2CPU的Cosmos实用工具。IL通常由Windows上的JIT(即时)编译器在可执行文件运行时处理。Visual Studio生成的可执行文件实际上不是本地代码,而是IL字节码。.NET可执行文件中通常存在的唯一本地代码是一小段引导代码,它允许Windows像运行普通可执行文件一样运行它。这段引导代码调用.NET JIT,然后读取并编译可执行文件中的IL。可以使用许多工具查看IL,如ILDasm或反射器。
以下是一个Cosmos项目的示例IL:
.method family hidebysig virtual instance void Run() cil managed
{
.maxstack 1
.locals init (
[
0]
int32 i,
[
1]
class BreakpointsKernel.Test xTest)
L_0000:
nop
L_0001:
ldc.i4.0
L_0002:
stloc.0
L_0003:
ret
}
然后将其传递给一个名为NASM的汇编器,将其转换为二进制格式。
现在有一个二进制文件,但需要启动它。为此,Cosmos使用一个引导程序。所有操作系统都使用引导程序,包括Windows和Linux。计算机的BIOS寻找引导代码,但这个引导代码必须非常小。然后这个引导代码加载一个稍大的引导程序,然后初始化内存并加载更大的操作系统。在转移到操作系统后,引导代码和引导程序从内存中移除。为此,Cosmos使用一个名为Syslinux的引导程序。尽管名字如此,它不是Linux,Cosmos也不是基于Linux构建的。Syslinux的根源涉及旧的Linux引导程序,因此得名。Syslinux只是用来让BIOS启动Cosmos代码,一旦Cosmos代码运行起来,BIOS和Syslinux就不再使用。
Cosmos支持正常的源代码调试,允许跟踪、断点甚至监视。Cosmos还支持实验性的汇编级调试器,以及GDB。为了在Visual Studio中支持调试,Cosmos有一个名为DebugStub的小手写汇编方法。DebugStub在Cosmos执行过程中反复调用,并由Cosmos编译器自动插入。DebugStub使用串行端口与DebugClient通信,DebugClient是Cosmos Visual Studio调试包的一部分。在VMWare上,串行端口映射到管道,DebugClient与管道通信。在物理硬件上调试时,两边都使用串行端口。将来,也将支持通过以太网调试。
许多框架类库使用icalls或pinvokes。例如,当.NET需要在屏幕上绘制时,它没有代码。它使用pinvoke直接调用Windows API。icalls类似,但映射到.NET运行时的内部函数。因为Cosmos在真实硬件上运行,没有.NET运行时和Windows API,所以必须实现这样的代码。Cosmos使用插件来实现这些。插件是一段代码,它标记一个类和/或方法,它将在IL2CPU阶段替换。插件可以用C#(或任何.NET语言)、汇编或X#编写。插件也可以用来将C#代码与汇编代码接口。Cosmos努力最小化编写汇编代码的需要,但在内核中直接与硬件交互时,必须使用汇编。插件通过创建实际运行汇编的C#类来隐藏这些深层细节,远离开发者。IOPort类就是这样一个例子。
public AtaPio(Core.IOGroup.ATA aIO, Ata.ControllerIdEnum aControllerId, Ata.BusPositionEnum aBusPosition)
{
IO = aIO;
mControllerID = aControllerId;
mBusPosition = aBusPosition;
// Disable IRQs, we use polling currently
IO.Control.Byte = 0x02;
mDriveType = DiscoverDrive();
if (mDriveType != SpecLevel.Null)
{
InitDrive();
}
}
这是PATA(硬盘访问)类的一个小程序。它能够使用C#代码直接与CPU IO总线通信。尽管IOPort类(此代码中的IO变量)的部分是用C#编写的,但部分是用X86汇编代码插入的。
Cosmos是用C#编写的。Cosmos开发人员,包括内核开发人员,都使用C#。然而IL2CPU库必须处理汇编,当然中处理编译器代码的人会使用X86。以前有自己的基于类的“内联编译器”。它工作得很好,但总是想要更多。亲切地称解决方案为X#(来自X86)。这个想法是将所有的源代码放在一个地方,并保持类型安全。以前的基于类的内联编译器做到了这一点。典型的代码看起来像这样:
new Move(Registers.DX, (xComAddr + 1).ToString());
new Move(Registers.AL, 0.ToString());
new Out("dx", "al");// disable all interrupts
new Move(Registers.DX, (xComAddr + 3).ToString());
new Move(Registers.AL, 0x80.ToString());
new Out("dx", "al");// Enable DLAB (set baud rate divisor)
new Move(Registers.DX, (xComAddr + 0).ToString());
new Move(Registers.AL, 0x1.ToString());
new Out("dx", "al");// Set diviso (low byte)
new Move(Registers.DX, (xComAddr + 1).ToString());
new Move(Registers.AL, 0x00.ToString());
new Out("dx", "al");// // set divisor (high byte)
UInt16 xComStatusAddr = (UInt16)(aComAddr + 5);
Label = "WriteByteToComPort";
Label = "WriteByteToComPort_Wait";
DX = xComStatusAddr;
AL = Port[DX];
AL.Test(0x20);
JumpIfEqual("WriteByteToComPort_Wait");
DX = aComAddr;
AL = Memory[ESP + 4];
Port[DX] = AL;
Return(4);
Label = "DebugWriteEIP";
AL = Memory[EBP + 3];
EAX.Push();
Call();
AL = Memory[EBP + 2];
EAX.Push();
Call();
AL = Memory[EBP + 1];
EAX.Push();
Call();
AL = Memory[EBP];
EAX.Push();
Call();
Return();