栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

postgresql 15源码浅析(3)—— 信号“1“的使命

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

postgresql 15源码浅析(3)—— 信号“1“的使命

摘要

这篇实际上是要说说配置文件的加载问题,有些标题党了。准确说是如何在线加载配置,用户通过向postgres进程发送信号SIGHUP触发服务端完成配置的刷新。这部分看下来代码还是有点多,挑选几段我认为比较重要的片段和大家一起学习一下。

信号

信号也是进程间通信的一种手段,之前在《postgresql 15源码浅析(1)—— postgres中的1号数据库》介绍了通过管道完成向psql客户端进程传递BKI命令的进程间通信方式,同样,信号是另一种进程间传递信息的方式,相对传统的共享内存和消息队列方式,信号要简单的多。

信号简介

信号机制是一种使用信号来进行进程之间的传递消息的方法,其中信号的全称为软中断信号,简称软中断。

我们最常用的一个就是 9) SIGKILL ,kill -9 ,我想这个大家应该都用过。实际上,Linux内核支持64种不同的信号,这些信号种的大部分都有了预先定义好的意义,但是都支持自定义动作,并且还提供了类似SIGUSR1这样由应用程序来定义的信号。

同样在C./C++中也可以使用类似方法,使用kill函数,传入进程号pid和信号值sig,即可以向指定进程发送特定的信号。

信号的处理

我们处理信号的方式大体有3种:

  1. 捕获信号,并忽略它,常用于在某些处理过程中不希望被终端打断的场景,如某些初始化过程。
  2. 不做任何捕获操作,使用信号的默认行为,信号的默认行为大部分是要终止进程。
  3. 捕获信号,并重新赋予被捕获信号新的使命。这里点题了。“信号1的使命”。

信号实际上在核心场景下并不是一个好的选择,它涉及中断,同时需要内核态和用户态的切换,效率不高,开销太大,且与其它进程通信方式相比,能够传递的信息有限。

再推荐一本书吧,有兴趣转行的同学可以看看《Linux C程序设计大全》,是一本贼厚的蓝皮书,也是我入行时的第一本工具书。

修改PostgreSQL的配置 配置文件在哪?

在初始化之后,数据库集簇目录下回创建4个配置文件。

也可以通过命令查看配置文件所在位置

配置文件生效的方法

修改配置文件后,配置项生效的方法共有四种:

  1. 重新启动数据库服务,这是网吧管理员的做法,除非是一些参数要求重启服务,否则不需要这么干;
  2. 使用超级用户执行 select pg_reload_conf(); 留个坑,后续补充这个函数的执行流程;
  3. bash下执行 kill -HUP 或 kill -1 ,这个就是今天要撸的代码;
  4. 使用pg_ctl工具执行 pg_ctl reload 这个命令实际上就是触发了SIGHUP信号;
GucContext

上文提到,不是所有的配置都可以动态的加载生效的,有些配置需要重新启动数据库服务。

通过select distinct(context) from pg_settings;命令列出所有配置的类型(共7种)。

postgres@/tmp:postgres> select distinct(context) from pg_settings;
+-------------------+
| context           |
|-------------------|
| postmaster        |
| superuser-backend |
| user              |
| internal          |
| backend           |
| sighup            |
| superuser         |
+-------------------+
SELECt 7
Time: 0.005s

对应的正是代码中GucContext的枚举类型(共7种)。


typedef enum
{
	PGC_INTERNAL,
	PGC_POSTMASTER,
	PGC_SIGHUP,
	PGC_SU_BACKEND,
	PGC_BACKEND,
	PGC_SUSET,
	PGC_USERSET
} GucContext;

注释中对每个选项的表述都比较良心,等级是从严到松的顺序。

如果配置项的context小于PGC_SIGHUP,那么需要重新启动生效,当然PGC_INTERNAL是只读的,当然PGC_INTERNAL的配置也不会出现在postgresql.conf文件中,所以只要context属性是PGC_POSTMASTER的配置项需要重启后会生效。

看一下这些配置在PostgreSQL 15中的分布情况:

