13.7. 源代码

13.7.1. 代码风格

我们有意在Open MPI代码库中不设置过多的编码规范。

13.7.1.1. 所有语言

  • 4个空格制表符。不多不少。

  • 绝对不要使用实际的制表符;始终使用空格。无论是emacs还是vim都有隐藏功能,可以在你按下键时自动使用空格。这样无论在不同的浏览器中如何设置制表符显示,代码看起来都是一致的。

13.7.1.2. C / C++

  • 在进行常量相等或不相等比较时,始终将常量放在左侧。这是一种防御性编程:如果测试中存在拼写错误并遗漏了!=,将会产生编译器错误。例如:

    /* 正确做法 */
    if (NULL == foo) { ... }
    
    /* 因为如果出现拼写错误(比如用=代替==),这将产生编译错误而非难以察觉的bug */
    if (NULL = foo) { ... }
    
  • 更防御性的编程:始终使用大括号{ }包裹代码块,即使只有一行代码。例如:

    /* 这样做 */
    if (whatever) {
        return OMPI_SUCCESS;
    }
    
    /* 不要这样做 */
    if (whatever)
       return OMPI_SUCCESS;
    
  • 从Open MPI 1.7版本开始,Open MPI需要一个符合C99标准的编译器。

    • 现在允许(并推荐)使用C++风格的注释。

    • 允许(且推荐)使用C99风格的混合声明。

  • 始终_config.h作为你的第一个#include文件,其中可以是ompioshmemopal——这取决于你正在编写的层级。只有极少数情况下不需要这样做(例如某些特殊的Windows场景)。但在99.9999%的情况下,这个文件应该被首先包含,以便在必要时影响系统级的#include文件。

  • 文件名和符号必须遵循前缀规则(参见[邮件讨论](http://www.open-mpi.org/community/lists/devel/2009/07/6389.php)):

    • 文件名必须以_作为前缀。

    • 公共符号在组件中必须使用前缀__,其中可以是mcaompioshmemopal之一。请注意mca过去是最常用的前缀,但现在相比其他前缀已不再受欢迎。如果不确定某个符号是否为公共符号,为安全起见请添加前缀。

    • 非公开符号必须声明为static或采取其他方式使其不出现在全局作用域中。

  • 始终 #define宏,即使是逻辑值。

    • GNU的方式是当宏为"真"时使用#define定义,当宏为"假"时使用#undef取消定义。

    • 在Open MPI中,我们始终使用#define将逻辑宏定义为0或1——我们从不使用#undef取消定义。

    • 这样做是出于防御性编程的考虑:如果你只是检查预处理宏是否定义(通过#ifdef FOO#if defined(FOO)),当宏名称拼写错误时,编译时不会收到任何警告。然而,如果你使用逻辑测试#if FOO但宏未定义(例如由于拼写错误),你将收到编译器警告或错误。

      设计原理

      当拼写错误的宏名称隐藏在数千行代码中时,可能极难发现;我们将尽可能利用预处理器/编译器提供的所有帮助!

    /* GNU Way - you will get no warning from the compiler if you
       misspell "FOO"; the test will simply be false */
    #ifdef FOO
    ...
    #else
    ...
    #endif
    
    /* Open MPI Way - you will get a warning from the compiler if you
       misspell "FOO"; the result of the test is a different value
       than whether you spelled the macro name right or not */
    #if FOO
    ...
    #else
    ...
    #endif
    

13.7.1.3. Fortran

我们没有针对Fortran的具体编码风格指南。请阅读源代码树中现有的Fortran代码,并尝试使用类似的风格。

13.7.1.4. Shell脚本编写

请阅读源代码树中的一些现有shell代码,并尝试使用类似的风格。

  • 始终将评估的shell变量用引号括起来,以确保正确处理多标记值。

    # This is bad
    if test $foo = bar; then
    
    # This is good
    if test "$foo" = "bar"; then
    
    • 唯一的例外是,当将一个shell变量的值赋给另一个shell变量时,右侧不需要使用引号:

      # 这样做无害但多余
      foo="$bar"
      
      # 这样写就足够了,即使$bar包含多个标记
      foo=$bar
      
  • 不要在test命令中使用==运算符——这是GNU的扩展功能,在BSD系统上可能导致可移植性问题。应该使用单个=运算符。

    # 错误用法
    if test "$foo" == "bar"; then
    
    # 正确用法
    if test "$foo" = "bar"; then
    

13.7.1.5. m4

我们没有针对m4语言(用于创建configure脚本)的具体编码风格指南。请阅读源代码树中现有的m4代码,并尝试使用类似的风格。

13.7.2. 树状布局

源代码树中有几个值得注意的顶级目录:

  • 主要子项目:

    • oshmem: 顶层的OpenSHMEM代码库

    • ompi: Open MPI代码库

    • opal: OPAL代码库

  • config: 支持顶层configure脚本的M4脚本 mpi.h

  • etc: 一些杂项文本文件

  • docs: Open MPI文档的源代码

  • examples: 简单的MPI/OpenSHMEM示例程序

  • 3rd-party: 包含所需核心库的副本(通过Git克隆中的Git子模块或二进制压缩包提供)。

    注意

    虽然可能被认为不太常见,但我们为第三方项目提供了二进制压缩包(而非Git子模块),这些项目包括:

    1. Open MPI正常运行所需,以及

    2. 并非所有操作系统发行版都默认包含,以及

    3. 很少更新。

三个主要源代码目录(oshmemompiopal)各自至少会生成一个顶级库,分别命名为liboshmemlibmpilibopen-pal。这些库可以构建为静态库或共享库。某些目录树下还会生成可执行文件。

libopen-pal顶层库在内部构建时分为两部分:

  • libopen-pal_core OPAL内部的核心组件,包含mpicc/mpirun等工具链接所需的基础源码和MCA框架。该"核心"库不会被安装。

    • 包含以下MCA框架:backtrace, dl, installdirs, threads, timer

    • 包含opal/class目录下的所有源代码以及opal/util目录下的大部分内容

    • 包含opal/runtime目录中后缀为_core的文件

  • libopen-pal 包含"核心"以及所有其他OPAL项目源代码。该库会被安装。

每个子项目源代码目录下都有类似(但不完全相同)的目录结构:

  • class: 本项目特有的类(使用OPAL类系统),类似C++的"类"

  • include: 本项目专用的顶层包含文件

  • mca: 本项目特定的MCA框架和组件

  • runtime: 项目在运行时的启动和关闭

  • tools: 本项目专用的可执行文件

  • util: 随机实用代码

每个子项目中还有其他顶级目录,这些目录与该项目特定的逻辑和代码相关。例如,MPI API的实现可以在ompi/mpi/LANGUAGE下找到,其中LANGUAGE可以是cfortranjava

mca 目录树的布局有严格定义,其格式如下:

PROJECT/mca/FRAMEWORK/COMPONENT

明确说明:在mca目录树下,不允许存在不符合此模板的目录(下文将解释的base目录除外)。因此,只有框架和组件代码可以存放在mca目录树中。

也就是说,框架和组件名称必须是有效的目录名(以及C变量;稍后会详细说明)。例如,TCP BTL组件位于opal/mca/btl/tcp/

名称base是保留关键字;不能存在名为base的框架或组件。名为base的目录专用于MCA和框架的实现。以下是几个示例(基于v5.0.x源码树):

# Main implementation of the MCA
opal/mca/base

# Implementation of the btl framework
opal/mca/btl/base

# Implementation of the sysv framework
oshmem/mcs/sshmem/sysv

# Implementation of the pml framework
ompi/mca/pml/base

在这些指定的目录下,框架和/或组件可以拥有任意的目录结构。

13.7.3. 符号可见性

*_DECLSPEC宏提供了一种方法来标注符号,以指示在编译动态共享对象文件(例如libmpi.so)时它们的预期可见性。这些宏是基于每个项目定义的:

  • Open MPI: OMPI_DECLSPEC

  • Open PAL: OPAL_DECLSPEC

  • OpenSHMEM: OSHMEM_DECLSPEC

这些宏展开为适当的编译器和平台标志,用于标记符号是否应在目标项目的库命名空间中显式公开。*_DECLSPEC属性用于声明符号在该库/DSO范围之外可见。例如,OMPI_DECLSPEC用于控制哪些符号在libmpi.so范围内可见。

注意

这完全与动态库编译相关,不适用于静态编译。

注意

这些宏最初是在Open MPI支持Windows时引入的(大约在Open MPI v1.0.0版本),其设计灵感来源于Windows的__declspec。虽然Open MPI已不再支持Windows平台,但这些符号可见性宏仍然保留了下来。