如何在Linux上热重新加载共享库

How to Hot Reload shared library on Linux

本文关键字:加载 共享 新加载 Linux      更新时间:2023-10-16

我正在尝试从Casey Muratori流行的手工英雄系列中复制一个很酷的技巧。在Win32上,Casey能够重新加载DLL,看到他的代码更改,只有几毫秒的延迟。

我正在尝试使用dlopen,dlsym,dlclose和stat在Linux上复制这种行为,但是我遇到了以下行为,并且我有一个直觉,我要么误解了关于小精灵的某些东西,例如链接器,或者也许是共享对象的概念。

我能够使他的代码在Win32上毫无困难,所以我觉得这是我缺少的Linux的。

我正在使用cmake构建,但我不特别相信cmake是罪魁祸首。

i制作共享库动态的副本,然后加载。每当原始共享对象更新的MTIME时,我都会关闭旧副本的句柄,制作新副本,然后尝试加载新副本。

我想指出的是,我打算在第一次更改后打破循环,因为我只是想弄清楚这一点。


#include <stdio.h>                                                                                                                                                                                                                                                                                                                                                                                                                                   
#include <dlfcn.h>                                                                                                                                                                                                                        
#include <time.h>                                                                                                                                                                                                                         
#include <sys/stat.h>                                                                                                                                                                                                                     
#include <unistd.h>      
void
CopyFile(const char* src, const char* dest)
{
  FILE* fsrc;
  FILE* fdest;
  unsigned char buffer[512];
  size_t bytes;
  fprintf(stderr, "copy from: %s to %s!n", src, dest);
  fsrc = fopen(src, "rb");
  if ( fsrc == NULL )
    ┆   fprintf(stderr, "failed to open file: %s for readingn", src);
  fdest = fopen(dest, "wb");
  if ( fdest == NULL )
    ┆   fprintf(stderr, "failed to open file: %s for readingn", src);
  while ( (bytes = fread(buffer, 1, sizeof(buffer), fsrc)) > 0 )
    {
    ┆   fwrite(buffer, 1, bytes, fdest);
    }
  fclose(fsrc);
  fclose(fdest);
  fprintf(stderr, "copy complete!n");
}
int main(int argc, char** argv)
{
const char* libpath = "/home/bacon/dynamic.so";
const char* copypath = "/home/bacon/dynamic-copy.so";
CopyFile(libpath, copypath);
void* handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
if ( handle == NULL )
    fprintf(stderr, "failed to load %s, error = %sn", copypath, dlerror());
struct stat s;
stat(libpath, &s);
time_t oldtime = s.st_mtime;
while (true)
{
    stat(libpath, &s);
    if ( oldtime != s.st_mtime )
    {
        if ( handle != NULL )
        {
            if ( dlclose(handle) )
                fprintf(stderr, "dlclose failed: %sn", dlerror());
            else
                handle = NULL;
        }
        CopyFile(libpath, copypath);
        handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
        if ( handle == NULL )
            fprintf(stderr, "failed to load %s, error = %sn", copypath, dlerror());
        break;
    }
}
}

至于动态库,任何事情都应该执行(示例标题(:

#ifndef DYNAMIC_HEADER
#define DYNAMIC_HEADER 1
#define DYNAMIC_API __attribute__ ((visibility("default")))
extern "C" DYNAMIC_API int
Add(int x, int y);
#endif /* DYNAMIC_HEADER */

和源文件:

#include "Dynamic.h"
int
Add(int x, int y)
{
    return x + y;
}

共享库只提供了一些例程将几个数字添加在一起,我已经验证了我能够在没有热重加载技巧的情况下dlopen和dlsym。

我还验证了我的副本例程实际上复制了共享对象。

我希望最初的dlopen成功,而dlsym可以正确链接添加(它将(。然后,我会编辑Dynamic.cpp,也许返回X X Y或其他内容,保存文件并重新编译,期望While Loop拾取ST_MTIME的更改。

我注意到,当我运行代码并更新时,我收到了错误:

dlopen: file too short

果然,当我ls -la包含共享对象的目录时,副本为0。

以某种方式,STAT报告的ST_MTIME已更新,但是共享对象的实际内容为空?链接器是否锁定共享对象并防止读取?

如果我的代码并没有非常错误,我该如何规避这种行为?

我不愿睡觉和重试,因为这是一个相当瞬时的更新。

如果我的代码没有可怕的错误

这是非常错误的:您的代码正在使用(静态(链接器进行赛车(由makecmake调用(。

make运行时,它(最终(调用:

gcc -shared -o /home/bacon/dynamic.so foo.o bar.o ...

然后,链接器将执行open("/home/bacon/dynamic.so", O_WRONLY|O_CREAT, ...)(或同等(,一段时间后将write,最后close文件。

m_time更改时,您的程序将醒来,即open之后的任何时间,并将尝试复制文件。如果您的副本在最终close之前的任何时间发生,则最终可能会获得部分副本(包括包含0个字节的部分副本(。

最明显的解决方案是Zsigmond建议的解决方案:您必须修改Makefile以链接您正在观看的不同的文件,并将mv执行到最终目的地(原子((步骤。

另一种解决方案是具有依赖dynamic.somake目标,例如

dynamic.so.done: dynamic.so
        touch dynamic.so.done

在您的程序中,您会查看m_timedynamic.so.done,并且只有在更新之前,请执行dynamic.so的副本(保证该副本是close d,by thit Point(。<<<<<<<<<<<<<。/p>