postgres中的hook机制

本文详细介绍了在瀚高数据库环境中,如何利用PostgreSQL的hooks机制进行定制化开发,包括使用hook功能在不修改内核代码的情况下实现自定义行为,以及从底层理解shared_preload_libraries配置和动态库加载过程,如pg_stat_statements插件的加载和功能安装。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

瀚高数据库
目录
环境
文档用途
详细信息

环境

系统平台: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是配置文件中用逗号分隔的动态库:

image.png

  1. 解析动态库列表,将每个动态库存放到数组中:本例只有1个pg_stat_statments,所以只有下标为0的位置有值。

image.png

  1. 下一步通过foreach宏遍历这个数组,对于其中的每一个元素分别调用load_file

image.png

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);

}
  1. 由上面可知目前filename为"pg_stat_statments",下一步要确定该动态库的绝对路径。

image.png

  1. 根据动态库的绝对路径,进行动态库的加载,处理的过程分为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)		;

image.png

上述根据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)

			;

}
  1. 如果根据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);
  1. 继6之后开始校验

dlsym根据句柄和符号名(这里是一个函数名 “Pg_magic_func”)返回该符号在动态库文件加载到内存之后所在的地址。拿到这个地址就可以调用该函数了。

/* Check the magic function to determine compatibility */

 magic_func = (PGModuleMagicFunction)dlsym(file_scanner->handle, PG_MAGIC_FUNCTION_NAME_STRING);

image.png

image.png

用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);

	}

}

image.png

这些结构体的信息在pg_config_manual.h有详细的说明。

  1. 如果说动态库文件中有_PG_init函数,跟处理magic_func一样:调用扩展的函数,install hooks
PG_init = (PG_init_t) dlsym(file_scanner->handle, "_PG_init");

if (PG_init)

    (*PG_init) ();

image.png

image.png

  1. 剩下就是将这个动态库文件元数据信息加入到链表,返回handle, 如果已经被加载过的动态库文件,直接走这里返回handle, 这部分调试过程省略。

10.根据2.1,之后会调用process_shmem_requests();之前pg_stat_statements有函数钩到这里,所以会在此处调用pg_stat_statments中的相关函数

image.png

image.png

后边就是插件计算需要的共享内存的大小,调试过程省略。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值