Ruby Ruby require 与动态链接库

zhuoerri · 2021年08月08日 · 579 次阅读

前言

本人水平有限,如有错误,欢迎指正与补充。

本文会先介绍 require 的基本使用,然后介绍动态链接库的基本知识。

最后深入 ruby 源码,查找对 dlopen, dlsym 的调用

require

ruby 里加载第三方 gem 的方式,通常是用 require 加载其 lib/ 文件夹下的某 .rb 文件。

但除了.rb 文件,gem 内也可以包含 c 语言写的源码,源码入口函数名往往是例如 Init_xxx 的格式。当我们用 gem install 安装 gem 的过程中,c 源码被编译成,例如名叫 xxx.so 的动态链接库。

最后 require 时,我们可以像加载 .rb 文件一样。用 require 'xxx.so' 加载动态链接库内的Init_xxx 函数。

动态链接库

对于不了解链接的同学,推荐 《深入理解计算机系统》 的第七章链接部分。或者 《程序员的自我修养》 也行。简单来说,链接就是把不同的 c 语言库和在一起,方便相互之间调用各自的函数与变量。

这个和的过程,可以是在编译时完成,称作静态链接。代价是最终的可执行文件较大。

这个和的过程,也可以推迟到程序启动时才完成,称作动态链接。优点是编译时,可执行文件只需记下自己依赖的库(linux 下可用 ldd 查看程序依赖哪些库),不用实际链接。等到启动时再实际链接,除了节省可执行文件的磁盘空间,也因为多个可执行文件共享同一个库,节省了内存。

这个和的过程,甚至可以在程序运行时进行。方法是调用 dlopen 函数加载动态链接库,再用 dlsym 获取动态链接库内的函数引用。

综上不难猜到,ruby require 动态链接库的过程,就是 调用 dlopen 加载动态链接库,再用 dlsym 获取 Init_xxx 函数指针并调用的过程。

ruby 源码

require 对应的 c 函数是在 load.c 中的 rb_f_require , 精简省略后,主要的调用栈如下

/* load.c */

void
Init_load(void)
{
  rb_define_global_function("require", rb_f_require, 1);
}

VALUE
rb_f_require(VALUE obj, VALUE fname)
{
     /* 调用 rb_require_string */
    return rb_require_string(fname);
}

VALUE
rb_require_string(VALUE fname)
{
   /* 省略 */

  /* 调用 require_internal */
  int result = require_internal(ec, fname, 1);
}

static int
require_internal(rb_execution_context_t *ec, VALUE fname, int exception)
{
  /*  省略 */  

  /* 调用 rb_vm_call_cfuncs, 其中 load_ext 参数是回调函数,也是实际被调用的函数。path 是动态链接库的文件地址,其他的参数可以忽略 */
  handle = (long)rb_vm_call_cfunc(rb_vm_top_self(), load_ext,
                            path, VM_BLOCK_HANDLER_NONE, path);
}

static VALUE
load_ext(VALUE path)
{
  /* 调用 dln_load,  RSTRING_PTR(path)参数是把path转成字符指针类型 */
  return (VALUE)dln_load(RSTRING_PTR(path));
}

最终实际调用dlopen的函数在 dln.c 的 dln_load 中,其中有针对不同操作系统的宏判断,这里只精简显示 linux 相关的 dlopen

/* dln.c */

void*
dln_load(const char *file)
{
     /* 省略 */

     /* buf存储动态链接库的入口函数名,即例子中的 Init_xxx */
    char *buf;
    init_funcname(&buf, file);

    /* 调用dlopen , 获取动态链接库对应的 handle指针 */
    handle = (void*)dlopen(file, RTLD_LAZY|RTLD_GLOBAL))
    /* 调用dlsym, 获取 buf即例子Init_xxx 的函数指针*/
    init_fct = (void(*)())(VALUE)dlsym(handle, buf);
    /* 用函数指针调用 例子Init_xxx 函数 */
    (*init_fct)();
}
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请 注册新账号