瀚高数据库
目录
环境
文档用途
详细信息
环境
系统平台:Linux x86-64 Red Hat Enterprise Linux 7
版本:14,13,12,11
文档用途
1.了解使用postgres中的hook机制,在不更改内核代码的前提下完成一些定制化需求;
2.从底层理解插件的加载过程,尤其是对于shared_preload_libraries配置文件选项。
详细信息
1.hooks的功能
PostgreSQL提供了一些钩子(hooks)机制,可以在服务器处理的特定时刻插入自定义代码,从而实现创建模块的方式,而无需修改核心代码。这些工具包括扩展(extensions)和自定义后台工作进程(custom worker backgrounds)。通过利用这些工具,PostgreSQL开发人员可以在服务器的不同环节插入自己的代码逻辑,扩展服务器的功能或实现自定义的行为,而不需要修改核心代码。这样可以使开发过程更加灵活和可扩展。
利用钩子(hooks),你可以实现许多功能,如创建自定义的查询规划器(custom planner)、输出自定义的解释计划(EXPLAIN)、运行个性化版本的实用工具,或者在执行层面控制查询处理。钩子机制允许你在特定的环节插入自定义的代码逻辑,以便在不修改核心代码的情况下对PostgreSQL进行定制化扩展。通过利用这些钩子,你可以修改查询规划过程、自定义解释计划的输出、添加个性化的实用工具或者控制查询在执行阶段的行为,从而满足特定的需求或实现定制化的功能。
2.从插件角度看hooks
/* contrib/pg_stat_statements/pg_stat_statments.c */
PG_MODULE_MAGIC;
/*
* Module load callback
*/
void
_PG_init(void)
{
/*
* In order to create our shared memory area, we have to be loaded via
* shared_preload_libraries. If not, fall out without hooking into any of
* the main system. (We don't throw error here because it seems useful to
* allow the pg_stat_statements functions to be created even when the
* module isn't active. The functions must protect themselves against
* being called then, however.)
*/
if (!process_shared_preload_libraries_in_progress)
return;
/*
* Inform the postmaster that we want to enable query_id calculation if
* compute_query_id is set to auto.
*/
EnableQueryId();
/* 此处省略了用户自定义GUC变量 */
/*
* Install hooks.
*/
prev_shmem_request_hook = shmem_request_hook;
shmem_request_hook = pgss_shmem_request;
prev_shmem_startup_hook = shmem_startup_hook;
shmem_startup_hook = pgss_shmem_startup;
prev_post_parse_analyze_hook = post_parse_analyze_hook;
post_parse_analyze_hook = pgss_post_parse_analyze;
prev_planner_hook = planner_hook;
planner_hook = pgss_planner;
prev_ExecutorStart = ExecutorStart_hook;
ExecutorStart_hook = pgss_ExecutorStart;
prev_ExecutorRun = ExecutorRun_hook;
ExecutorRun_hook = pgss_ExecutorRun;
prev_ExecutorFinish = ExecutorFinish_hook;
ExecutorFinish_hook = pgss_ExecutorFinish;
prev_ExecutorEnd = ExecutorEnd_hook;
ExecutorEnd_hook = pgss_ExecutorEnd;
prev_ProcessUtility = ProcessUtility_hook;
ProcessUtility_hook = pgss_ProcessUtility;
}
2.1 启动过程中处理需要预加载的动态库
/*
* process any libraries that should be preloaded at postmaster start
*/
process_shared_preload_libraries();
/* 省略初始化SSL库和计算Maxbackends */
/*
* Give preloaded libraries a chance to request additional shared memory.
*/
process_shmem_requests();
2.1.1 读取libxxxx.so文件内容
/*
* process any libraries that should be preloaded at postmaster start
*/
void
process_shared_preload_libraries(void)
{
// process_shared_preload_libraries_xxxxx作为标志,用于判断
process_shared_preload_libraries_in_progress = true;
load_libraries(shared_preload_libraries_string,
"shared_preload_libraries",
false);
process_shared_preload_libraries_in_progress = false;
process_shared_preload_libraries_done = true;
}
2.1.2 调试过程
1.查看调用栈,load_libraries函数的参数分别如下,libraries是配置文件中用逗号分隔的动态库:
- 解析动态库列表,将每个动态库存放到数组中:本例只有1个pg_stat_statments,所以只有下标为0的位置有值。
- 下一步通过foreach宏遍历这个数组,对于其中的每一个元素分别调用load_file
load_file函数定义:
参数restricted限定了动态库所在位置,要到 l i b / p l u g i n s 下查找该动态库,如果为 f a l s e 则在 lib/plugins下查找该动态库,如果为false则在 lib/plugins下查找该动态库,如果为false则在lib下查找,在当前环境下restricted为false。
/*
* 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.
*/
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);
/* Load the shared library */
(void) internal_load_library(fullname);
pfree(fullname);
}
- 由上面可知目前filename为"pg_stat_statments",下一步要确定该动态库的绝对路径。
- 根据动态库的绝对路径,进行动态库的加载,处理的过程分为2步:PG_MODULE_MAGIC这个函数将返回动态库的元数据信息,进行校验;再者就是执行_PG_init,保存老的钩子函数,安装该插件的钩子函数,此时就钩到内核中了。
下面代码来源:/src/backend/utils/fmgr/dfmgr.c:internal_load_library
遍历file_list链表,根据libname确定该动态库是否已经被加载过。
/*
* Scan the list of loaded FILES to see if the file has been loaded.
*/
for (file_scanner = file_list;file_scanner != NULL &&strcmp(libname, file_scanner->filename) != 0;file_scanner = file_scanner->next) ;
上述根据libname可能不准确,获取文件元数据信息,根据设备号和inode号再次确认
if (file_scanner == NULL)
{
/*
* Check for same files - different paths (ie, symlink or link)
*/
if (stat(libname, &stat_buf) == -1)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not access file \"%s\": %m",
libname)));
for (file_scanner = file_list;
file_scanner != NULL &&
!SAME_INODE(stat_buf, *file_scanner);
file_scanner = file_scanner->next)
;
}
- 如果根据5的检查没有加载过该动态库,则打开该动态库文件。
file_scanner->handle是指向动态库文件的句柄,就像用FILE *接收fopen()的返回值一样。
RTLD_NOW:是对符号表中undefined的符号进行解析,这些符号比如变量或者函数不在本文件中声明。解析要发生在dlopen返回之前,因为我们马上要调用该动态库文件的函数,否则可能会报错:symbol unresolved…;
RTLD_GLOBAL:该文件中的符号对之后加载的动态库可见。
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);
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);
- 继6之后开始校验
dlsym根据句柄和符号名(这里是一个函数名 “Pg_magic_func”)返回该符号在动态库文件加载到内存之后所在的地址。拿到这个地址就可以调用该函数了。
/* Check the magic function to determine compatibility */
magic_func = (PGModuleMagicFunction)dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);
用readelf查看该动态库文件可以看出地址并不一样,因为动态库编译时一般都采用-fPIC(postion-independent code)选项,生成位置无关代码。
在明确获得magic_func地址之后,调用该函数,该函数返回一个magic_data_ptr类型的指针,指向一个用于校验的结构体:
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(file_scanner);
/* issue suitable complaint */
incompatible_module_error(libname, &module_magic_data);
}
}
这些结构体的信息在pg_config_manual.h有详细的说明。
- 如果说动态库文件中有_PG_init函数,跟处理magic_func一样:调用扩展的函数,install hooks
PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");
if (PG_init)
(*PG_init) ();
- 剩下就是将这个动态库文件元数据信息加入到链表,返回handle, 如果已经被加载过的动态库文件,直接走这里返回handle, 这部分调试过程省略。
10.根据2.1,之后会调用process_shmem_requests();之前pg_stat_statements有函数钩到这里,所以会在此处调用pg_stat_statments中的相关函数
后边就是插件计算需要的共享内存的大小,调试过程省略。