库其实就是一些通用代码,可以在程序中重复使用,比如一些数学函数,可以不需要自己编写,直接调用相关函数即可实现,避免重复造轮子。
在linux中,支持两种类型的库:
编译阶段将整个库复制到可执行文件。
直到运行时才将库链接到可执行程序。
linux上的动态库路径:
在启动时预先加载,对整个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;
}
用于指定在连接创建时预先需要加载的共享库,修改不需要重启生效,但只允许超级用户修改。如果这个变量指向了一个不存在的共享库,会导致无法连接数据库。
/* ----------------------------------------------------------------
* 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);
}
用户建立连接时加载,任何用户都可以设置。如果这个变量指向了一个不存在的共享库,会导致该用户无法连接数据库。
load_libraries(local_preload_libraries_string,
"local_preload_libraries",
true);
这不是变量,是个命令,载入一个共享库文件(加载单个插件),仅对当前会话生效。
LOAD 'pg_hint_plan';
该命令直接调用load_file函数(dfmgr.c文件),后面一起学习。
可以看到,前面三个参数核心都是调用load_libraries函数,下层调用栈主要为:load_libraries()->load_file()->internal_load_library(),其中load_file也是LOAD命令的实现原理。
该函数动态加载指定的共享库(即 PostgreSQL 插件)。接收一个字符串参数,即插件名(不包括扩展名 如.so或 .dll),尝试在多个目录下查找可加载的文件,并使用操作系统平台相关的动态链接库机制将其动态地装载到 PostgreSQL 进程中。这些目录包括:
加载成功后,插件代码就会被加载进入数据库的内存空间中,从而可以通过相关函数调用执行插件功能。
/*
* 加载指定名称的共享库(即插件)
* 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);
}
该函数负责将共享库动态地装入一个独立的进程中。对于每个输入的插件名,该函数首先检查是否启用了安全限制,并对此进行必要的检查。然后,将可能缩写的文件名扩展为正确的完整路径,接着在内部逻辑中处理加载/卸载操作。调用函数 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);
}
/*
* 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 WIN32
file_scanner->inode = stat_buf.st_ino;
#endif
file_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;
else
file_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