代码改变世界

Plugs介绍(翻译)---- .net/C#开源操作系统学习系列六

2011-08-11 03:18  Hundre  阅读(2338)  评论(0编辑  收藏  举报

原文地址:http://www.codeproject.com/KB/cs/CosmosPlugs.aspx

介绍

这篇文章将展示在COSMOS中如何实现基于WINDOWS API调用和内部调用的.net代码。另外,也包含了如何使用COSMOS,汇编或者X#语言与硬件直接进行交互。

Cosmos是什么

Cosmos一个使用Visual Studio作为开发环境的操作系统开发工具(development kit)。尽管如此,任何基于.net的语言,包括VB.NET,Fortran,Delphi Prism,IronPython,F#等等都可以用来进行开发。Cosmos本省和内核运行都是使用C# 来写的,所以命名为Cosmos。而且,NOSMOS(.NET Open Source Managed Operating System)听起来太蠢了。

Cosmos不是传统意义上的操作系统,它更应该叫做操作系统工具(Operating System Kit) ,或者正如我所说的叫做“Operating System Legos”(不知如何翻译,嘿嘿)。Cosmos让您能像使用Visual Studio和C#创建应用程序一样创建操作系统。大部分用户可以在几分钟之内自己写和引导一个他们自己的操作系统,所有这些都可以在Visual Studio中完成。Cosmos提供了与Visual Studio集成的项目类型、调试器、断点工具和观察其(watchers)等。你可以向调试你的C#或者VB.NET应用程序一样调试你的操作系统。

什么情况下需要Plugs

在以下三种场景中需要用到Plugs:

1. 内部调用(Internal Calls)

2. P/Invoke

3. 直接汇编

内部调用和P/Invoke的情况

在.net框架提供的各种类中,有一部分不是使用.net代码来实现的,而是使用的本地代码。这样做的原因有两:

1. 被实现的方法依赖于Windows API(P/Invoke的情况)

2.被实现的方法依赖于高度优化过的C++或者在.net运行时中的汇编代码(内部调用的情况)

P/Invoke被用来往屏幕上画图,访问已存在的Window encrypition API,访问网络和其他一些类似的功能。

内部调用被需要直接访问.net运行时的类来使用。比如直接访问内存管理的类,或者对速度有要求的情况。如Math.Pow方法就是使用的内部调用。

Plugs可以使用C# ,汇编或者任何.net语言来实现。

直接汇编的情况

对于直接与硬件进行会话,Cosmos必须能够和PIC总线,CPU IO总线,内存等进行交互。访问内存经常使用的是非类型安全的指针(unsafe pointers),尽管如此,在某些情况下还是得自己手写汇编代码。Plugs相当于接口,提供给C#直接访问汇编代码,使得对汇编调用的访问就像C#代码的方法调用一样。

在Cosmos中写X86汇编

可以在Cosmos中使用类来实现X86汇编

View Code
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)   
  

但是 Cosmos也支持一个更高层次的调用X#。X#是一种类型安全的直接与X86汇编对应的汇编语言。X#如下:

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
<WriteByteToComPort>();
AL 
= Memory[EBP + 2];
EAX.Push();
Call
<WriteByteToComPort>();
AL 
= Memory[EBP + 1];
EAX.Push();
Call
<WriteByteToComPort>();
AL 
= Memory[EBP];
EAX.Push();
Call
<WriteByteToComPort>();
                Return();  

开始实现Plugs

首先我们必须先决定我们的plug要干什么。举例来说,Math.Abs(double)被用来实现一个内部调用

