接16页:
4.4 Switch-Receive 语句
考虑下面的代码,它等待着输入类型为C1.Imp的端点上的Replay或者Error消息。
void M( C1.Imp a) {
switch receive {
case a.Reply(x):
Console.WriteLine(“Reply {0}”, x);
break;
case a.Error():
Console.WriteLine(“Error”);
break;
}
}
Switch-receive语句的执行分为两步:
在上例中,switch-receive有两种模式,既有在端点a上接受Reply消息,也有在同一端点上接受Error消息。在第一种情况中,Reply消息中的整型参数自动与本地变量x进行绑定。
Switch-receive的结构很普通,它是一种可以包含多个端点的模式。以下的例子有a和b两个端点,用来接收Reply和Error消息:
void M (C1.Imp a, C1.Imp b) {
switch receive {
case a.Reply(x) && b.Reply(y):
Console.WriteLine(“Both replies {0} and{1}”, x, y);
break;
case a.Error():
Console.WriteLine(“Error reply on a”);
break;
case b.Error():
Console.WriteLine(“Error reply on b”);
break;
case a.ChannelClosed():
Console.WriteLine(“Channel a is closed”);
break;
}
}
上例举例说明了如何使用switch-receive语句来等待特殊的消息组合。第一个分支只有在端点a和b都收到Reply消息才能达到。最后的case分支包含了ChannelClosed()模式,这是一个特别的模式,只有在通道被关闭(被其它的参与者)并且接收不到消息的时候才被触发。
4.5所有权
为了保证端点和通道传输的数据的内存分离,所有位于交换堆(Exchange Heap) 中的块都是在编译时被追踪的资源。特别是,静态检查强制对这些资源的存取只能发生在特定的程序点,这些程序点拥有资源并且方法不会泄漏资源的所有权。被追 踪的资源有着严格的所有权模型。每一个资源在任何点最多被一个线程(或者是线程内的数据结构)所拥有。举例来说,如果从线程T1向线程T2对发送了一个包含端点的消息,那么端点的所有权发生如下改变:从T1变到消息,再变到T2,依赖于消息的接收者。
为了简化对资源的静态追踪,指向资源的指针只能被本地变量,消息和自追踪的数据结构所拥有。这些约束有时是繁重的,所以Sing#提供了一种方法来克服这个缺点,方法是通过一种称为TRef的抽象类来间接将被追踪的资源存储到数据结构中。
4.6 TRefs
TRef是类型为TRef<T>的存储单元,它有如下定义:
class TRef<T> where T:ITracked {
public TRef([Claims] T i_obj);
public T Acquire();
public void Release([Claims] T newObj);
}
当生成一个TRef<T>,构造函数需要一个类型为T的对象做为参数。调用者必须在构造阶段对这个对象有所有权。构造结束后,所有权就被传递给新分配的TRef了。Acquire方法用来获得TRef的内容。如果不为空,那么Acquire返回TRef的内容并将所有权传递给调用者。然后,TRef被认为空。Release方法将一个T型对象的所有权从调用者传递给TRef,然后,TRef非空。TRef是线程安全并且直到TRef非空前,Acquire的操作都是被阻塞的。
TRef代表了在静态检查和动态检查的一个折中方案。通过使用TRef,不正确的多次请求被变成死锁,垃圾收集器的回收机制用来对声明一个资源负责。
4.7 交换堆
因为对于内存块的所有权通过消息交换从一个线程或者进程传递到另一个,Singularity需要一个途径来分配和跟踪可以使用这种方式来交换的内存块。通道系统需要消息的参数是交换堆内的标量或者内存块。交换堆内有两种内存块:独立块或者是向量。它们的类型分别如下表示:
using Microsoft.Singularity.Channels;
R* in ExHeap pr;
R[] in ExHeap pv;
指针pr的类型证明它指向一个交换堆中的R结构。ExHeap是系统运行时候定义的类型,它提供了分配,回收和其它对堆的操作支持。Pv是交换堆中R的一个向量。
一个交换堆的常量不包含任何指向进程堆中的指针。R类型必须是一个可交换的类型,比如,一个原数据类型(int,char等等),一个枚举弄或者一个rep结构体,rep结构体是一个简单的结构体,它的内部所有的成员都是可交换类型。
4.8 校验
校验----用来确保代码在Singularity中执行是类型安全的并且内存不互相依赖的常量是安全的----是一个三个阶段的过程。Sing#编译器检查类型安全性,所有权规则,以及在编译过程中的协议一致性。Singularity校验器在生成的MSIL代码上检查这些属性。最后,后端编译器应该(现在仍未实现)产 生一种类型化的汇编语言的形式,这种形式可以让这些属性被操作系统再次检查。有人可能要说只有最后一个阶段对安全性来说是严格需要的。当然这只是字面上正 确,实际上,在更高层次上,程序员从三个阶段可以尽可能早的发现错误并且可以让错误出现的更彻底。此外,冗余的校验可以在校验阶段预防错误。
4.9编译时反射
SIP的闭合世界是与反射的便利性不兼容的,一个Java完整的一部分和CLR环境都能够在运行时生成并且调用代码。结果是,Singularity不支持运行时的反射服务。
编译时反射(CTR)是CLR完全反射能力的部分替代品。CTR类似于下列技术,比如宏,二进制代码重写,aspects,原数据编程和多阶段语言。基本的思想是程序可以包含保持位置的元素(类,方法,字段等等),这些元素然后被一个生成器进行扩展。
从由已存程序结构检查所驱动的模板产生boiler plate和其它重复代码的特性是十分强大的。比如,在Singulartity中,应用程序和设备驱动器公开描述它们的资源需求,比如I/O范围和服务通道。从这些描述中就可以自动生成对这些过程的启动代码。
用Sing#所 写的生成器被称为变换式。一个变换式包含一个模式匹配程序结构和一个用来生成新代码元素的代码模板。结合它们二者可以让一个变换式被独立的分析和检查,而 与它将要应该的代码无关。比如,类似调用了一个不存在的方法或者调用了对象的错误类型等等错误可以在一个变换式中被发现。在这方面,CTR与多阶段语言类似。注意,CTR变换式可以是信任的计算基础的一部分,所以你可以将信任代码发布到其它不被信任的过程中。
transform DriverTransform
where $IoRangeType: IoRange {
class $DriverCategory: DriverCategoryDeclaration {
[$IoRangeAttribute(*)]
$IoRangeType $$ioranges;
public readonly static $DriverCategory Values;
generate static $DriverCategory() {
Values = new $DriverCategory();
}
implement private $DriverCategory() {
IoConfig config = IoConfig.GetConfig();
Tracing.Log(Tracing.Debug, "Config: {0}", config.ToPrint());
forall ($cindex = 0; $f in $$ioranges; $cindex++) {
$f = ($f.$IoRangeType) config.DynamicRanges[$cindex];
}
}
}
}
上面的变换式,被命名为DriverTransform,从一个公布的驱动器所需资源的声明中生成设备驱动器的启动代码。比如,下面的SB16驱动器声明描述了IoPorts的需求:
internal class Sb16Resources: DriverCategoryDeclaration {
[IoPortRange(0, Default = 0x0220,Length = 0x10)]
internal readonly IoPortRange basePorts;
[IoPortRange(1, Default = 0x0380,Length = 0x10)]
internal readonly IoPortRange gamePorts;
internal readonly static Sb16Resources Values;
reflective private Sb16Resources();
}
DriverTransform匹配这个类,因为它从DriverCategoryDeclaration继承来并且包含指定的元素,比如一个适当类型的Values域和一个私有构造函数的占位符。Reflective关键字声明一个占位符的定义将会由一个使用了implement修改器的变换式来生成。占位符是向前参考的,这样可以让程序中的代码参考紧接着由变换式产生的代码。
变换式中的模式变量用$符开头。在这个例子中,$DriverCategory一定是SB16Resources类型的。匹配多于一个元素的变量用两个$符开头。比如:$$ioranges代表一个字段的列表,每一个都是从IoRange继承来的$IoRangeType类型(变量字段的类型不必相同)。为了给集合中的每一个元素都生成代码(比如字段$$ioranges的集合),模板可以包含forall关键字,这将对集合中的每一个绑定都复制模板。上面例子中的交换式产生的结果代码等同于:
class SB16Resources {
…
static Sb16Resources() {
Values = new Sb16Resources();
}
private SB16Resources() {
IoConfig config = IoConfig.GetConfig();
Tracing.Log(Tracing.Debug,
"Config: {0}", config.ToPrint());
basePorts = (IoPortRange)config.DynamicRanges[0];
gamePorts = (IoPortRange)config.DynamicRanges[1];
}
}
这个例子也说明了交换式产生的代码在交换式编译的时候也可以进行类型检查,而不是象宏一样,将错误检查推迟到交换式被应用的时候进行。在本例中,给Values赋值证实是安全的,因为构造的对象($DriverCategory)的类型匹配Values字段的类型。
5 Singularity系统
构建于上面描述的内核,SIPs,通道及语言,Singularity支持众多的常规操作系统的服务。
5.1 I/O系统
Singularity的I/O系统包含三层:HAL,I/O管理器和驱动器。HAL是一个小型的,可信任的PC硬件的抽象:IoPorts,IoDma,IoIrq和IoMemory是对存取设备的抽象;计时器,中断控制器,实时时钟和调试控制台等的接口;内核调试存根;事件日志器;中断和例外向量;BIOS资源发现和堆栈连接代码。它是由C#,C++和汇编语言写的。HAL的汇编和C++部分代表着系统内接近5%的信任代码(561个文件中的35)。
Singularity内核使用一个对照单来产生并且绑定设备驱动器。启动的时候,内核对系统做一个既插既用的配置。内核通过引导加载器中的BIOS和总线(比如PCI总线)获得信息,进行设备的列举,启动适当的设备驱动器,并且将这些封装了对设备硬件存取的设备对象进行传递。
每 一个驱动器都是用安全代码写的,并且运行在自己的进程内。驱动器通过专有的通道和系统的其它部分(包括网络堆栈和文件系统)进行通信。当一个驱动器启动 后,内核提供了四种初始化对象来让驱动器和它们的设备来通信。所有这些对象都提供了一个用来在设备引用直接存取硬件内存映射位置前进行检查的接口。
IoPort提供了一个设备I/O端口注册的接口。它校验注册引用在范围内并且驱动器没有写到只读内存。IoDma提供了内建的DMA控制器来对早先的硬件的存取。IoIrq当一个硬件的中断到达的时候通知驱动器。IoMemory对包含内存映射注册或者与DMA使用相关的一块固定内存区域的边界检查。
驱动器-设备接口唯一不安全的地方就是DMA。已经存在的DMA体系结构不提供内存保护,所以行为不正常的或者有恶意的驱动器将对一个支持DMA设备编程来覆盖内存的一部分。因为DMA接口的多样性,我们还没有发现一个好的抽象来封装它们。我们期望未来的硬件将会对DMA传输提供内存保护。
从设备产生的中断将会由内核来服务,它将对中断进行掩码后,给适当的驱动器的IoIrq发信号。每一个驱动器进程都有一个线程用来等待它的Irq事件,这个线程处理中断并且使用通过内核ABI的中断线再次可能。计划器在中断处理后立即运行并给队列内的所有事件发信号。
5.2 驱动器配置
Singularity系统大量的使用了元数据,元数据用来描述系统的某一块,解释它们如何安装在一起,并且指定它们的行为。Singularity中的元数据给每一个Singularity的组件,系统,或者包含依赖关系的应用程序,出口,资源等等加上声明标签。Singularity中的工具在系统执行的前期和过程中间,使用这些元数据来校验并且配置应用程序和系统代码。
一个Singularity系统映像是一个混合的人工物品。它包含一个内核,设备驱动器,应用程序和充足的来形容这些独立人工物品的元数据。它还包含一个描述系统方针的对照单。对照单还指向描述单独组件的对照单。通过这些对照单,软件,比如一个启动加载器或者系统校验器,将可以发现Singularity系统内的每一个组件。
一个Singularity系统映像和它们的对照单对离线分析系统已经足够了。我们的目标是允许管理员仅仅使用硬件设备的描述和系统对照单来回答诸如:系统是否会在特定硬件上启动,哪些驱动器和服务将被初始化,哪些应用程序可以运行等等问题。
一个Singularity系统映像包含了描述设备驱动器的元数据。通过元数据,Singularity维护三个不变部分。第一,对于一个因为和其它驱动器或者系统的某部分有资源冲突而不能成功启动的设备驱动器,Singularity将永远不会安装它。第二,对于一个因为资源冲突或者资源缺失而不能成功启动的设备驱动器,Singularity将永远不会启动它。第三,一个设备驱动器在运行时将不能够存取在它的元数据中未声明的资源。
如果可能的话,Singularity使用C#的自定义属性来向源代码中插入元数据,所以只有一个源文件必须被维护。自定义属性可以附加到一个程序的实体上,比如类,方法或者字段声明。编译器把这些属性传给结果的MSIL二进制中。编译器,连接器,安装工具和校验工具可以不用从文件中执行代码就读取MSIL二进制中加密在属性中的元数据。
举个例子,下面的代码显示了一些属性,它们用来声明一个 S3Trio64 设备驱动器的依赖关系和资源需求。