一、 基础知识

1. 什么是库

库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子。

在linux中,支持两种类型的库:

1. 静态库

编译阶段将整个库复制到可执行文件。

  • 优点:不依赖外界库支持,具有良好的可移植性。
  • 缺点: 每次库更新都需要重新编译程序,即使更新很小或只是局部。每个静态库都有一份库文件,存储时增加了硬盘空间消耗,运行时则增加了内存消耗。
  • 命名规则:linux中为xxx.a,Windows中为xxx.lib

2. 动态(共享)库

直到运行时才将库链接到可执行程序。

  • 优点: 动态链接方式的程序不需要包含库,编译时节省时间,占用的空间小很多。运行时系统内存只需提供一个共享库给所有程序动态链接,内存消耗减少。
  • 缺点: 需要系统中动态库支持才可运行,可能有动态库不兼容问题
  • 命名规则:linux中为xxx.so,Windows中为xxx.dll

linux上的动态库路径:

  • 默认在 /usr/local/lib, /usr/local/lib64, /usr/lib, /usr/lib64
  • 系统启动的库文件在 /lib, /lib64。
  • pg共享库的路径在 <安装目录>/lib下,可以设置LD_LIBRARY_PATH环境变量,指定相关库的路径。

二、 pg中四种动态库加载方法

1. shared_preload_libraries变量

在启动时预先加载,对整个pg实例均生效,修改需要重启。如果这个变量指向了一个不存在的共享库,会导致无法启动数据库。

/** Postmaster main entry point*/
void
PostmasterMain(int argc, char *argv[])
{/** process any libraries that should be preloaded at postmaster start*/process_shared_preload_libraries();
…
}

所以为什么这个参数修改需要重启生效,因为只在DB启动时调一次。

/** process any libraries that should be preloaded at postmaster start*/
void
process_shared_preload_libraries(void)
{process_shared_preload_libraries_in_progress = true;load_libraries(shared_preload_libraries_string,"shared_preload_libraries",false);process_shared_preload_libraries_in_progress = false;
}

2. session_preload_libraries变量

用于指定在连接创建时预先需要加载的共享库,修改不需要重启生效,但只允许超级用户修改。如果这个变量指向了一个不存在的共享库,会导致无法连接数据库。

/* ----------------------------------------------------------------* PostgresMain*     postgres main loop -- all backends, interactive or otherwise start here* ----------------------------------------------------------------*/
void
PostgresMain(int argc, char *argv[],const char *dbname,const char *username)
{
…/** process any libraries that should be preloaded at backend start (this* likewise can't be done until GUC settings are complete)*/process_session_preload_libraries();
…
}

可以看到进行了两个参数配置,session_preload_libraries和local_preload_libraries。

/** process any libraries that should be preloaded at backend start*/
void
process_session_preload_libraries(void)
{load_libraries(session_preload_libraries_string,"session_preload_libraries",false);load_libraries(local_preload_libraries_string,"local_preload_libraries",true);
}

3. local_preload_libraries变量

用户建立连接时加载,任何用户都可以设置。如果这个变量指向了一个不存在的共享库,会导致该用户无法连接数据库。

    load_libraries(local_preload_libraries_string,"local_preload_libraries",true);

4. LOAD方式

这不是变量,是个命令,载入一个共享库文件(加载单个插件),仅对当前会话生效。

LOAD 'pg_hint_plan';

该命令直接调用load_file函数(dfmgr.c文件),后面一起学习。

三、 源码学习

可以看到,前面三个参数核心都是调用load_libraries函数,下层调用栈主要为:load_libraries()->load_file()->internal_load_library(),其中load_file也是LOAD命令的实现原理。

1. load_libraries函数

该函数动态加载指定的共享库(即 PostgreSQL 插件)。接收一个字符串参数,即插件名(不包括扩展名 如.so或 .dll),尝试在多个目录下查找可加载的文件,并使用操作系统平台相关的动态链接库机制将其动态地装载到 PostgreSQL 进程中。这些目录包括:

  • PG LD_LIBRARY_PATH环境变量指定的路径
  • PG 软件所在的 lib 目录
  • 与 PG 数据库集群关联的插件目录

加载成功后,插件代码就会被加载进入数据库的内存空间中,从而可以通过相关函数调用执行插件功能。