.method public hidebysig static float64 Abs(float64 'value') cil managed internalcall
{
    .custom instance 
void System.Security.SecuritySafeCriticalAttribute::.ctor()

如果你直接使用这个方法而Cosmos没有相应的plug,编译器便会产生一个“plug needed”错误。因为在IL2CPU没有IL代码把它编译为X86代码。所以“plug needed”错误的意思是你需要一些依赖内部调用或者P/Invoke的方法,否则Cosmos将无法编译。

在Math.Pow这个例子中,是可以通过编译的因为Cosmos的内核已经包含了一个在编译的时候会自动被调用的plug.

编译器在运行时中使用plug来替换实际的代码。Plug中提供的代码被用来替换对内部调用和WINDOWS API的调用,这些调用无法在Cosmos中直接使用因为Cosmos不是运行在Windows或者CLR下面的。Plug就是这么一个强制嵌入和替换的格式(It’s a form of forced inlining and replacement)

为了创建一个Plug,我们需要创建一个新的类。在内核中的Plug被创建为各个单独的程序集(assemblies)并且被内核单独引用。这样允许IL2CPU包含和使用plug。

[Plug(Target = typeof(global::System.Math))]    
public class MathImpl  {
        
public static double Abs(double value)  {
            
if (value < 0) {
                
return -value;
            } 
else {
                
return value;
            }
        } 

虽然这里是显示了一个方法,但其实Plug类能包括多个方法。在这个例子中Plug的属性(attribute)是关键因素。它告诉IL2CPU这个plug用来替换System.Math类中的方法。然后IL2CPU便会去找与System.Math中方法对应的方法,并把他们给换掉。

直接汇编的plug

直接汇编的plug是被用来运行C#直接和X86汇编进行交互的代码。比如IOPort类允许设备驱动程序在需要和硬件设备通信的时候直接访问CPU总线。

首先创建一个空的C# 类,创建将要被替换的空方法。如果这个被替换的方法的返回类型不是VOID,则plug中的方法需要随便返回一个值以使C# 编译器能编译它。尽管如此这个方法的返回值是不会被用到的,因为plug将使被替换的目标方法被忽略,而以plug中实现的方法替换掉原方法。

public abstract class IOPortBase  {
        
public readonly UInt16 Port;
 
        
// all ctors are internal - Only Core ring can create it.. but hardware ring can use it.
        internal IOPortBase(UInt16 aPort)
        {
            Port 
= aPort;
        }
        
internal IOPortBase(UInt16 aBase, UInt16 aOffset)
        {
            
// C# math promotes things to integers, so we have this constructor
            
// to relieve the use from having to do so many casts
            Port = (UInt16)(aBase + aOffset);
        }
 
        
//TODO: Reads and writes can use this to get port instead of argument
        static protected void Write8(UInt16 aPort, byte aData) { } // Plugged
        static protected void Write16(UInt16 aPort, UInt16 aData) { } // Plugged
        static protected void Write32(UInt16 aPort, UInt32 aData) { } // Plugged
 
        
static protected byte Read8(UInt16 aPort) { return 0; } // Plugged
        static protected UInt16 Read16(UInt16 aPort) { return 0; } // Plugged
        static protected UInt32 Read32(UInt16 aPort) { return 0; } // Plugged  

正如你看到的“Write”方法是空的,而“Read”方法需要 一个名义上的返回值。

这个类将被一下代码给替换掉:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cosmos.IL2CPU.Plugs;
using Assembler = Cosmos.Compiler.Assembler.Assembler;
using CPUx86 = Cosmos.Compiler.Assembler.X86;
 
namespace Cosmos.Core.Plugs
{
    [Plug(Target 
= typeof(Cosmos.Core.IOPortBase))]
    
public class IOPortImpl
    {
        [Inline]
        
public static void Write8(UInt16 aPort, byte aData)
        {
            
//TODO: This is a lot of work to write to a single port.
            
// We need to have some kind of inline ASM option that can
            
// emit a single out instruction
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceDisplacement = 0x0C, SourceIsIndirect = true };
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceReg = CPUx86.Registers.EBP, SourceDisplacement = 0x08, SourceIsIndirect = true };
            
new CPUx86.Out { DestinationReg = CPUx86.Registers.AL };
        }
 
        [Inline]
        
public static void Write16(UInt16 aPort, UInt16 aData)
        {
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x0C };
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x08 };
            
new CPUx86.Out { DestinationReg = CPUx86.Registers.AX };
        }
 
        [Inline]
        
public static void Write32(UInt16 aPort, UInt32 aData) 
        {
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x0C };
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x08 };
            
new CPUx86.Out { DestinationReg = CPUx86.Registers.EAX };
        }
 
        [Inline]
        
public static byte Read8(UInt16 aPort)
        {
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x08 };
            
//TODO: Do we need to clear rest of EAX first?
            
//    MTW: technically not, as in other places, it _should_ be working with AL too..
            new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceValue = 0 };
            
new CPUx86.In { DestinationReg = CPUx86.Registers.AL };
            
new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            
return 0;
        }
 
        [Inline]
        
public static UInt16 Read16(UInt16 aPort)
        {
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x08 };
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EAX, SourceValue = 0 };
            
new CPUx86.In { DestinationReg = CPUx86.Registers.AX };
            
new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            
return 0;
        }
 
        [Inline]
        
public static UInt32 Read32(UInt16 aPort)
        {
            
new CPUx86.Move { DestinationReg = CPUx86.Registers.EDX, SourceReg = CPUx86.Registers.EBP, SourceIsIndirect = true, SourceDisplacement = 0x08 };
            
new CPUx86.In { DestinationReg = CPUx86.Registers.EAX };
            
new CPUx86.Push { DestinationReg = CPUx86.Registers.EAX };
            
return 0;
        }
    }

注:在这个例子中的代码(指那些看起来像汇编的代码—译者注)不是X#代码。我们一些比较老的plug任然是使用比较老的语法来写的。

现在我们有了一个plug,我们可以使用C#直接访问IOPort这个类。下面这个例子摘自ATA类

public override void ReadBlock(UInt64 aBlockNo, UInt32 aBlockCount, byte[] aData) {
      CheckDataSize(aData, aBlockCount);
      SelectSector(aBlockNo, aBlockCount);
      SendCmd(Cmd.ReadPio);
      IO.Data.Read8(aData);
    }
 

其他plug的例子

在BCL(Binary Classes Library?。.net中框架提供的类),Console类中使用的一些内部调用通常最后都使用到了WINDOWS API,我们不需要逐个替换每一个调用层次上直接映射到WINDOWS API调用,而只需在一个更高的调用层次树上调用我们的TextScreen类完全替换这些方法。(We don't need to plug only the methods that directly map to Windows API calls, but instead we plug methods much higher up the tree and completely replace the implementation to call our TextScreen class instead.)

namespace Cosmos.System.Plugs.System.System {
    [Plug(Target 
= typeof(global::System.Console))]
    
public static class ConsoleImpl {
 
        
private static ConsoleColor mForeground = ConsoleColor.White;
        
private static ConsoleColor mBackground = ConsoleColor.Black;
 
        
public static ConsoleColor get_BackgroundColor() {
            
return mBackground;
        }
 
        
public static void set_BackgroundColor(ConsoleColor value) {
            mBackground 
= value;
            Cosmos.Hardware.Global.TextScreen.SetColors(mForeground, mBackground);
        }   

(翻译就到此结束了,水平有限,欢迎大家指点)