GraalVM 与原生代码的交互

GraalVM LLVM 运行时允许用户运行以传统上直接编译为原生代码的语言编写的代码。这些语言通常不需要任何托管运行时或虚拟机即可运行。因此,需要特别注意此类代码与 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)。

联系我们