/** 加载指定名称的共享库(即插件)
* libraries:待加载插件名字列表,以逗号分隔* gucname:GUC设置项名字,用于错误消息* restricted:是否只加载 $libdir/plugins 路径下的插件
*/
static void
load_libraries(const char *libraries, const char *gucname, bool restricted)
{char       *rawstring;  // 用于保存输入参数 libraries 的可写副本List       *elemlist;   // 用于保存 libraries 参数解析后的插件名字列表ListCell   *l;if (libraries == NULL || libraries[0] == '\0')return;                 /* nothing to do,没有需要加载的插件 *//* Need a modifiable copy of string */rawstring = pstrdup(libraries);/* Parse string into list of filename paths,将字符串解析为路径名字的列表 */if (!SplitDirectoriesString(rawstring, ',', &elemlist)){/* syntax error in list,列表中存在语法错误 */list_free_deep(elemlist);pfree(rawstring);ereport(LOG,(errcode(ERRCODE_SYNTAX_ERROR),errmsg("invalid list syntax in parameter \"%s\"",gucname)));return;}foreach(l, elemlist){/* Note that filename was already canonicalized,文件名已被规范化 */char       *filename = (char *) lfirst(l);char       *expanded = NULL;/* If restricting, insert $libdir/plugins if not mentioned already */if (restricted && first_dir_separator(filename) == NULL){expanded = psprintf("$libdir/plugins/%s", filename);filename = expanded;}/* 核心函数,加载插件文件 */load_file(filename, restricted);ereport(DEBUG1,(errmsg_internal("loaded library \"%s\"", filename)));if (expanded)pfree(expanded);}list_free_deep(elemlist);pfree(rawstring);
}

2. load_file函数

该函数负责将共享库动态地装入一个独立的进程中。对于每个输入的插件名,该函数首先检查是否启用了安全限制,并对此进行必要的检查。然后,将可能缩写的文件名扩展为正确的完整路径,接着在内部逻辑中处理加载/卸载操作。调用函数 internal_load_library 加载插件,并确保内存映射中没有冲突,最后回收内存资源。

/** This function loads a shlib file without looking up any particular* function in it.  If the same shlib has previously been loaded,* unload and reload it. 加载一个动态库文件,不去查找其中特定的函数。如果已经加载同名同地址的动态库,则重新加载。** When 'restricted' is true, only libraries in the presumed-secure* directory $libdir/plugins may be referenced. 是否限制仅能引用预设安全目录 $libdir/plugins 下的动态库。*/
void
load_file(const char *filename, bool restricted)
{char       *fullname;/* Apply security restriction if requested,如果需要,应用安全限制 */if (restricted)check_restricted_library_name(filename);/* Expand the possibly-abbreviated filename to an exact path name,将可能缩写的文件名扩展为正确的完整路径 */fullname = expand_dynamic_library_name(filename);/* Unload the library if currently loaded,如果当前已经加载了该库,则卸载之 */internal_unload_library(fullname);/* Load the shared library,核心函数,加载共享库 */(void) internal_load_library(fullname);pfree(fullname);
}

3. internal_load_library函数

  • 检查要安装的扩展,其文件是否可以访问,不能访问则报错退出。检查是否为已安装扩展的软链接或者硬链接,如果是,则直接返回该已安装扩展的 handle。
  • 调用 dlopen 加载动态库文件,dlopen 返回的就是一个动态库的 handle。
  • 调用 dlsym 获取动态库里的函数 Pg_magic_func,执行该函数,获取 Pg_magic_struct 结构体数据,并与标准的 magic_data 进行对比,进行动态库兼容性测试。
  • 调用 dlsym 获取动态库里的函数 _PG_init,如果有该函数,则执行该函数,进行动态库的初始化。
  • 将动态库 handle 对应的结构体加入到全局 file_list 链表中。
