charm++程序基本上是一个C++程序,其中一些组件描述其并行结构。串行代码可以使用任何编程技术,与C++工具链协作。这包括C和Fortran。用户代码中的并行实体是用C++编写的。这些实体与charm++框架通过继承类和函数调用进行交互。
所有用户的程序组件,包括并行接口(如消息、chare,入口方法等)通过在单独的charm++接口(charm interface)描述文件中定义或描述他们来授予这一更高层状态。这些文件有.ci后缀,并采用了一个类似C++的声明语法,带有几个额外的关键字。在一些声明的上下文中,它们还可以包含一些串行的C++源代码。charm++解析这些接口描述并且生成C++代码(基础类、实用类,封装的函数等),便于用户程序的实体与框架交互。一个程序可以有几个接口描述文件。
在一个文件中的顶层结构是一个名为容器的接口声明称为模块。模块允许将相关声明组合在一起,并使得这些声明的生成代码被分组到已模块命名的文件中。模块不能嵌套,但每个.ci文件可以有多个模块。使用关键字module指定模块。
module myFirstModule { // 这些是并行接口声明 ... };
在一个ci文件中出现的每个模块都被解析以生成2个文件。这些文件的基名都同样是模块名,其后缀名是.decl.h和.def.h。例如,前面定义的模块会产生“myfirstmodule.decl.h”和”myfirstmodule .def.h”。就像后缀表明的,它们分别包含基于并行接口文件生成的所有的类和函数的声明(declarations)和定义(definitions)。
我们建议包含声明的头文件(decl.h)被包含在相应的模块中提到的用户程序实体的声明或定义文件的顶部。def.h实际上不是头文件,因为它包含生成实体的定义。为了避免多个定义错误,应该将它编译成一个对象文件。我们找到一个有用的惯例是把def.h文件放在包括相应的用户程序实体的定义源文件(.c,.cpp,.cc等)的底部。
需要注意的是,生成的文件没有依赖ci文件的名称只依赖模块的名称。这使自动化的基于依赖的构建系统稍微复杂一点。我们采取一些惯例来简化这个过程。这些在[ * ]里描写。
模块可依赖于另一个模块中声明的并行实体。它可以使用extern关键字表达这种依赖。外部模块不必出现在同一文件中。
module mySecondModule { // 这个模块的实体依赖另声明在另一个模块里的实体 extern module myFirstModule; // 更多的并行接口声明 ... };
extern关键字在当前模块生成的代码中放置了一个外部扩展模块decl.h文件的include。因此,从外部模块生成decl.h文件是当前模块的源代码编译过程中需要的。这通常是必需的,因为在2个模块交叉的用户程序实体之间的依赖关系。
charm++软件可以包含几个模块的定义,他们来自几个独立开发的库/组件。然而,用户程序必须指定一个模块,该模块包含程序的执行起点。这个模块是主模块(mainmodule)。每一个charm++程序必须包含且只有一个主模块。
所有的模块都“可达”模块,从主模块沿着扩展模块依赖链被包含在一个charm++程序。更确切地说,在程序执行期间,charm++运行时系统将只识别在可达模块中声明的用户程序实体。decl.h和def.h文件可能 被其他模块生成,但运行时系统不知道这些不可达模块。
module A { ... }; module B { extern module A; ... }; module C { extern module A; ... }; module D { extern module B; ... }; module E { ... }; mainmodule M { extern module C; extern module D; // 只要A,B,C,D模块是可达的且被运行时系统可知。 // 模块E通过扩展模块链是不可达的。 ... };
有可能会出现从模块定义生成的代码需要使用用户程序的串行代码中的其他声明/定义的情况。通常,这可以通过将这些用户代码放在decl.h文件包含点的前面来实现。然而,如果decl.h必须在多个地方包含,这样就会变得费劲。charm++在ci文件中支持include关键字允许任何头文件的包含直接纳入到生成的decl.h文件中。
module A { include "myUtilityClass.h"; //< 注意分号 // 依赖于myUtilityClass的接口定义 ... }; module B { include "someUserTypedefs.h"; // 需要用户类型定义的接口声明 ... }; module C { extern module A; extern module B; // 用户包含的文件将在这里间接可见 ... };
charm++框架实现了它自己的main函数,并保留控制到并行执行环境被初始化并准备执行用户代码。因此,用户程序一定不能定义main()函数。控制进入用户代码是通过mainmodule里的mainchare。这将在[*]中进一步细节的讨论。
使用到目前为止描述的工具,为charm++程序声明的并行接口可以跨多个ci文件和多个模块,允许在分组和输出的并行接口上的良好控制。这些特性有助于并行软件的封装。
charm++提供了一个封装的编译器称为charmc,它处理所有的ci,C,C++和Fortran源文件,这些是用户程序的一部分。用户可以调用charmc解析接口说明,编译源码并且链接成二进制文件。在生成二进制时它还链接了适当的一组charm框架对象和库。charmc及其功能在附录B中描述。