postgres@/tmp:postgres> select context,count(*) from pg_settings group by context;
+-------------------+-------+
| context           | count |
|-------------------+-------|
| postmaster        | 55    |
| superuser-backend | 4     |
| user              | 136   |
| internal          | 20    |
| backend           | 2     |
| sighup            | 92    |
| superuser         | 44    |
+-------------------+-------+
SELECT 7
Time: 0.006s
postgres@/tmp:postgres>

可以通过查询pg_settings表的context字段来确定,修改的配置是否允许动态加载。

信号处理流程

整理流程如上图,1.进程启动时,注册信号捕获处理函数;2.用户发送信号,并被进程捕获,交由SIGHUP_handler处理;3.主进程先处理配置文件重加载流程,并把信号传递个各个子进程;4.分别对hba和ident两个配置文件进程加载处理;

注册信号处理函数
void
PostmasterMain(int argc, char *argv[])

在父进程(主进程)postmaster的入口函数PostmasterMain中注册信号处理函数。

	pqsignal_pm(SIGHUP, SIGHUP_handler);	
	pqsignal_pm(SIGINT, pmdie); 
	pqsignal_pm(SIGQUIT, pmdie);	
	pqsignal_pm(SIGTERM, pmdie);	
	pqsignal_pm(SIGALRM, SIG_IGN);	
	pqsignal_pm(SIGPIPE, SIG_IGN);	
	pqsignal_pm(SIGUSR1, sigusr1_handler);	
	pqsignal_pm(SIGUSR2, dummy_handler);	
	pqsignal_pm(SIGCHLD, reaper);	

其中pqsignal_pm(SIGHUP, SIGHUP_handler);注册了对新SIGHUP的处理函数SIGHUP_handler,

SIGHUP_handler

核心代码:

		ereport(LOG,(errmsg("received SIGHUP, reloading configuration files")));
		ProcessConfigFile(PGC_SIGHUP);
		SignalChildren(SIGHUP);
		if (StartupPID != 0)
			signal_child(StartupPID, SIGHUP);
		if (BgWriterPID != 0)
			signal_child(BgWriterPID, SIGHUP);
		if (CheckpointerPID != 0)
			signal_child(CheckpointerPID, SIGHUP);
		if (WalWriterPID != 0)
			signal_child(WalWriterPID, SIGHUP);
		if (WalReceiverPID != 0)
			signal_child(WalReceiverPID, SIGHUP);
		if (AutoVacPID != 0)
			signal_child(AutoVacPID, SIGHUP);
		if (PgArchPID != 0)
			signal_child(PgArchPID, SIGHUP);
		if (SysLoggerPID != 0)
			signal_child(SysLoggerPID, SIGHUP);

		
		if (!load_hba())
			ereport(LOG,
			
			(errmsg("%s was not reloaded", "pg_hba.conf")));

		if (!load_ident())
			ereport(LOG,
			(errmsg("%s was not reloaded", "pg_ident.conf")));

  1. 处理配置文件加载ProcessConfigFile。
  2. 将捕获信号传递给子进程signal_child,子进程kill(-pid, signal);
  3. 加载hba配置文件load_hba();
  4. 加载ident配置文件load_ident();
子进程处理

子进程收到父进程传递过来的信号好,进入处理流程。

以WalWriter进程为例,入口函数WalWriterMain在backend/postmaster/walwriter.c中。主要处理过程为与postmaster主进程类似,最终也将调用guc_file.c中的ProcessConfigFile函数;

子进程的处理需要注意的是,收到HUP信号后,并不是立即进入处理流程,而是设置重载标志,在下一次loop forever中检查标志,如果为true则调用配置文件重新加载的流程。

  1. 设置标记ConfigReloadPending
void
SignalHandlerForConfigReload(SIGNAL_ARGS)
{
	int			save_errno = errno;

	ConfigReloadPending = true;
	SetLatch(MyLatch);

	errno = save_errno;
}
  1. 读取标记ConfigReloadPending,是否需要重新加载配置文件
