解读PolarDB PostgreSQL postmaster入口代码
PolarDB PostgreSQL postmaster入口
1 解读PostmasterMain函数
代码流程总结
初始化:全局变量、信号、内存、配置。 参数解析:读取命令行参数,设置运行时选项。 环境检查:验证数据目录和关键文件。 共享内存:为后续进程通信分配资源。 启动子进程:检查点、后台写等关键服务。 主循环:监听客户端连接,进入服务状态。 退出清理:关闭所有资源,结束进程。
关键概念解释
信号(Signal):操作系统通知进程事件(如终止、中断)的机制。 共享内存:允许多个进程访问同一块内存,用于高效通信。 后台进程:PostgreSQL依赖多个专用进程完成持久化、清理等任务。
下面逐行解读 PostmasterMain() 函数。这个函数是 postmaster 进程的主要入口点,负责初始化和启动数据库服务器。
/*
* Postmaster main entry point
*/
void
PostmasterMain(int argc, char *argv[])
{
这是一个函数定义,名为 PostmasterMain,返回类型为void(不返回任何值)。int argc, char *argv[]: 函数的参数,与main()函数的参数相同,用于接收命令行参数。argc是参数的个数,argv是一个字符串数组,包含了每个参数的内容。
int opt;
int status;
char *userDoption = NULL;
bool listen_addr_saved = false;
int i;
char *output_config_variable = NULL;
声明了一些局部变量: opt: 用于存储getopt()函数返回的选项字符。status: 用于存储函数调用的状态码。userDoption: 用于存储用户指定的-D选项的值,即数据目录。listen_addr_saved: 一个布尔变量,用于标记是否已保存监听地址到锁文件。i: 一个循环计数器。output_config_variable: 用于存储用户指定的-C选项的值,即要输出的配置变量名。
InitProcessGlobals();
调用 InitProcessGlobals()函数,初始化进程相关的全局变量。
PostmasterPid = MyProcPid;
将当前进程的 PID 赋值给 PostmasterPid全局变量,用于记录postmaster进程的 PID。
IsPostmasterEnvironment = true;
设置 IsPostmasterEnvironment全局变量为true,表示当前进程是postmaster进程。
#ifdef WIN32
pgwin32_signal_initialize();
#endif
条件编译指令。如果定义了 WIN32宏,则调用pgwin32_signal_initialize()函数,初始化 Windows 平台的信号处理机制。
umask(PG_MODE_MASK_OWNER);
调用 umask()函数,设置文件创建掩码。PG_MODE_MASK_OWNER通常定义为077,表示新创建的文件或目录的权限为700(只有所有者可读写执行)。 这样做是为了确保在检查数据目录之前,任何意外创建的文件都具有最严格的权限。
PostmasterContext = AllocSetContextCreate(TopMemoryContext,
"Postmaster",
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(PostmasterContext);
创建 PostmasterContext内存上下文,并将其设置为当前内存上下文。 内存上下文是 PostgreSQL 中用于管理内存分配的一种机制。PostmasterContext用于存储postmaster进程使用的内存,这些内存可以被后端进程回收。
getInstallationPaths(argv[0]);
调用 getInstallationPaths()函数,根据argv[0](即程序的可执行文件路径) 获取 PostgreSQL 的安装路径。
pqinitmask();
PG_SETMASK(&BlockSig);
pqinitmask(): 初始化信号掩码,用于阻塞某些信号。PG_SETMASK(&BlockSig): 设置信号掩码,阻塞BlockSig中定义的信号。
pqsignal_pm(SIGHUP, SIGHUP_handler); /* reread config file and have
* children do same */
pqsignal_pm(SIGINT, pmdie); /* send SIGTERM and shut down */
pqsignal_pm(SIGQUIT, pmdie); /* send SIGQUIT and die */
pqsignal_pm(SIGTERM, pmdie); /* wait for children and shut down */
pqsignal_pm(SIGALRM, SIG_IGN); /* ignored */
pqsignal_pm(SIGPIPE, SIG_IGN); /* ignored */
pqsignal_pm(SIGUSR1, sigusr1_handler); /* message from child process */
pqsignal_pm(SIGUSR2, dummy_handler); /* unused, reserve for children */
pqsignal_pm(SIGCHLD, reaper); /* handle child termination */
使用 pqsignal_pm()函数设置信号处理程序。pqsignal_pm()是 PostgreSQL 提供的用于设置信号处理程序的函数,它与标准的signal()函数类似,但提供了一些额外的功能,例如自动阻塞信号。SIGHUP: 当接收到SIGHUP信号时,调用SIGHUP_handler()函数,重新读取配置文件,并通知子进程也这样做。SIGINT: 当接收到SIGINT信号 (通常是 Ctrl+C) 时,调用pmdie()函数,发送SIGTERM信号给所有子进程,并关闭服务器。SIGQUIT: 当接收到SIGQUIT信号时,调用pmdie()函数,发送SIGQUIT信号给所有子进程,并立即关闭服务器。SIGTERM: 当接收到SIGTERM信号时,调用pmdie()函数,发送SIGTERM信号给所有子进程,并等待它们关闭后关闭服务器。SIGALRM: 忽略SIGALRM信号。SIGPIPE: 忽略SIGPIPE信号。SIGUSR1: 当接收到SIGUSR1信号时,调用sigusr1_handler()函数,处理来自子进程的消息。SIGUSR2: 调用dummy_handler()函数,这是一个空函数,用于保留SIGUSR2信号给子进程使用。SIGCHLD: 当子进程终止时,调用reaper()函数,处理子进程的退出状态。
#ifdef SIGURG
pqsignal_pm(SIGURG, SIG_IGN); /* ignored */
#endif
#ifdef SIGTTIN
pqsignal_pm(SIGTTIN, SIG_IGN); /* ignored */
#endif
#ifdef SIGTTOU
pqsignal_pm(SIGTTOU, SIG_IGN); /* ignored */
#endif
#ifdef SIGXFSZ
pqsignal_pm(SIGXFSZ, SIG_IGN); /* ignored */
#endif
条件编译指令。如果定义了相应的宏,则忽略 SIGURG、SIGTTIN、SIGTTOU和SIGXFSZ信号。
InitializeGUCOptions();
调用 InitializeGUCOptions()函数,初始化 GUC (Grand Unified Configuration) 选项。GUC 选项是 PostgreSQL 中用于配置服务器行为的参数。
opterr = 1;
设置 opterr变量为 1,表示getopt()函数在遇到未知选项时会打印错误消息到标准错误流。
while ((opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:")) != -1)
{
switch (opt)
{
case'B':
SetConfigOption("shared_buffers", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break; case'b':
/* Undocumented flag used for binary upgrades */
IsBinaryUpgrade = true;
break;
case'C':
output_config_variable = strdup(optarg);
break;
case'D':
userDoption = strdup(optarg);
break;
case'd':
set_debug_options(atoi(optarg), PGC_POSTMASTER, PGC_S_ARGV);
break;
case'E':
SetConfigOption("log_statement", "all", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'e':
SetConfigOption("datestyle", "euro", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'F':
SetConfigOption("fsync", "false", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'f':
if (!set_plan_disabling_options(optarg, PGC_POSTMASTER, PGC_S_ARGV))
{
write_stderr("%s: invalid argument for option -f: \"%s\"\n",
progname, optarg);
ExitPostmaster(1);
}
break;
case'h':
SetConfigOption("listen_addresses", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case'i':
SetConfigOption("listen_addresses", "*", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'j':
/* only used by interactive backend */
break;
case'k':
SetConfigOption("unix_socket_directories", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case'l':
SetConfigOption("ssl", "true", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'N':
SetConfigOption("max_connections", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case'n':
/* Don't reinit shared mem after abnormal exit */
Reinit = false;
break;
case'O':
SetConfigOption("allow_system_table_mods", "true", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'P':
SetConfigOption("ignore_system_indexes", "true", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'p':
SetConfigOption("port", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case'r':
/* only used by single-user backend */
break;
case'S':
SetConfigOption("work_mem", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case's':
SetConfigOption("log_statement_stats", "true", PGC_POSTMASTER, PGC_S_ARGV);
break;
case'T':
/*
* In the event that some backend dumps core, send SIGSTOP,
* rather than SIGQUIT, to all its peers. This lets the wily
* post_hacker collect core dumps from everyone.
*/
SendStop = true;
/*
* POLAR: set guc parameter polar_enable_send_stop at the same
* time when specified -T on command line to avoid the value
* of SendStop being reset by polar_enable_send_stop in
* postgresql.conf, the priority of setting guc parameter by
* command line is higher than that by postgresql.conf.
*/
SetConfigOption("polar_enable_send_stop", "true", PGC_POSTMASTER, PGC_S_ARGV);
break;
case't':
{
constchar *tmp = get_stats_option_name(optarg);
if (tmp)
{
SetConfigOption(tmp, "true", PGC_POSTMASTER, PGC_S_ARGV);
}
else
{
write_stderr("%s: invalid argument for option -t: \"%s\"\n",
progname, optarg);
ExitPostmaster(1);
}
break;
}
case'W':
SetConfigOption("post_auth_delay", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case'c':
case'-':
{
char *name,
*value;
ParseLongOption(optarg, &name, &value);
if (!value)
{
if (opt == '-')
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("--%s requires a value",
optarg)));
else
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("-c %s requires a value",
optarg)));
}
SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV);
free(name);
if (value)
free(value);
break;
}
default:
write_stderr("Try \"%s --help\" for more information.\n",
progname);
ExitPostmaster(1);
}
}
这是一个 while循环,用于解析命令行参数。getopt()函数用于解析命令行参数,它会返回下一个选项字符,如果所有选项都已解析完毕,则返回 -1。case 'B': 设置shared_buffers配置选项的值。case 'b': 设置IsBinaryUpgrade变量为true,表示正在进行二进制升级。case 'C': 将选项参数复制到output_config_variable变量中,表示要输出该配置变量的值。case 'D': 将选项参数复制到userDoption变量中,表示用户指定了数据目录。case 'd': 设置调试选项。case 'E': 设置log_statement配置选项的值为all,表示记录所有 SQL 语句。case 'e': 设置datestyle配置选项的值为euro,表示使用欧洲日期格式。case 'F': 设置fsync配置选项的值为false,表示禁用fsync。case 'f': 设置计划禁用选项。case 'h': 设置listen_addresses配置选项的值,表示监听的 IP 地址。case 'i': 设置listen_addresses配置选项的值为*,表示监听所有 IP 地址。case 'j': 该选项只用于交互式后端。case 'k': 设置unix_socket_directories配置选项的值,表示 Unix 域套接字目录。case 'l': 设置ssl配置选项的值为true,表示启用 SSL。case 'N': 设置max_connections配置选项的值,表示最大连接数。case 'n': 设置Reinit变量为false,表示在异常退出后不重新初始化共享内存。case 'O': 设置allow_system_table_mods配置选项的值为true,表示允许修改系统表。case 'P': 设置ignore_system_indexes配置选项的值为true,表示忽略系统索引。case 'p': 设置port配置选项的值,表示监听的端口号。case 'r': 该选项只用于单用户后端。case 'S': 设置work_mem配置选项的值,表示工作内存大小。case 's': 设置log_statement_stats配置选项的值为true,表示记录 SQL 语句的统计信息。case 'T': 设置SendStop变量为true,表示在后端进程崩溃时发送SIGSTOP信号给其他后端进程。case 't': 设置统计信息选项。case 'W': 设置post_auth_delay配置选项的值,表示身份验证后的延迟时间。case 'c': 设置配置选项的值。case '-': 设置配置选项的值。default: 如果遇到未知选项,则打印错误消息并退出。opt = getopt(argc, argv, "B:bc:C:D:d:EeFf:h:ijk:lN:nOPp:r:S:sTt:W:-:"): 调用getopt()函数,解析命令行参数。第三个参数是一个字符串,包含了所有有效的选项字符。如果一个选项字符后面跟着一个冒号 (:),则表示该选项需要一个参数。switch (opt): 根据getopt()函数返回的选项字符,执行相应的操作。
if (optind < argc)
{
write_stderr("%s: invalid argument: \"%s\"\n",
progname, argv[optind]);
write_stderr("Try \"%s --help\" for more information.\n",
progname);
ExitPostmaster(1);
}
检查是否存在非选项参数。如果存在,则打印错误消息并退出。
if (!SelectConfigFiles(userDoption, progname, output_config_variable != NULL))
ExitPostmaster(2);
调用 SelectConfigFiles()函数,选择配置文件。SelectConfigFiles()函数会根据用户指定的-D选项和程序名称,选择合适的配置文件,并读取postgresql.conf文件。
if (output_config_variable != NULL)
{
int flags = GetConfigOptionFlags(output_config_variable, true); if ((flags & GUC_RUNTIME_COMPUTED) == 0)
{
constchar *config_val = GetConfigOption(output_config_variable,
false, false);
puts(config_val ? config_val : "");
ExitPostmaster(0);
}
SetConfigOption("log_min_messages", "FATAL", PGC_SUSET,
PGC_S_OVERRIDE);
}
如果指定了 -C选项,则输出配置变量的值并退出。GetConfigOptionFlags(): 获取配置选项的标志。GUC_RUNTIME_COMPUTED: 运行时计算的配置选项。GetConfigOption(): 获取配置选项的值。puts(): 输出字符串到标准输出。ExitPostmaster(): 退出postmaster进程。SetConfigOption(): 设置配置选项的值。
checkDataDir();
调用 checkDataDir()函数,检查数据目录是否存在且可访问。
checkControlFile();
调用 checkControlFile()函数,检查控制文件是否存在且有效。
ChangeToDataDir();
调用 ChangeToDataDir()函数,将当前工作目录切换到数据目录。
if (ReservedBackends >= MaxConnections)
{
write_stderr("%s: superuser_reserved_connections (%d) must be less than max_connections (%d)\n",
progname,
ReservedBackends, MaxConnections);
ExitPostmaster(1);
}
if (XLogArchiveMode > ARCHIVE_MODE_OFF && wal_level == WAL_LEVEL_MINIMAL)
ereport(ERROR,
(errmsg("WAL archival cannot be enabled when wal_level is \"minimal\"")));
if (max_wal_senders > 0 && wal_level == WAL_LEVEL_MINIMAL)
ereport(ERROR,
(errmsg("WAL streaming (max_wal_senders > 0) requires wal_level \"replica\" or \"logical\"")));
检查配置选项的有效性。 ReservedBackends: 为超级用户保留的连接数。MaxConnections: 最大连接数。XLogArchiveMode: WAL 归档模式。ARCHIVE_MODE_OFF: 关闭归档模式。wal_level: WAL 级别。WAL_LEVEL_MINIMAL: 最小 WAL 级别。max_wal_senders: 最大 WAL 发送器数。
if (!CheckDateTokenTables())
{
write_stderr("%s: invalid datetoken tables, please fix\n", progname);
ExitPostmaster(1);
}
调用 CheckDateTokenTables()函数,检查日期令牌表是否有效。
optind = 1;
#ifdef HAVE_INT_OPTRESET
optreset = 1; /* some systems need this too */
#endif
重置 getopt()函数的状态,以便子进程可以重新解析命令行参数。
{
externchar **environ;
char **p; ereport(DEBUG3,
(errmsg_internal("%s: PostmasterMain: initial environment dump:",
progname)));
ereport(DEBUG3,
(errmsg_internal("-----------------------------------------")));
for (p = environ; *p; ++p)
ereport(DEBUG3,
(errmsg_internal("\t%s", *p)));
ereport(DEBUG3,
(errmsg_internal("-----------------------------------------")));
}
如果调试级别设置为 3 或更高,则打印环境变量。
polar_unlink_shmem_stat_file(0, 0);
on_proc_exit(polar_unlink_shmem_stat_file, 0);
POLAR: 删除共享内存统计文件。
CreateDataDirLockFile(true);
调用 CreateDataDirLockFile()函数,创建数据目录锁文件。
polar_init_node_type();
POLAR: 初始化节点类型。
if (polar_enable_shared_storage_mode)
{
if (!polar_is_replica())
polar_remove_replica_booted_file();
polar_init_global_dir_for_replica_or_standby();
}
POLAR: 如果启用了共享存储模式,则初始化全局目录。
LocalProcessControlFile(false);
ereport(LOG, errmsg("PolarDB load controlfile in postmaster"));
调用 LocalProcessControlFile()函数,读取控制文件。
ApplyLauncherRegister();
调用 ApplyLauncherRegister()函数,注册应用启动器。
polar_register_logindex_primary_saver();
POLAR: 注册日志索引主保存器。
process_shared_preload_libraries();
调用 process_shared_preload_libraries()函数,加载预加载的共享库。
#ifdef USE_SSL
if (EnableSSL)
{
(void) secure_initialize(true);
LoadedSSL = true;
}
#endif
如果启用了 SSL,则初始化 SSL 库。
InitializeMaxBackends();
调用 InitializeMaxBackends()函数,初始化MaxBackends变量。
process_shmem_requests();
调用 process_shmem_requests()函数,处理共享内存请求。
InitializeShmemGUCs();
调用 InitializeShmemGUCs()函数,初始化共享内存相关的 GUC 选项。
InitializeWalConsistencyChecking();
调用 InitializeWalConsistencyChecking()函数,初始化 WAL 一致性检查。
if (output_config_variable != NULL)
{
constchar *config_val = GetConfigOption(output_config_variable,
false, false);
puts(config_val ? config_val : "");
ExitPostmaster(0);
}
如果指定了 -C选项,则输出配置变量的值并退出。
reset_shared();
调用 reset_shared()函数,设置共享内存和信号量。
set_max_safe_fds();
调用 set_max_safe_fds()函数,估计可打开的文件数。
(void) set_stack_base();
调用 set_stack_base()函数,设置堆栈深度检查的参考点。
InitPostmasterDeathWatchHandle();
调用 InitPostmasterDeathWatchHandle()函数,初始化管道 (或 Windows 上的进程句柄),允许子进程在postmaster死亡时从睡眠中唤醒。
#ifdef WIN32
win32ChildQueue = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1);
if (win32ChildQueue == NULL)
ereport(FATAL,
(errmsg("could not create I/O completion port for child queue")));
#endif
在 Windows 平台上,创建一个 I/O 完成端口,用于传递死亡子进程的列表。
#ifdef EXEC_BACKEND
write_nondefault_variables(PGC_POSTMASTER);
RemovePgTempFilesInDir(PG_TEMP_FILES_DIR, true, false);
#endif
如果使用 EXEC_BACKEND,则将非默认 GUC 设置写入文件,供子进程使用,并删除临时文件。
RemovePromoteSignalFiles();
RemoveLogrotateSignalFiles();
if (unlink(LOG_METAINFO_DATAFILE) < 0 && errno != ENOENT)
ereport(LOG,
(errcode_for_file_access(),
errmsg("could not remove file \"%s\": %m",
LOG_METAINFO_DATAFILE)));
删除升级信号文件和日志轮换信号文件。
for (i = 0; i < MAXLISTEN; i++)
ListenSocket[i] = PGINVALID_SOCKET;
on_proc_exit(CloseServerPorts, 0);
初始化输入套接字,并将它们标记为关闭状态。
if (!polar_enable_multi_syslogger)
SysLoggerPID = SysLogger_Start(0);
else
{
int i; memset(SysLoggerPIDs, 0, sizeof(pid_t) * MAX_SYSLOGGER_NUM);
for (i = 0; i < polar_syslogger_num; i++)
SysLoggerPIDs[i] = SysLogger_Start(i);
}
启动系统日志收集子进程。
if (!(Log_destination & LOG_DESTINATION_STDERR))
ereport(LOG,
(errmsg("ending log output to stderr"),
errhint("Future log output will go to log destination \"%s\".",
Log_destination_string)));
whereToSendOutput = DestNone;
停止将日志消息发送到标准错误流。
ereport(LOG,
(errmsg("starting %s", PG_VERSION_STR)));
在日志中报告服务器启动。
if (ListenAddresses)
{
char *rawstring;
List *elemlist;
ListCell *l;
int success = 0; rawstring = pstrdup(ListenAddresses);
if (!SplitGUCList(rawstring, ',', &elemlist))
{
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid list syntax in parameter \"%s\"",
"listen_addresses")));
}
foreach(l, elemlist)
{
char *curhost = (char *) lfirst(l);
if (strcmp(curhost, "*") == 0)
status = StreamServerPort(AF_UNSPEC, NULL,
(unsigned short) PostPortNumber,
NULL,
ListenSocket, MAXLISTEN);
else
status = StreamServerPort(AF_UNSPEC, curhost,
(unsigned short) PostPortNumber,
NULL,
ListenSocket, MAXLISTEN);
if (status == STATUS_OK)
{
success++;
if (!listen_addr_saved)
{
AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, curhost);
listen_addr_saved = true;
}
}
else
ereport(WARNING,
(errmsg("could not create listen socket for \"%s\"",
curhost)));
}
if (!success && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any TCP/IP sockets")));
list_free(elemlist);
pfree(rawstring);
}
建立输入套接字。
#ifdef USE_BONJOUR
if (enable_bonjour && ListenSocket[0] != PGINVALID_SOCKET)
{
DNSServiceErrorType err; err = DNSServiceRegister(&bonjour_sdref,
0,
0,
bonjour_name,
"_postgresql._tcp.",
NULL,
NULL,
pg_hton16(PostPortNumber),
0,
NULL,
NULL,
NULL);
if (err != kDNSServiceErr_NoError)
ereport(LOG,
(errmsg("DNSServiceRegister() failed: error code %ld",
(long) err)));
}
#endif
如果启用了 Bonjour,则注册 Bonjour 服务。
#ifdef HAVE_UNIX_SOCKETS
if (Unix_socket_directories)
{
char *rawstring;
List *elemlist;
ListCell *l;
int success = 0; rawstring = pstrdup(Unix_socket_directories);
if (!SplitDirectoriesString(rawstring, ',', &elemlist))
{
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid list syntax in parameter \"%s\"",
"unix_socket_directories")));
}
foreach(l, elemlist)
{
char *socketdir = (char *) lfirst(l);
status = StreamServerPort(AF_UNIX, NULL,
(unsigned short) PostPortNumber,
socketdir,
ListenSocket, MAXLISTEN);
if (status == STATUS_OK)
{
success++;
if (success == 1)
AddToDataDirLockFile(LOCK_FILE_LINE_SOCKET_DIR, socketdir);
}
else
ereport(WARNING,
(errmsg("could not create Unix-domain socket in directory \"%s\"",
socketdir)));
}
if (!success && elemlist != NIL)
ereport(FATAL,
(errmsg("could not create any Unix-domain sockets")));
list_free_deep(elemlist);
pfree(rawstring);
}
#endif
如果启用了 Unix 域套接字,则创建 Unix 域套接字。
if (ListenSocket[0] == PGINVALID_SOCKET)
ereport(FATAL,
(errmsg("no socket created for listening")));
检查是否创建了任何套接字。
if (!listen_addr_saved)
AddToDataDirLockFile(LOCK_FILE_LINE_LISTEN_ADDR, "");
如果没有有效的 TCP 端口,则为监听地址写入一个空行,表示必须使用 Unix 套接字。
if (!CreateOptsFile(argc, argv, my_exec_path))
ExitPostmaster(1);
调用 CreateOptsFile()函数,记录postmaster选项。
if (external_pid_file)
{
FILE *fpidfile = fopen(external_pid_file, "w"); if (fpidfile)
{
fprintf(fpidfile, "%d\n", MyProcPid);
fclose(fpidfile);
if (chmod(external_pid_file, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0)
write_stderr("%s: could not change permissions of external PID file \"%s\": %s\n",
progname, external_pid_file, strerror(errno));
}
else
write_stderr("%s: could not write external PID file \"%s\": %s\n",
progname, external_pid_file, strerror(errno));
on_proc_exit(unlink_external_pid_file, 0);
}
如果请求了外部 PID 文件,则写入 PID 文件。
RemovePgTempFiles();
调用 RemovePgTempFiles()函数,删除旧的临时文件。
autovac_init();
调用 autovac_init()函数,初始化自动清理子系统。
if (!load_hba())
{
ereport(FATAL,
(errmsg("could not load pg_hba.conf")));
}
if (!load_ident())
{
}
加载客户端身份验证配置文件。
#ifdef HAVE_PTHREAD_IS_THREADED_NP
if (pthread_is_threaded_np() != 0)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("postmaster became multithreaded during startup"),
errhint("Set the LC_ALL environment variable to a valid locale.")));
#endif
检查 postmaster是否在启动期间变为多线程。
/*
* Remember postmaster startup time
*/
PgStartTime = GetCurrentTimestamp();
PgStartTime = GetCurrentTimestamp();: 调用GetCurrentTimestamp()函数获取当前时间戳,并将其赋值给PgStartTime全局变量。PgStartTime用于记录postmaster进程的启动时间。
/*
* Report postmaster status in the postmaster.pid file, to allow pg_ctl to
* see what's happening.
*/
AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);
AddToDataDirLockFile(LOCK_FILE_LINE_PM_STATUS, PM_STATUS_STARTING);: 调用AddToDataDirLockFile()函数,将postmaster进程的状态写入数据目录锁文件 (postmaster.pid)。LOCK_FILE_LINE_PM_STATUS是一个宏,表示要写入的状态类型,PM_STATUS_STARTING是一个宏,表示postmaster进程正在启动。 这样做是为了让pg_ctl等管理工具能够监控postmaster进程的状态。
/* Start bgwriter and checkpointer so they can help with recovery */
if (CheckpointerPID == 0)
CheckpointerPID = StartCheckpointer();
if (BgWriterPID == 0)
BgWriterPID = StartBackgroundWriter();
这段代码启动后台写入进程 ( bgwriter) 和检查点进程 (checkpointer)。 这两个进程对于数据库的正常运行至关重要。if (CheckpointerPID == 0): 检查CheckpointerPID全局变量是否为 0。如果为 0,表示检查点进程尚未启动。CheckpointerPID = StartCheckpointer();: 调用StartCheckpointer()函数启动检查点进程,并将返回的进程 ID 赋值给CheckpointerPID全局变量。if (BgWriterPID == 0): 检查BgWriterPID全局变量是否为 0。如果为 0,表示后台写入进程尚未启动。BgWriterPID = StartBackgroundWriter();: 调用StartBackgroundWriter()函数启动后台写入进程,并将返回的进程 ID 赋值给BgWriterPID全局变量。这两个进程在数据库启动过程中起着重要的作用,尤其是在数据库需要从崩溃中恢复时。
/*
* We're ready to rock and roll...
*/
StartupPID = StartupDataBase();
Assert(StartupPID != 0);
StartupStatus = STARTUP_RUNNING;
pmState = PM_STARTUP;
这段代码启动数据库启动进程 ( startup),并设置postmaster进程的状态。StartupPID = StartupDataBase();: 调用StartupDataBase()函数启动数据库启动进程,并将返回的进程 ID 赋值给StartupPID全局变量。 数据库启动进程负责执行数据库的启动过程,例如恢复 WAL 日志、应用未完成的事务等。Assert(StartupPID != 0);: 使用Assert()宏检查StartupPID是否不为 0。如果为 0,表示数据库启动进程启动失败,程序会立即终止。StartupStatus = STARTUP_RUNNING;: 设置StartupStatus全局变量为STARTUP_RUNNING,表示数据库启动进程正在运行。pmState = PM_STARTUP;: 设置pmState全局变量为PM_STARTUP,表示postmaster进程处于启动状态。
/* Some workers may be scheduled to start now */
maybe_start_bgworkers();
maybe_start_bgworkers();: 调用maybe_start_bgworkers()函数,启动一些可能需要立即启动的后台工作进程。
status = ServerLoop();
status = ServerLoop();: 调用ServerLoop()函数,进入主服务器循环。ServerLoop()函数负责监听客户端连接请求,并为每个连接请求创建一个新的后端进程来处理。 这个函数是postmaster进程的核心部分,它会一直运行,直到服务器关闭。
/*
* ServerLoop probably shouldn't ever return, but if it does, close down.
*/
ExitPostmaster(status != STATUS_OK);
ExitPostmaster(status != STATUS_OK);: 如果ServerLoop()函数返回,则调用ExitPostmaster()函数关闭服务器。ExitPostmaster()函数会发送信号给所有子进程,并等待它们关闭,然后关闭共享内存和信号量,并退出postmaster进程。status != STATUS_OK表示ServerLoop()函数返回了一个错误状态。
abort(); /* not reached */
abort();: 调用abort()函数终止程序。 由于ServerLoop()函数通常不会返回,因此这行代码实际上是不可达的。abort()函数会生成一个SIGABRT信号,导致程序异常终止,并生成一个 core dump 文件,可以用于调试。
总结:
PostmasterMain() 函数是 postmaster 进程的主要入口点,它负责:
初始化进程环境。 解析命令行参数。 读取配置文件。 检查数据目录和控制文件。 启动后台写入进程和检查点进程。 启动数据库启动进程。 进入主服务器循环,监听客户端连接请求。 如果服务器循环返回,则关闭服务器。
这个函数是 PostgreSQL 服务器的核心,它负责管理和协调数据库服务器的各个组件。
2 解读ServerLoop()函数
代码流程总结
初始化:设置监听套接字集合,记录初始时间。 主循环: 使用 select监听新连接,设置超时。处理新连接,启动子进程。 检查并重启挂掉的后台进程。 执行定时任务(锁文件检查、文件触摸)。 异常处理:强制终止卡住的子进程。
关键概念解释
select模型:一种I/O多路复用机制,监听多个文件描述符的事件。后台进程:如 Checkpointer(检查点)、BgWriter(后台写)、SysLogger(日志)等,各司其职。文件描述符集合( fd_set):表示一组需要监听的套接字。信号屏蔽( PG_SETMASK):在关键代码段阻塞信号,避免竞态条件。
通过ServerLoop,Postmaster 持续监听客户端请求,确保后台服务健康运行,并维护系统稳定性。
下面解读 ServerLoop() 函数。这个函数是 postmaster 进程的主循环,负责监听连接请求、启动子进程、管理后台进程等。
/*
* Main idle loop of postmaster
*
* NB: Needs to be called with signals blocked
*/
staticint
ServerLoop(void)
{
这是一个函数定义,名为 ServerLoop,返回类型为int。注释说明这个函数是 postmaster进程的主循环,并且需要在信号被阻塞的情况下调用。
fd_set readmask;
int nSockets;
time_t last_lockfile_recheck_time,
last_touch_time;
last_lockfile_recheck_time = last_touch_time = time(NULL);
nSockets = initMasks(&readmask);
声明了一些局部变量: readmask: 一个fd_set类型的变量,用于存储需要监听的文件描述符集合。nSockets: 一个int类型的变量,用于存储需要监听的文件描述符的数量。last_lockfile_recheck_time: 一个time_t类型的变量,用于存储上次检查数据目录锁文件的时间。last_touch_time: 一个time_t类型的变量,用于存储上次触摸 Unix 域套接字文件和锁文件的时间。last_lockfile_recheck_time = last_touch_time = time(NULL);: 初始化last_lockfile_recheck_time和last_touch_time变量为当前时间。nSockets = initMasks(&readmask);: 调用initMasks()函数,初始化readmask变量,并将需要监听的文件描述符添加到readmask中。initMasks()函数还会返回需要监听的文件描述符的数量,并将其赋值给nSockets变量。
for (;;)
{
fd_set rmask;
int selres;
time_t now; /*
* Wait for a connection request to arrive.
*
* We block all signals except while sleeping. That makes it safe for
* signal handlers, which again block all signals while executing, to
* do nontrivial work.
*
* If we are in PM_WAIT_DEAD_END state, then we don't want to accept
* any new connections, so we don't call select(), and just sleep.
*/
memcpy((char *) &rmask, (char *) &readmask, sizeof(fd_set));
if (pmState == PM_WAIT_DEAD_END)
{
PG_SETMASK(&UnBlockSig);
pg_usleep(100000L); /* 100 msec seems reasonable */
selres = 0;
PG_SETMASK(&BlockSig);
}
else
{
/* must set timeout each time; some OSes change it! */
struct timeval timeout;
/* Needs to run with blocked signals! */
DetermineSleepTime(&timeout);
PG_SETMASK(&UnBlockSig);
selres = select(nSockets, &rmask, NULL, NULL, &timeout);
PG_SETMASK(&BlockSig);
}
/* Now check the select() result */
if (selres < 0)
{
if (errno != EINTR && errno != EWOULDBLOCK)
{
ereport(LOG,
(errcode_for_socket_access(),
errmsg("select() failed in postmaster: %m")));
return STATUS_ERROR;
}
}
/*
* New connection pending on any of our sockets? If so, fork a child
* process to deal with it.
*/
if (selres > 0)
{
int i;
for (i = 0; i < MAXLISTEN; i++)
{
if (ListenSocket[i] == PGINVALID_SOCKET)
break;
if (FD_ISSET(ListenSocket[i], &rmask))
{
Port *port;
port = ConnCreate(ListenSocket[i]);
if (port)
{
BackendStartup(port);
/*
* We no longer need the open socket or port structure
* in this process
*/
StreamClose(port->sock);
ConnFree(port);
}
}
}
}
这是一个无限循环,用于监听连接请求。 int i;: 声明一个int类型的变量i,用于循环遍历监听套接字。for (i = 0; i < MAXLISTEN; i++): 循环遍历监听套接字。Port *port;: 声明一个Port类型的指针port,用于存储新连接的信息。port = ConnCreate(ListenSocket[i]);: 调用ConnCreate()函数,创建一个新的Port结构体,并将监听套接字ListenSocket[i]传递给ConnCreate()函数。ConnCreate()函数会接受新的连接请求,并创建一个新的套接字用于与客户端通信。if (port): 检查port是否为 NULL。 如果不为 NULL,表示连接创建成功。BackendStartup(port);: 调用BackendStartup()函数,启动一个新的后端进程来处理该连接。BackendStartup()函数会将port结构体传递给新的后端进程。StreamClose(port->sock);: 关闭监听套接字。 由于连接已经由新的后端进程处理,因此postmaster进程不再需要该套接字。ConnFree(port);: 释放port结构体占用的内存。if (ListenSocket[i] == PGINVALID_SOCKET): 检查当前监听套接字是否无效。 如果无效,则跳出循环。if (FD_ISSET(ListenSocket[i], &rmask)): 检查当前监听套接字是否在rmask中。 如果在rmask中,表示该套接字准备好可读,即有新的连接请求到达。if (errno != EINTR && errno != EWOULDBLOCK): 检查错误码是否为EINTR或EWOULDBLOCK。EINTR表示select()函数被信号中断,EWOULDBLOCK表示select()函数在非阻塞模式下调用,但没有文件描述符准备好。 如果错误码不是EINTR或EWOULDBLOCK,则表示发生了其他错误。ereport(LOG, (errcode_for_socket_access(), errmsg("select() failed in postmaster: %m")));: 使用ereport()函数记录错误日志。return STATUS_ERROR;: 返回STATUS_ERROR,表示ServerLoop()函数执行失败。struct timeval timeout;: 声明一个timeval类型的变量timeout,用于存储select()函数的超时时间。DetermineSleepTime(&timeout);: 调用DetermineSleepTime()函数,计算select()函数的超时时间,并将结果存储在timeout变量中。PG_SETMASK(&UnBlockSig);: 取消阻塞所有信号。selres = select(nSockets, &rmask, NULL, NULL, &timeout);: 调用select()函数,监听文件描述符集合rmask,等待连接请求。select()函数会阻塞,直到有文件描述符准备好可读,或者超时时间到达。select()函数的返回值selres表示准备好的文件描述符的数量。PG_SETMASK(&BlockSig);: 重新阻塞所有信号。PG_SETMASK(&UnBlockSig);: 取消阻塞所有信号。pg_usleep(100000L);: 休眠 100 毫秒。selres = 0;: 设置selres为 0,表示没有文件描述符准备好。PG_SETMASK(&BlockSig);: 重新阻塞所有信号。fd_set rmask;: 声明一个fd_set类型的变量rmask,用于存储select()函数返回的可读文件描述符集合。int selres;: 声明一个int类型的变量selres,用于存储select()函数的返回值。time_t now;: 声明一个time_t类型的变量now,用于存储当前时间。memcpy((char *) &rmask, (char *) &readmask, sizeof(fd_set));: 将readmask复制到rmask中。readmask包含了需要监听的文件描述符集合,rmask将被传递给select()函数。if (pmState == PM_WAIT_DEAD_END): 检查postmaster进程是否处于PM_WAIT_DEAD_END状态。 如果处于该状态,表示postmaster进程正在等待所有子进程退出,不接受新的连接请求。else: 如果postmaster进程不处于PM_WAIT_DEAD_END状态,则执行以下代码:if (selres < 0): 检查select()函数的返回值是否小于 0。 如果小于 0,表示select()函数调用失败。if (selres > 0): 检查select()函数的返回值是否大于 0。 如果大于 0,表示有文件描述符准备好可读,即有新的连接请求到达。
/* If we have lost the log collector, try to start a new one */
/* POLAR */
if (!polar_enable_multi_syslogger)
{
if (SysLoggerPID == 0 && Logging_collector)
SysLoggerPID = SysLogger_Start(0);
}
else
{
/* POLAR */
if (Logging_collector)
{
int i; for (i = 0; i < polar_syslogger_num; i++)
{
if (SysLoggerPIDs[i] == 0)
SysLoggerPIDs[i] = SysLogger_Start(i);
}
}
}
/* POLAR end */
这段代码检查系统日志收集进程 ( syslogger) 是否正在运行,如果未运行,则启动一个新的系统日志收集进程。if (Logging_collector): 检查Logging_collector是否为 true。int i;: 声明一个int类型的变量i,用于循环遍历系统日志收集进程。for (i = 0; i < polar_syslogger_num; i++): 循环遍历系统日志收集进程。if (SysLoggerPIDs[i] == 0): 检查当前系统日志收集进程是否正在运行。SysLoggerPIDs[i] = SysLogger_Start(i);: 调用SysLogger_Start(i)函数,启动一个新的系统日志收集进程,并将返回的进程 ID 赋值给SysLoggerPIDs[i]变量。if (SysLoggerPID == 0 && Logging_collector): 检查SysLoggerPID是否为 0,以及Logging_collector是否为 true。SysLoggerPID为 0 表示系统日志收集进程未运行,Logging_collector为 true 表示启用了日志收集。SysLoggerPID = SysLogger_Start(0);: 调用SysLogger_Start(0)函数,启动一个新的系统日志收集进程,并将返回的进程 ID 赋值给SysLoggerPID变量。if (!polar_enable_multi_syslogger): 检查是否启用了多系统日志收集器。else: 如果启用了多系统日志收集器,则执行以下代码:
/*
* If no background writer process is running, and we are not in a
* state that prevents it, start one. It doesn't matter if this
* fails, we'll just try again later. Likewise for the checkpointer.
*/
if (pmState == PM_RUN || pmState == PM_RECOVERY ||
pmState == PM_HOT_STANDBY || pmState == PM_STARTUP)
{
if (CheckpointerPID == 0)
CheckpointerPID = StartCheckpointer();
if (BgWriterPID == 0)
BgWriterPID = StartBackgroundWriter();
/* POLAR: start logindex background process */
if (polar_logindex_redo_instance && LogIndexBgPID == 0)
LogIndexBgPID = StartLogIndexBgWriter();
/* POLAR end */
}
这段代码检查后台写入进程 ( bgwriter) 和检查点进程 (checkpointer) 是否正在运行,如果未运行,则启动一个新的后台写入进程和检查点进程。if (CheckpointerPID == 0): 检查CheckpointerPID是否为 0。CheckpointerPID为 0 表示检查点进程未运行。CheckpointerPID = StartCheckpointer();: 调用StartCheckpointer()函数,启动一个新的检查点进程,并将返回的进程 ID 赋值给CheckpointerPID变量。if (BgWriterPID == 0): 检查BgWriterPID是否为 0。BgWriterPID为 0 表示后台写入进程未运行。BgWriterPID = StartBackgroundWriter();: 调用StartBackgroundWriter()函数,启动一个新的后台写入进程,并将返回的进程 ID 赋值给BgWriterPID变量。if (polar_logindex_redo_instance && LogIndexBgPID == 0): POLAR: 检查是否启用了日志索引重做实例,以及日志索引后台进程是否正在运行。LogIndexBgPID = StartLogIndexBgWriter();: POLAR: 启动日志索引后台进程。if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP): 检查postmaster进程是否处于PM_RUN、PM_RECOVERY、PM_HOT_STANDBY或PM_STARTUP状态。 这些状态表示数据库正在正常运行、从崩溃中恢复、作为热备服务器运行或正在启动。
/*
* Likewise, if we have lost the walwriter process, try to start a new
* one. But this is needed only in normal operation (else we cannot
* be writing any new WAL).
*/
if (WalWriterPID == 0 && pmState == PM_RUN)
WalWriterPID = StartWalWriter();
这段代码检查 WAL 写入进程 ( walwriter) 是否正在运行,如果未运行,则启动一个新的 WAL 写入进程。if (WalWriterPID == 0 && pmState == PM_RUN): 检查WalWriterPID是否为 0,以及postmaster进程是否处于PM_RUN状态。WalWriterPID为 0 表示 WAL 写入进程未运行,PM_RUN表示数据库正在正常运行。WalWriterPID = StartWalWriter();: 调用StartWalWriter()函数,启动一个新的 WAL 写入进程,并将返回的进程 ID 赋值给WalWriterPID变量。
/*
* If we have lost the autovacuum launcher, try to start a new one. We
* don't want autovacuum to run in binary upgrade mode because
* autovacuum might update relfrozenxid for empty tables before the
* physical files are put in place.
*/
if (!IsBinaryUpgrade && AutoVacPID == 0 &&
(AutoVacuumingActive() || start_autovac_launcher) &&
pmState == PM_RUN)
{
AutoVacPID = StartAutoVacLauncher();
if (AutoVacPID != 0)
start_autovac_launcher = false; /* signal processed */
}
这段代码检查自动清理启动进程 ( autovacuum launcher) 是否正在运行,如果未运行,则启动一个新的自动清理启动进程。start_autovac_launcher = false;: 设置start_autovac_launcher变量为 false,表示已经处理了启动自动清理启动进程的信号。!IsBinaryUpgrade: 数据库是否处于二进制升级模式。AutoVacPID == 0: 自动清理启动进程是否正在运行。(AutoVacuumingActive() || start_autovac_launcher): 自动清理是否处于活动状态,或者是否需要启动自动清理启动进程。pmState == PM_RUN:postmaster进程是否处于PM_RUN状态。if (!IsBinaryUpgrade && AutoVacPID == 0 && (AutoVacuumingActive() || start_autovac_launcher) && pmState == PM_RUN): 检查以下条件是否成立:AutoVacPID = StartAutoVacLauncher();: 调用StartAutoVacLauncher()函数,启动一个新的自动清理启动进程,并将返回的进程 ID 赋值给AutoVacPID变量。if (AutoVacPID != 0): 检查AutoVacPID是否不为 0。 如果不为 0,表示自动清理启动进程启动成功。
/* If we have lost the archiver, try to start a new one. */
if (PgArchPID == 0 && PgArchStartupAllowed())
PgArchPID = StartArchiver();
这段代码检查归档进程 ( archiver) 是否正在运行,如果未运行,则启动一个新的归档进程。if (PgArchPID == 0 && PgArchStartupAllowed()): 检查PgArchPID是否为 0,以及是否允许启动归档进程。PgArchPID为 0 表示归档进程未运行,PgArchStartupAllowed()函数用于检查是否允许启动归档进程。PgArchPID = StartArchiver();: 调用StartArchiver()函数,启动一个新的归档进程,并将返回的进程 ID 赋值给PgArchPID变量。
/* If we need to signal the autovacuum launcher, do so now */
if (avlauncher_needs_signal)
{
avlauncher_needs_signal = false;
if (AutoVacPID != 0)
kill(AutoVacPID, SIGUSR2);
}
这段代码检查是否需要向自动清理启动进程发送信号,如果需要,则发送 SIGUSR2信号。kill(AutoVacPID, SIGUSR2);: 向自动清理启动进程发送SIGUSR2信号。if (avlauncher_needs_signal): 检查avlauncher_needs_signal变量是否为 true。avlauncher_needs_signal变量用于表示是否需要向自动清理启动进程发送信号。avlauncher_needs_signal = false;: 设置avlauncher_needs_signal变量为 false,表示已经处理了发送信号的请求。if (AutoVacPID != 0): 检查AutoVacPID是否不为 0。 如果不为 0,表示自动清理启动进程正在运行。
/* If we need to start a WAL receiver, try to do that now */
if (WalReceiverRequested)
MaybeStartWalReceiver();
这段代码检查是否需要启动 WAL 接收进程 ( wal receiver),如果需要,则启动一个新的 WAL 接收进程。if (WalReceiverRequested): 检查WalReceiverRequested变量是否为 true。WalReceiverRequested变量用于表示是否需要启动 WAL 接收进程。MaybeStartWalReceiver();: 调用MaybeStartWalReceiver()函数,启动一个新的 WAL 接收进程。
/* Get other worker processes running, if needed */
if (StartWorkerNeeded || HaveCrashedWorker)
maybe_start_bgworkers();
这段代码检查是否需要启动其他工作进程,如果需要,则启动新的工作进程。 if (StartWorkerNeeded || HaveCrashedWorker): 检查StartWorkerNeeded变量是否为 true,或者HaveCrashedWorker变量是否为 true。StartWorkerNeeded变量用于表示是否需要启动新的工作进程,HaveCrashedWorker变量用于表示是否有工作进程崩溃。maybe_start_bgworkers();: 调用maybe_start_bgworkers()函数,启动新的工作进程。
#ifdef HAVE_PTHREAD_IS_THREADED_NP
/*
* With assertions enabled, check regularly for appearance of
* additional threads. All builds check at start and exit.
*/
Assert(pthread_is_threaded_np() == 0);
#endif
这段代码在启用了断言的情况下,定期检查是否出现了额外的线程。 #ifdef HAVE_PTHREAD_IS_THREADED_NP: 检查是否定义了HAVE_PTHREAD_IS_THREADED_NP宏。 该宏表示系统是否支持pthread_is_threaded_np()函数。Assert(pthread_is_threaded_np() == 0);: 调用pthread_is_threaded_np()函数,检查当前进程是否为多线程。 如果为多线程,则pthread_is_threaded_np()函数返回非 0 值,Assert()宏会触发断言失败,导致程序终止。
/*
* Lastly, check to see if it's time to do some things that we don't
* want to do every single time through the loop, because they're a
* bit expensive. Note that there's up to a minute of slop in when
* these tasks will be performed, since DetermineSleepTime() will let
* us sleep at most that long; except for SIGKILL timeout which has
* special-case logic there.
*/
now = time(NULL);
这段代码获取当前时间,用于后续的时间相关的检查。 now = time(NULL);: 调用time(NULL)函数,获取当前时间,并将结果赋值给now变量。
/*
* If we already sent SIGQUIT to children and they are slow to shut
* down, it's time to send them SIGKILL. This doesn't happen
* normally, but under certain conditions backends can get stuck while
* shutting down. This is a last measure to get them unwedged.
*
* Note we also do this during recovery from a process crash.
*/
if ((Shutdown >= ImmediateShutdown || (FatalError && !SendStop)) &&
AbortStartTime != 0 &&
(now - AbortStartTime) >= SIGKILL_CHILDREN_AFTER_SECS)
{
/* We were gentle with them before. Not anymore */
ereport(LOG,
(errmsg("issuing SIGKILL to recalcitrant children")));
TerminateChildren(SIGKILL);
/* reset flag so we don't SIGKILL again */
AbortStartTime = 0;
}
这段代码检查是否需要向子进程发送 SIGKILL信号。(Shutdown >= ImmediateShutdown || (FatalError && !SendStop)): 服务器是否正在立即关闭,或者是否发生了致命错误且没有发送SIGSTOP信号。AbortStartTime != 0:AbortStartTime变量是否不为 0。AbortStartTime变量用于记录开始关闭服务器的时间。(now - AbortStartTime) >= SIGKILL_CHILDREN_AFTER_SECS: 当前时间与开始关闭服务器的时间之差是否大于或等于SIGKILL_CHILDREN_AFTER_SECS。SIGKILL_CHILDREN_AFTER_SECS是一个宏,表示在发送SIGQUIT信号后,等待子进程关闭的时间。if ((Shutdown >= ImmediateShutdown || (FatalError && !SendStop)) && AbortStartTime != 0 && (now - AbortStartTime) >= SIGKILL_CHILDREN_AFTER_SECS): 检查以下条件是否成立:ereport(LOG, (errmsg("issuing SIGKILL to recalcitrant children")));: 使用ereport()函数记录日志消息,表示正在向子进程发送SIGKILL信号。TerminateChildren(SIGKILL);: 调用TerminateChildren()函数,向所有子进程发送SIGKILL信号。AbortStartTime = 0;: 设置AbortStartTime变量为 0,表示已经发送了SIGKILL信号。
/*
* Once a minute, verify that postmaster.pid hasn't been removed or
* overwritten. If it has, we force a shutdown. This avoids having
* postmasters and child processes hanging around after their database
* is gone, and maybe causing problems if a new database cluster is
* created in the same place. It also provides some protection
* against a DBA foolishly removing postmaster.pid and manually
* starting a new postmaster. Data corruption is likely to ensue from
* that anyway, but we can minimize the damage by aborting ASAP.
*/
if (now - last_lockfile_recheck_time >= 1 * SECS_PER_MINUTE)
{
if (!RecheckDataDirLockFile())
{
ereport(LOG,
(errmsg("performing immediate shutdown because data directory lock file is invalid")));
kill(MyProcPid, SIGQUIT);
}
last_lockfile_recheck_time = now;
}
这段代码定期检查数据目录锁文件 ( postmaster.pid) 是否仍然有效。ereport(LOG, (errmsg("performing immediate shutdown because data directory lock file is invalid")));: 使用ereport()函数记录日志消息,表示数据目录锁文件无效,正在执行立即关闭。kill(MyProcPid, SIGQUIT);: 向postmaster进程发送SIGQUIT信号,导致postmaster进程立即关闭。if (now - last_lockfile_recheck_time >= 1 * SECS_PER_MINUTE): 检查当前时间与上次检查数据目录锁文件的时间之差是否大于或等于 1 分钟。if (!RecheckDataDirLockFile()): 调用RecheckDataDirLockFile()函数,检查数据目录锁文件是否仍然有效。 如果无效,则执行以下代码:last_lockfile_recheck_time = now;: 设置last_lockfile_recheck_time变量为当前时间,表示已经完成了数据目录锁文件的检查。
/*
* Touch Unix socket and lock files every 58 minutes, to ensure that
* they are not removed by overzealous /tmp-cleaning tasks. We assume
* no one runs cleaners with cutoff times of less than an hour ...
*/
if (now - last_touch_time >= 58 * SECS_PER_MINUTE)
{
TouchSocketFiles();
TouchSocketLockFiles();
last_touch_time = now;
}
}
}
这段代码定期触摸 Unix 域套接字文件和锁文件,以防止它们被过度的 /tmp清理任务删除。if (now - last_touch_time >= 58 * SECS_PER_MINUTE): 检查当前时间与上次触摸文件的时间之差是否大于或等于 58 分钟。TouchSocketFiles();: 调用TouchSocketFiles()函数,触摸 Unix 域套接字文件。TouchSocketLockFiles();: 调用TouchSocketLockFiles()函数,触摸锁文件。last_touch_time = now;: 设置last_touch_time变量为当前时间,表示已经完成了文件的触摸操作.
总结:
ServerLoop() 函数是 postmaster 进程的主循环,它负责:
监听连接请求,并为每个连接请求启动一个新的后端进程。 检查系统日志收集进程、后台写入进程、检查点进程、WAL 写入进程、自动清理启动进程和归档进程是否正在运行,如果未运行,则启动新的进程。 定期检查数据目录锁文件是否仍然有效,如果无效,则关闭服务器。 定期触摸 Unix 域套接字文件和锁文件,以防止它们被删除。
这个函数是 PostgreSQL 服务器的核心,它负责管理和协调数据库服务器的各个组件。
以上内容基于DeepSeek及诸多AI生成, 轻微人工调整, 感谢杭州深度求索人工智能等公司
AI 生成的内容请自行辨别正确性, 当然也多了些许踩坑的乐趣, 毕竟冒险是每个男人的天性.