GraalVM 与原生代码交互

GraalVM LLVM 运行时允许用户运行传统上直接编译为原生代码的语言编写的代码。这些语言通常不需要任何托管运行时或 VM 才能运行。因此,在考虑此代码与 GraalVM 托管运行时 的交互时需要特别注意,尤其是当代码使用某些底层功能时。

对底层系统调用的有限访问 #

  • 信号处理基于以下假设执行
    • 托管运行时假定它完全控制处理所有信号。
    • 安装的信号处理程序的行为可能与原生执行中的行为不同。
  • 进程控制和线程基于以下方面完成
    • GraalVM 假定它完全控制线程。
    • 通过 pthreads 库(例如 pthread_create)支持多线程。
    • 不支持直接使用与进程相关的系统调用,例如 cloneforkvfork 等。
    • 不支持 exec 函数族。

内存布局 #

在 GraalVM 上运行的进程的内存和堆栈布局与直接原生执行不同。特别是,无法对全局变量、堆栈变量等的相对位置做出任何假设。

仅可以使用 GraalVM API 遍历堆栈。代码和数据之间严格分离。自修改代码将无法工作。不支持对代码指针的读取、写入和指针算术。

与原生模式下的系统库交互 #

在原生执行模式(默认模式)下,在 GraalVM LLVM 运行时上运行的代码可以调用真正的原生库(例如,系统库)。这些调用的行为类似于 Java 中的 JNI 调用:它们暂时离开托管执行环境。

由于在这些库中执行的代码不受 GraalVM 控制,因此该代码本质上可以执行任何操作。特别是,不适用任何多上下文隔离,并且 GraalVM API(如虚拟文件系统)被绕过。

请注意,这尤其适用于大多数标准 C 库。

托管执行模式 #

托管模式(使用 --llvm.managed 选项启用)是一种特殊的执行模式,其中 LLVM 运行时完全在托管模式下运行,类似于所有其他 GraalVM 支持的语言。

注意:托管模式仅在 Oracle GraalVM 中可用。

在此模式下,按设计,不允许调用原生代码并访问原生内存。所有内存都由垃圾收集器管理,并且所有应该运行的代码都需要编译为位代码。

指针算术仅限于 C 标准允许的程度。特别是,溢出被阻止,并且无法通过越界访问访问不同的分配。所有此类无效访问都会导致运行时异常,而不是未定义行为。

在托管模式下,GraalVM 模拟了一个虚拟的 Linux/AMD64 操作系统,使用 musl libclibc++ 作为 C/C++ 标准库。所有代码都需要为此系统编译,然后才能用于在 GraalVM 支持的任何架构或操作系统上运行。系统调用被虚拟化并通过适当的 GraalVM API 路由。

原生代码和托管语言之间的多语言交互 #

当使用 LLVM 语言(例如,C/C++)和托管语言(例如,JavaScript、Python 或 Ruby)之间的多语言互操作性时,需要对手动内存管理小心。请注意,本节仅适用于原生执行模式(默认模式)。在托管模式(使用 --llvm.managed 选项启用,仅在 Oracle GraalVM 中可用)下,LLVM 运行时本身的行为类似于托管语言,并且多语言交互与其他托管语言之间相同。

  • 要考虑的垃圾收集策略
    • 指向托管语言对象的指针由垃圾收集器管理,因此它们不需要手动释放。
    • 另一方面,指向 LLVM 代码分配的指针(例如,malloc)不受垃圾收集器的控制,因此它们需要手动释放。
  • 要考虑的非托管堆策略
    • 原生内存(例如,malloc、数据段、线程本地变量)不受垃圾收集器的控制。
    • 无法将指向垃圾收集器控制的外部对象的指针直接存储在原生内存中。
    • 有处理函数可用于解决此限制(参见 graalvm/llvm/handles.h)。

联系我们