PostgreSQL码农集散地

解读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 进程的主要入口点,它负责:

  1. 初始化进程环境。
  2. 解析命令行参数。
  3. 读取配置文件。
  4. 检查数据目录和控制文件。
  5. 启动后台写入进程和检查点进程。
  6. 启动数据库启动进程。
  7. 进入主服务器循环,监听客户端连接请求。
  8. 如果服务器循环返回,则关闭服务器。

这个函数是 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 进程的主循环,它负责:

  1. 监听连接请求,并为每个连接请求启动一个新的后端进程。
  2. 检查系统日志收集进程、后台写入进程、检查点进程、WAL 写入进程、自动清理启动进程和归档进程是否正在运行,如果未运行,则启动新的进程。
  3. 定期检查数据目录锁文件是否仍然有效,如果无效,则关闭服务器。
  4. 定期触摸 Unix 域套接字文件和锁文件,以防止它们被删除。

这个函数是 PostgreSQL 服务器的核心,它负责管理和协调数据库服务器的各个组件。

 以上内容基于DeepSeek及诸多AI生成, 轻微人工调整, 感谢杭州深度求索人工智能等公司 

 AI 生成的内容请自行辨别正确性, 当然也多了些许踩坑的乐趣, 毕竟冒险是每个男人的天性.