/** Load the specified dynamic-link library file, unless it already is* loaded.  Return the pg_dl* handle for the file. 加载指定的动态链接库文件,除非它已经被加载。返回该文件的 pg_dl* 句柄。** Note: libname is expected to be an exact name for the library file. 注意:libname 应该是一个准确的库文件名。*/
static void *
internal_load_library(const char *libname)
{DynamicFileList *file_scanner;PGModuleMagicFunction magic_func;char       *load_error;struct stat stat_buf;PG_init_t   PG_init;/** 扫描文件列表,查找指定的文件是否已经加载,如果是,直接返回该扩展的 handle。如果未加载,进行下一步。首先定义了一个指针变量 file_scanner,并将其初始化为 file_list,即已加载文件列表的头指针。
执行循环条件判断,当满足以下两个条件之一时,跳出循环:
   file_scanner 为 NULL:已经扫描完所有已加载的文件;
   libname 和当前扫描到的文件名相同:已找到指定的文件。
在循环体中没有任何操作,只是简单的空语句。
每次循环结束时,将 file_scanner 指向下一个已加载文件的节点(链表),继续执行第二步的条件判断。*/for (file_scanner = file_list;file_scanner != NULL &&strcmp(libname, file_scanner->filename) != 0;file_scanner = file_scanner->next);//在扫描已加载的文件列表时,如果没有找到指定的文件(file_scanner 为NULL),则进行一个额外的检查以确保不会加载同名但路径不同的重复文件。if (file_scanner == NULL){/** 对目标文件进行 stat 操作,获取文件的详细信息(如 inode、文件大小、修改时间等)存储到 stat_buf 结构体中*/if (stat(libname, &stat_buf) == -1)ereport(ERROR,(errcode_for_file_access(),errmsg("could not access file \"%s\": %m",libname)));
//执行一个新的循环,扫描已加载文件列表,查找是否存在「同名文件但路径不同」的情况,也就是说目标文件可能是加载了一个链接或符号链接。循环过程中依次比较当前节点对应的文件是否与目标文件有相同的 inode 值。
// 如果找到了相同 inode 的节点,说明已经加载了同名但地址不同的文件,直接跳出循环返回该节点,否则说明不存在相同 inode 的节点,可以安全地将目标文件加载到进程中。for (file_scanner = file_list;file_scanner != NULL &&!SAME_INODE(stat_buf, *file_scanner);file_scanner = file_scanner->next);}if (file_scanner == NULL){/** File not loaded yet. file_scanner 为空指针,则表示此前还没有加载过该库文件*///申请一定的内存空间并将目标库文件路径复制到该空间中。file_scanner = (DynamicFileList *)malloc(offsetof(DynamicFileList, filename) + strlen(libname) + 1);if (file_scanner == NULL)ereport(ERROR,(errcode(ERRCODE_OUT_OF_MEMORY),errmsg("out of memory")));MemSet(file_scanner, 0, offsetof(DynamicFileList, filename));strcpy(file_scanner->filename, libname);
//记录目标库文件的 inode 值,并使用 dlopen 函数打开目标库文件。file_scanner->device = stat_buf.st_dev;
#ifndef WIN32file_scanner->inode = stat_buf.st_ino;
#endiffile_scanner->next = NULL;file_scanner->handle = dlopen(file_scanner->filename, RTLD_NOW | RTLD_GLOBAL);if (file_scanner->handle == NULL){load_error = dlerror();free((char *) file_scanner);/* errcode_for_file_access might not be appropriate here? */ereport(ERROR,(errcode_for_file_access(),errmsg("could not load library \"%s\": %s",libname, load_error)));}/* Check the magic function to determine compatibility, 检查共享库文件是否符合PG_MODULE_MAGIC 宏定义的格式。 */magic_func = (PGModuleMagicFunction)dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);if (magic_func){const Pg_magic_struct *magic_data_ptr = (*magic_func) ();if (magic_data_ptr->len != magic_data.len ||memcmp(magic_data_ptr, &magic_data, magic_data.len) != 0){/* copy data block before unlinking library */Pg_magic_struct module_magic_data = *magic_data_ptr;/* try to close library */dlclose(file_scanner->handle);free((char *) file_scanner);/* issue suitable complaint */incompatible_module_error(libname, &module_magic_data);}}else{/* try to close library */dlclose(file_scanner->handle);free((char *) file_scanner);/* complain */ereport(ERROR,(errmsg("incompatible library \"%s\": missing magic block",libname),errhint("Extension libraries are required to use the PG_MODULE_MAGIC macro.")));}/** If the library has a _PG_init() function, call it. 若符合要求,则执行 PG/PL SQL 扩展组件必须要实现的入口函数 _PG_init。*/PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");if (PG_init)(*PG_init) ();/* OK to link it into list,连接该库文件到双向链表中 */if (file_list == NULL)file_list = file_scanner;elsefile_tail->next = file_scanner;file_tail = file_scanner;}// 返回该库文件对应的指针,即完成这个函数中最重要的任务——成功加载并且初始化库文件return file_scanner->handle;
}

参考

https://blog.csdn.net/pg_hgdb/article/details/100726356

https://blog.51cto.com/u_15078930/5671540

https://blog.csdn.net/dazuiba008/article/details/117436921

https://zhuanlan.zhihu.com/p/375644210

https://www.wendaok.cn/post/16702.html

https://dude6.com/article/249546.html

