一尘不染

如何快速动态加载经常重新生成的C代码?

linux

我希望能够动态生成C代码并将其快速重新加载到正在运行的C程序中。

我在Linux上,怎么做?

Linux上的库.so文件可以在运行时重新编译并重新加载吗?

是否可以在不生成.so文件的情况下进行编译,编译后的输出是否可以以某种方式进入内存,然后重新加载?我想快速重新加载已编译的代码。


阅读 373

收藏
2020-06-02

共1个答案

一尘不染

您想做的事情是合理的,我正在用MELT(用于扩展GCC的高级领域特定语言;通过使用MELT编写的翻译器本身将MELT编译为C)来做到这一点。

首先,在生成C代码(或许多其他源语言)时,一个好的建议是在内存中保留某种抽象语法树(AST)。因此,首先构建所生成的C代码的整个AST,然后将其作为C语法发出。不要想到没有显式AST的代码生成框架(换句话说,用一堆printf生成C代码是一个维护噩梦,您需要一些中间表示形式)。

其次,生成C代码的主要原因是要利用良好的优化编译器(另一个原因是C的可移植性和普遍性)。如果您不关心所生成代码的性能(TCC将C很快地编译为非常幼稚和慢速的机器代码),则可以使用其他方法,例如,使用某些JIT库,例如Gnu
lightning
(非常快地生成慢速机器)代码),Gnu
Libjit
ASMJIT(生成的机器代码要好一点),LLVMGCCJIT(生成的机器代码好,但生成时间可与编译器相比)。

因此,如果您生成C代码并希望其快速运行,那么C代码的编译时间就可以忽略不计(因为您可能会派生一个gcc -O -fPIC -shared
命令来foo.so 从您生成的代码中创建一些共享库foo.c)。根据经验,生成C代码比编译它(用gcc -O)花费的时间少得多。在MELT中,C代码的生成速度比GCC的编译速度快10倍以上(通常快30倍)。但是由C编译器完成的优化是值得的。

发出C代码,将其编译分支为.so共享库后,就dlopen可以使用。别害羞,我的manydl.c示例演示了在Linux上您可以dlopen大量共享对象(数十万个)。真正的瓶颈是生成的C代码的编译。实际上,您实际上并不需要dlclose在Linux上运行(除非您正在编写一个需要运行数月的服务器程序)。一个未使用的共享模块实际上可以保留dlopen-ed,并且您大多数时候正在泄漏进程地址空间(这是一种廉价的资源),因为大部分未使用的共享模块都.so将被交换掉。dlopen因为要快速完成,所以花费时间是C源代码的编译,因为您确实希望优化由C编译器完成。

您可以使用许多其他方法,例如使用字节码解释器并为该字节码生成,使用Common
Lisp(例如Linux上的SBCL,可动态编译为机器代码),LuaJit,Java,MetaOcaml等。

正如其他人的建议,你不要在乎写一个C文件的时候,它会停留在文件系统缓存在实践中(另见)。而且编写它比编译它要快得多,因此保留在内存中不值得麻烦。如果您担心I
/ O时间,请使用一些 tmpfs

附加物

您问

.soLinux上的库文件可以在运行时重新编译并 重新 加载吗?

当然可以:您应该派生一个命令,从生成的C代码构建库(例如,a gcc -O -fPIC -shared generated.c -o generated.so,但是您可以间接地做到这一点,例如,通过运行a make -j,特别是如果a
generated.so足够大以使其可以将generated.c生成的C
拆分成几个C文件!),然后使用dlopen动态加载您的库(给出了完整的路径,例如/some/file/path/to/generated.so,可能还有RTLD_NOW它的标志),并且必须使用它dlsym来查找内部的相关符号。不要以为
重新
加载(第二次)相同generated.so,最好发出一个唯一的generated1.c(然后generated2.c等…)C文件,然后将其编译为一个
唯一的
generated1.so(第二次到generated2.so等),然后至dlopen它(可以完成数十万次)。您可能希望在发出的generated*.c文件中包含一些构造函数,这些构造函数将dlopengenerated*.so

您的基本应用程序应该已经定义了有关dlsym
-ed名称集(通常是函数)以及如何调用它们的约定。它仅应直接在generated*.so通过dlsym函数的指针中调用函数。在实践中,你将决定例如,每个generated*.c定义一个函数void dynfoo(int),并int dynbar(int,int)和使用dlsym"dynfoo""dynbar"和调用这些通函数指针(由返回dlsym)。你也应该定义如何以及何时这些公约dynfoo,并dynbar会被调用。您最好将基本应用程序与链接,-rdynamic以便generated*.c文件可以调用应用程序函数。

希望你generated*.so重新定义 现有的
名称。例如,您不想重新定义malloc自己,generated*.c并希望所有堆分配函数都神奇地使用您的新变体(这可能不起作用,即使这样做也很危险)。

dlclose除了在应用程序清理和退出时,您可能不会理会动态加载的共享对象(但我完全不会理会dlclose)。如果您要dlclose动态加载generated*.so文件,请确保其中没有使用任何文件:没有指针,甚至没有调用帧中的返回地址。

PS MELT转换器当前将MELT代码的57KLOC转换为C语言的将近1770KLOC。

2020-06-02