static void
HandleWalWriterInterrupts(void)
{
	if (ProcSignalBarrierPending)
		ProcessProcSignalBarrier();

	if (ConfigReloadPending)
	{
		ConfigReloadPending = false;
		ProcessConfigFile(PGC_SIGHUP);
	}

	if (ShutdownRequestPending)
	{
		
		pgstat_report_wal(true);

		proc_exit(0);
	}

	
	if (LogMemoryContextPending)
		ProcessLogMemoryContextInterrupt();
}
ProcessConfigFile

改函数时处理配置文件重新加载的核心部分,整体流程如下:

这部分代码量比较大,宏观的看一下重点,这部分处理使用了递归处理,主要是处理postgressql.conf中include子配置文件是需要递归。

                if (guc_name_compare(opt_name, "include_dir") == 0)
		{
			
			if (!ParseConfigDirectory(opt_value,
									  config_file, ConfigFileLineno - 1,
									  depth + 1, elevel,
									  head_p, tail_p))
                        ……
		}
		else if (guc_name_compare(opt_name, "include_if_exists") == 0)
		{
			
			if (!ParseConfigFile(opt_value, false,
								 config_file, ConfigFileLineno - 1,
								 depth + 1, elevel,
								 head_p, tail_p))
                        ……
		}
		else if (guc_name_compare(opt_name, "include") == 0)
		{
			
			if (!ParseConfigFile(opt_value, true,
								 config_file, ConfigFileLineno - 1,
								 depth + 1, elevel,
								 head_p, tail_p))
                        ……
		}
		else
		{
                    ……
		}

且递归深度不能超过10级。

	
	if (depth > 10)
	{
		ereport(elevel,
				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
				errmsg("could not open configuration file "%s": maximum nesting depth exceeded",
				config_file)));
		record_config_file_error("nesting depth exceeded",
					  calling_file, calling_lineno,
					  head_p, tail_p);
		return false;
	}

所有的配置保存在静态变量guc_variables中:

static struct config_generic **guc_variables;

config_generic结构如下:

struct config_generic
{
	
	const char *name;			
	GucContext	context;		
	enum config_group group;	
	const char *short_desc;		
	const char *long_desc;		
	int			flags;			
	
	enum config_type vartype;	
	int			status;			
	GucSource	source;			
	GucSource	reset_source;	
	GucContext	scontext;		
	GucContext	reset_scontext; 
	GucStack   *stack;			
	void	   *extra;			
	char	   *last_reported;	
	char	   *sourcefile;		
	int			sourceline;		
};
注意

几种不能设置参数的情况,未一一列举:

ereport(elevel,(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
                errmsg("cannot set parameters during a parallel operation")));
case PGC_INTERNAL:
if (context != PGC_INTERNAL)
{
    ereport(elevel,
            (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
                errmsg("parameter "%s" cannot be changed",
                    name)));
    return 0;
}
break;
else if (context != PGC_POSTMASTER)
{
    ereport(elevel,
            (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
                errmsg("parameter "%s" cannot be changed without restarting the server",
                    name)));
    return 0;
}
case PGC_SIGHUP:
    if (context != PGC_SIGHUP && context != PGC_POSTMASTER)
    {
        ereport(elevel,
                (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
                    errmsg("parameter "%s" cannot be changed now",
                        name)));
        return 0;
    }

    
    break;
case PGC_SU_BACKEND:
    if (context == PGC_BACKEND)
    {
        
        AclResult	aclresult;

        aclresult = pg_parameter_aclcheck(name, GetUserId(), ACL_SET);
        if (aclresult != ACLCHECK_OK)
        {
            
            ereport(elevel,
                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                        errmsg("permission denied to set parameter "%s"",
                            name)));
            return 0;
        }
    }
结构

其中涉及一些内存结构:

整体调用逻辑:

总结

这篇的内容比较多,有其是解析流程和参数设置的过程,这部分代码经过10几年的打磨,应该是很成熟了,但是面向过程的涉及显得代码有点冗长,后续看看openGauss对这部分的处理是否有改进。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/941015.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号