postgresql源码学习(57)—— pg中的四种动态库加载方法相关推荐

  1. PostgreSQL源码学习(一)编译安装与GDB入门

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 PostgreSQL源码学习(一)编译安装与GDB入门 前言 一.安装PostgreSQL 1.获取源码 2.配置 3.编译 3.安装 ...

  2. postgresql源码学习(27)—— 事务日志⑦-日志落盘上层函数 XLogFlush

    一. 预备知识 1. XLOG什么时候需要落盘 事务commit之前 log buffer被覆盖之前 后台进程定期落盘 2. 两个核心结构体 这两个结构体定义代码在xlog.c,它们在日志落盘过程中非 ...

  3. postgresql源码学习(51)—— 提交日志CLOG 原理 用途 管理函数

    一. CLOG是什么 CLOG(commit log)记录事务的最终状态. 物理上,是$PGDATA/pg_xact目录下的一些文件 逻辑上,是一个数组,下标为事务id,值为事务最终状态 1. 事务最 ...

  4. postgresql源码学习(49)—— MVCC⑤-cmin与cmax 同事务内的可见性判断

    一. 难以理解的场景 postgresql源码学习(十九)-- MVCC④-可见性判断 HeapTupleSatisfiesMVCC函数_Hehuyi_In的博客-CSDN博客 在前篇的可见性判断中有 ...

  5. PostgreSQL源码学习(1)--PG13代码结构

    PostgreSQL源码学习(1)–PG13代码结构 PostgreSQL代码结构 Bootstrap:用于支持Bootstrap运行模式,该模式主要用来创建初始的模板数据库. Main:主程序模块, ...

  6. Linux动态库加载函数dlopen源码梳理(一)

    下载了libc的源码,现在就开始libc源码的学习,最近了解到了linux动态库的相关知识,那么就从linux动态库加载函数dlopen进行梳理学习吧. 如果还没下载libc源码,可通过 https: ...

  7. postgresql源码学习(53)—— vacuum②-lazy vacuum之heap_vacuum_rel函数

    一. table_relation_vacuum函数 1. 函数定义 前篇最后(https://blog.csdn.net/Hehuyi_In/article/details/128749517),我 ...

  8. postgresql源码学习(52)—— vacuum①-准备工作与主要流程

    关于vacuum的基础知识,参考,本篇从源码层继续学习 https://blog.csdn.net/Hehuyi_In/article/details/102992065 https://blog.c ...

  9. postgresql源码学习(一)—— 源码编译安装与gdb调试入门

    一. postgresql源码编译安装 因为只是用来调试的测试环境,把基本的软件装好和库建好就可以,一切从简. 1. 创建用户和目录 mkdir -p /data/postgres/base/ mkd ...

最新文章

  1. Python培训中有哪些是必须学的运算符
  2. linux diff(differential) 命令
  3. javascript高精度计算解决方案
  4. Linux Centos6.5在哪里输入命令
  5. java 字段为空设置默认值_java – 当字段为空时使用MyBatis添加默认值
  6. 工作285:判断绑定逻辑
  7. “买苹果辞退,买华为补贴”,这家公司支持国产手机的理由给我惊到了!
  8. mysql ibdata1 损坏_mysql innodb文件ibdata1损坏导致mysql无法启动
  9. 智能一代云平台(三十六):项目中如何做到避免传递依赖
  10. 学习SQL 的网址集合
  11. Co-Fusion: Real-time Segmentation, Tracking and Fusion of Multiple Objects
  12. 呵呵呵,一周这么来也不错
  13. java8—Stream
  14. 根据判断浏览器类型屏幕分辨率自动调用不同CSS的代码
  15. windows游戏编程大师技巧光盘
  16. D-S envidence theory(DS 证据理论)的基本概念和推理过程
  17. HDU 5835 Danganronpa(弹丸论破)
  18. 微信 群相册服务器,微信也有群相册!用这个小程序,轻松优雅收集聚会合影...
  19. 二叉排序树基本操作(链表实现)(有错误)
  20. Java导出excel中response.setHeader()参数设置

热门文章

  1. ios11,新系统,新bug
  2. python中randint_python中randint函数的用法是什么?_后端开发
  3. 一文详解DCMM(数据管理能力成熟度评估模型)贯标评估全流程
  4. 我買了10個美白和黃色面具
  5. 用Python爬虫+Crontab实现自动更换电脑壁纸
  6. Java标识符命名规范
  7. 如何创建一组精美的冬季圣诞图标
  8. unity小游戏——得分高低的判定
  9. 机器学习笔记8——ERM
  10. [荐] [顶] 【爱情箴言】看后会幸福一生的漫画~~!!(欢迎转载)