zoukankan      html  css  js  c++  java
  • Linux共享库(so)动态加载和升级

    >>转载请注明来源:飘零的代码 piao2010 ’s blog,谢谢!^_^
    >>本文链接地址:Linux共享库(so)动态加载和升级

    学习Linux共享库动态加载缘于一个生产环境升级apache so文件常见错误操作:apache在运行中直接cp覆盖目标so文件,一段时间后错误日志里面出现关键词:Segmentation fault (段错误) ,一个个worker进程就这样渐渐退出,最后无法处理HTTP请求。
    首先了解一下共享库的创建,源文件test.c

    #include<stdio.h>
    #include<unistd.h>
     
    void test1(void){
        printf("This is do test1\n");
        sleep(10);
        printf("End of test1\n");
    }
     
    void test2(void){
        printf("This is do test2\n");
        sleep(10);
        printf("End of test2\n");
    }

    执行gcc -fPIC -shared -o libtest.so test.c 会生成共享库文件 libtest.so
    参数含义:
    -fPIC/-fpic: Compiler directive to output position independent code, a characteristic required by shared libraries. 创建共享库必须的参数
    -shared: Produce a shared object which can then be linked with other objects to form an executable.

    然后使用共享库:源文件main2.c

    #include <stdio.h>
     
    int main()
    {
        test1();
        test2(); 
        return 0;
    }

    动态库链接:gcc -o main2 -L . -ltest main2.c 生成二进制程序main2
    参数含义:
    -L 指定动态库目录为当前目录
    -l 指定动态库名test,不要写libtest.so

    执行main2程序发现报错:error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory
    原因是共享库不在系统默认的路径里面,可以在shell执行export LD_LIBRARY_PATH=./ 添加当前路径或者在 /etc/ld.so.conf 增加路径并ldconfig生效
    执行main2成功输出:
    This is do test1
    End of test1
    This is do test2
    End of test2

    接下来是主角:动态加载,源文件main.c

    #include <stdio.h>
    #include <dlfcn.h> /* 必须加这个头文件 */
     
    int main()
    {
       void *lib_handle;
       void (*fn1)(void);
       void (*fn2)(void);
       char *error;
     
       lib_handle = dlopen("libtest.so", RTLD_LAZY);
       if (!lib_handle) 
       {
          fprintf(stderr, "%s\n", dlerror());
          return 1;
       }
     
       fn1 = dlsym(lib_handle, "test1");
       if ((error = dlerror()) != NULL)  
       {
          fprintf(stderr, "%s\n", error);
          return 1;
       }
     
       fn1();
     
       fn2 = dlsym(lib_handle, "test2");
       if ((error = dlerror()) != NULL)  
       {
          fprintf(stderr, "%s\n", error);
          return 1;
       }
     
       fn2();
     
       dlclose(lib_handle);
     
       return 0;
    }

    接口函数介绍:
    (1) dlopen
    函数原型:void *dlopen(const char *libname,int flag);
    功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。
    如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。
    参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:
    a.根据环境变量LD_LIBRARY_PATH查找
    b.根据/etc/ld.so.cache查找
    c.查找依次在/lib和/usr/lib目录查找。
    flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内 存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

    (2) dlerror
    函数原型:char *dlerror(void);
    功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

    (3) dlsym
    函数原型:void *dlsym(void *handle,const char *symbol);
    功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。
    如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

    (4) dlclose
    函数原型:int dlclose(void *);
    功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

    编译gcc -o main main.c -ldl 生成二进制程序main,执行输出
    This is do test1
    End of test1
    This is do test2
    End of test2

    到这里共享库动态加载就介绍完了:)
    最后模拟一下升级so故障:
    在执行main的时候,趁sleep期间cp 另外的so文件覆盖libtest.so,一会就出现Segmentation fault。
    但是如果是mv 另外的so文件覆盖libtest.so,则无此问题,或者先rm libtest.so 再cp/mv 也不会有问题,因此升级方法就是这两种,当然最好是先停应用再升级。至于原因,可以参考我前一篇博客《Linux cp mv rm ln 命令对于 inode 和 dentry 的影响》


    12.5更新:
    今天咨询了维扬同学,可以用strace观察程序运行期间的系统调用,发现有不少mmap操作:

    省略前面
    open("/root/so/libtest.so", O_RDONLY)   = 3
    read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240\3\0\0004\0\0\0"..., 512) = 512
    brk(0)                                  = 0x8227000
    brk(0x8248000)                          = 0x8248000
    fstat64(3, {st_dev=makedev(253, 0), st_ino=17559, st_mode=S_IFREG|0755, st_nlink=1, st_uid=0, st_gid=0, st_blksize=4096, st_blocks=16, st_size=4348, st_atime
    =2012/05/13-14:13:18, st_mtime=2012/05/13-14:13:01, st_ctime=2012/05/13-14:13:01}) = 0
    mmap2(NULL, 5772, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x6e6000
    mmap2(0x6e7000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0) = 0x6e7000
    close(3)                                = 0
    munmap(0xb7753000, 15020)               = 0
    fstat64(1, {st_dev=makedev(0, 11), st_ino=3, st_mode=S_IFCHR|0620, st_nlink=1, st_uid=0, st_gid=5, st_blksize=1024, st_blocks=0, st_rdev=makedev(136, 0), st_
    atime=2012/05/13-14:56:03, st_mtime=2012/05/13-14:56:03, st_ctime=2012/05/13-14:53:31}) = 0
    mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7756000
    write(1, "This is do test1\n", 17)      = 17
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({10, 0}, 0xbfd63fe4)          = 0
    write(1, "End of test1\n", 13)          = 13
    write(1, "This is do test2\n", 17)      = 17
    rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
    rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
    rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
    nanosleep({10, 0}, 0xbfd63fe4)          = 0
    --- SIGSEGV (Segmentation fault) @ 0 (0) ---
    +++ killed by SIGSEGV +++

    SIGSEGV信号估计和mmap只读映射之后写入(覆盖)文件有关?
    详见续篇《为何cp覆盖进程的动态库(so)会导致coredump》

    参考资料:
    http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
    http://hi.baidu.com/luoxsbupt/item/a9d346b7653a2771254b09bc

  • 相关阅读:
    【转+补充】在OpenCV for Android 2.4.5中使用SURF(nonfree module)
    Delphi StarOffice Framework Beta 1.0 发布
    Angular ngIf相关问题
    angularjs文档下载
    公众号微信支付开发
    公众号第三方平台开发 教程六 代公众号使用JS SDK说明
    公众号第三方平台开发 教程五 代公众号处理消息和事件
    公众号第三方平台开发 教程四 代公众号发起网页授权说明
    公众号第三方平台开发 教程三 微信公众号授权第三方平台
    公众号第三方平台开发 教程二 component_verify_ticket和accessToken的获取
  • 原文地址:https://www.cnblogs.com/cnland/p/2969337.html
Copyright © 2011-2022 走看看