zoukankan      html  css  js  c++  java
  • 关于C ++:如何在Linux上热重载共享库

    我正在尝试从Casey Muratori受欢迎的Handmade Hero系列中复制一个很酷的技巧。在win32上,Casey能够重新加载DLL,并且仅几毫秒的延迟就可以看到他的代码更改。

    我正在尝试使用dlopen,dlsym,dlclose和stat在linux上复制此行为,但是我遇到了以下行为,并且我有一种直觉,就是我误解了ELF,示例,链接器,或共享对象的概念。

    我能够在win32上轻松完成他的代码工作,所以我觉得这是我所缺少的特定于Linux的东西。

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

    我将共享库复制为dynamic.so,然后加载它。每当原始共享库的mtime更新时,我都会关闭旧副本的句柄,制作一个新副本,然后尝试加载新副本。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    #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!\
    "
    , src, dest);

      fsrc = fopen(src,"rb");
      if ( fsrc == NULL )
        ┆   fprintf(stderr,"failed to open file: %s for reading\
    "
    , src);

      fdest = fopen(dest,"wb");
      if ( fdest == NULL )
        ┆   fprintf(stderr,"failed to open file: %s for reading\
    "
    , src);

      while ( (bytes = fread(buffer, 1sizeof(buffer), fsrc)) > 0 )
        {
        ┆   fwrite(buffer, 1, bytes, fdest);
        }

      fclose(fsrc);
      fclose(fdest);

      fprintf(stderr,"copy complete!\
    "
    );
    }

    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 = %s\
    "
    , 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: %s\
    "
    , dlerror());
                else
                    handle = NULL;
            }

            CopyFile(libpath, copypath);

            handle = dlopen(copypath, RTLD_NOW | RTLD_GLOBAL);
            if ( handle == NULL )
                fprintf(stderr,"failed to load %s, error = %s\
    "
    , copypath, dlerror());

            break;
        }
    }
    }

    至于动态库,任何事情都应该做(示例标头):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #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 */

    和源文件:

    1
    2
    3
    4
    5
    6
    7
    #include"Dynamic.h"

    int
    Add(int x, int y)
    {
        return x + y;
    }

    共享库仅提供了一些例程以将一些数字加在一起,并且我已经验证了我能够进行dlopen和dlsym而无需进行热重装。

    我还验证了我的复制例程实际上复制了共享库。

    我期望初始dlopen成功,并且dlsym正确链接Add(这样做)。然后,我将编辑Dynamic.cpp,并可能返回x + x + y或其他内容,保存文件并重新编译,并期望while循环获取st_mtime中的更改。

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

    1
    dlopen: file too short

    果然,当我在包含共享库的目录中执行ls -la时,副本的大小为0。

    某种程度上,由stat报告的st_mtime已更新,但是共享库的实际内容为空?链接器是否锁定共享对象并防止读取?

    如果我的代码不是完全错误,我该如何规避这种行为?

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


    If my code isn't horribly wrong

    完全错误:您的代码正在使用(静态)链接程序(由make或cmake调用)。

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

    1
    gcc -shared -/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到最终目标作为最后(原子)步骤。

    另一种解决方案是使make目标取决于dynamic.so,例如

    1
    2
    dynamic.so.done: dynamic.so
            touch dynamic.so.done

    在您的程序中,您将在m_time中监视dynamic.so.done,并且只有在更新该文件时,才执行dynamic.so的副本(保证此时已被close d复制)。

  • 相关阅读:
    awk二十问-【AWK学习之旅】
    awk十三问-【AWK学习之旅】
    awk分割列-【AWK学习之旅】
    securecrt重建
    SQuirrel-GUI工具安装手册-基于phoenix驱动
    phoenix部署手册-基于hbase
    MySQL5.6一键部署
    CSV文件导入导出MySQL
    Percona-Server-5.7.16 启动错误
    Linux下安装php加速软件Xcache
  • 原文地址:https://www.cnblogs.com/lidabo/p/15508797.html
Copyright © 2011-2022 走看看