PostgreSQL系统的主要功能都集中于Postgres程序,其入口是Main模块中的main函数,在初始化数据集簇、启动数据库服务器时,都将从这里开始执行。Main模块主要的工作是确定当前的操作系统平台,并据此做一些平台相关的环境变量设置和初始化,然后通过对命令行参数的判断,将控制转到相应的模块中去。PG使用一种专用服务器进程体系结构,其中,最主要的两个进程就是守护进程Postmaster和服务进程Postgres。从本质上来说,Postmaster和Postgres都是通过载入Postgres程序而形成的进程,只是在运行时所处的分支不同而已。Postmaster在Linux和Unix系统上,仅仅是Postgres的一个符号链接。
守护进程Postmaster负责整个系统的启动和关闭它监听并接受客户端的连接请求,为其分配服务进程Postgres。服务进程Postgres接受并执行客户端发送的命令,它在底层模块(如存储、事务管理、索引等)之上调用各个主要的功能模块(如编译器、优化器、执行器等),完成客户端的各种数据库操作,并返回执行结果。PostgreSQL守护进程Postmaster除了用户连接请求分配后台Postgres服务进程外,还将启动相关的后台辅助进程。守护进程Postmaster在完成基本运行环境初始化、创建接受用户请求的监听端口后,顺序启动如下系统辅助进程:SysLogger(系统日志进程)、PgStat(统计数据收集进程)、AutoVacuum(系统自动清理进程)。在守护进程Postmaster进入到循环监听中时启动如下进程:BgWriter(后台写进程)、WALWriter(预写式日志写进程)、PgArch(预写式日志归档进程)。
完成数据集簇初始化后,用户可以启动一个数据库实例来运行数据库管理系统,多用户模式下一个数据库实例由数据库服务器守护进程Postmaster来管理。它是一个运行在服务器上的总控进程,负责整个系统的启动和关闭,并且在服务进程出错时完成系统的恢复。它管理数据库文件、监听并接受来自客户端的连接请求,并且为客户端连接请求fork一个Postgres服务器,来代表客户端在数据库上执行各种命令。同时Postmaster还管理与数据库运行相关的辅助进程。用户可以使用postmaster、postgres或者pg_ctl命令启动Postmaster。使用/opt/pgsql(安装目录)/bin/postgres -D /pgdata/10/data/可以在前台运行数据库服务器,通常加上&符号可以在后台运行。PG的守护进程postmaster的入口函数注册了信号处理程序,对SIGINT、SIGTERM、SIGQUIT的处理方式分别对应PG的三种关闭方式smart、fast、immediate。因此可以使用kill命令给postgres进程发送SIGTERM、SIGINT、SIGQUIT信号停止数据库,比如
kill -sigterm head -l /pgdata/10/data/postmaster.pid
Postmaster负责管理整个系统范围的操作,例如中断等操作,Postmaster本身不进行折现操作,它指派一个子进程在适当的时间去处理它们。同时它要在数据库崩溃的时候重启系统。Postmaster进程在起始时会建立共享内存和信号库,Postmaster及其子进程的通信就通信就通过共享内存和信号来实现。这种多进程设计使得整个系统的稳定性更好,即使某个后台进程崩溃也不会影响系统中其他进程的工作,Postmaster只需要重置共享内存即可从单个后台进程的崩溃中恢复。
Postmaster守护进程执行流程:初始化内存上下文->初始化GUC选项->解析命令行参数并配置参数->设置连接环境->创建监听套接字->注册信号处理函数->启动子系统装载用户认证文件->循环等待用户请求,为每个请求fork后台进程。代码参照PostmasterMain函数(E:\opensource\postgresql-8.4.1\src\backend\postmaster\postmaster.c)
telebdx中的实现顺序:初始化内存上下文、注册信号处理函数、初始化GUC选项、解析命令行参数并配置参数、设置连接环境、创建监听套接字
PostmasterMain函数
Postmaster文件夹下面的代码主要用来创建一个服务器进程Postmaster,用来监听用户的连接请求,并fork子进程来处理客户端请求,此外还包含了用来进行统计、建立系统日志的代码。相应的代码位置在src/backend/postmaster文件夹中。包含以下文件:Postmaster进程源文件postmaster.c、统计数据收集进程的源文件pgstat.c、预写式日志归档进程的源文件pgarch.c、后台写进程的源文件bgwrite.c、系统日志进程的源文件syslogger.c和系统自动清理进程的源文件autovacuum.c。
/* Postmaster main entry point */
int PostmasterMain(int argc, char *argv[]) {
......
case '-':
{
char *name,
*value;
ParseLongOption(optarg, &name, &value);
#ifdef PGXC
/* A Coordinator is being activated */
if (strcmp(name, "coordinator") == 0 &&
!value)
isPGXCCoordinator = true;
else if (strcmp(name, "datanode") == 0 &&
!value)
isPGXCDataNode = true;
else if (strcmp(name, "restoremode") == 0 && !value)
{
/*
* In restore mode both coordinator and datanode
* are internally treeated as datanodes
*/
isRestoreMode = true;
isPGXCDataNode = true;
}
.......
初始化内存上下文
初始化GUC选项
GUC(Grand Unified Configuration)模块实现了多种数据类型(目前boolean、int、float、string四种)的变量配置。GucContext的项定义了参数可能会由不同进程在不同的时机进行配置的选项,系统会根据既定的优先权来确定什么情况下的配置可以生效。共有六种类型(通过枚举类型GucContext定义)**并且只能在合适的环境下进行配置**:
-
PGC_INTERNAL:参数只能通过内部进程设定,用户不能设定
-
PGC_POSTMASTER:参数只能在Postmaster启动时通过读配置文件或处理文件或处理命令行参数来配置
-
PGC_SIGHUP:参数只能在Postmaster启动时配置,或当我们改变了配置文件并发送信号SIGHUP通知Postmaster或Postgres的时候进行配置
-
PGC_BACKEND:参数只能在Postmaster启动时读配置文件设置,或由客户端在进行连接请求时设置。已经启动的后台进程会忽略此类参数的改变
-
PGC_USERSET:可以在任何时候配置
-
PGC_SUSET:参数只能在Postmaster启动时或由超级用户通过SQL语言(SET命令)进行设置
每种数据类型的GUC参数都由两部分组成:共性部分(config_generic)和特性部分。GUCSource枚举类型数据结构用于描述参数的来源,按照优先级从低到高的顺序排列,一个设置起作用当且仅当先前的设置优先级比当前的设置的优先级低或者相等。
Postmaster配置参数的基本过程包括:初始化GUC参数,将参数设置为默认值;配置GUC参数,根据命令行参数配置参数;读取配置文件,读配置文件重新设置参数。
初始化GUC参数
Postmaster将首先调用InitializeGUCOptions函数将参数设置为默认值:
1 /*
2 * Options setup
3 */
4 InitializeGUCOptions();
1))首先调用build_guc_variables函数来统计参数个数并分配相应的config_generic类型的全局指针数组guc_variables以保存每个参数结构体的地址,并且对该数据进行排序。由于参数是通过全局静态数组ConfigureNamesBool、ConfigureNamesInt、ConfigureNamesReal、ConfigureNamesString、ConfigureNamesEnum存储的,因此在build_guc_variables函数中只需要遍历相应的数组,统计参数的个数并将参数结构体中config_generic域的参数vartyoe设置为相应的参数类型。当遍历完所有参数后,根据总的参数个数分配config_generic指针数组guc_vars,然后再次遍历静态参数数组,将每个参数结构的首地址保存到guc_vars数组中(这里分配的数组个数为当前参数总数的1.25倍,主要是为了方便以后参数的扩充)。接着将全局变量guc_variables也指向guc_vars数组。最后通过快速排序法把guc_variables按照参数名进行排序。
1 void InitializeGUCOptions(void) {
2 int i;
3 char *env;
4 long stack_rlimit;
5 /* Before log_line_prefix could possibly receive a nonempty setting, make sure that timezone processing is minimally alive (see elog.c). */
6 pg_timezone_pre_initialize();
7 /* Build sorted array of all GUC variables. */
8 build_guc_variables();
build_guc_variables函数的代码如下所示:
1 void build_guc_variables(void) {
2 int size_vars;
3 int num_vars = 0;
4 struct config_generic **guc_vars;
5 int i;
6 for (i = 0; ConfigureNamesBool[i].gen.name; i++) {
7 struct config_bool *conf = &ConfigureNamesBool[i];
8 /* Rather than requiring vartype to be filled in by hand, do this: */
9 conf->gen.vartype = PGC_BOOL;
10 num_vars++;
11 }
12 ...
13 /* Create table with 20% slack */
14 size_vars = num_vars + num_vars / 4;
15 guc_vars = (struct config_generic **)guc_malloc(FATAL, size_vars * sizeof(struct config_generic *));
16 num_vars = 0;
17 for (i = 0; ConfigureNamesBool[i].gen.name; i++)
18 guc_vars[num_vars++] = &ConfigureNamesBool[i].gen;
19 for (i = 0; ConfigureNamesInt[i].gen.name; i++)
20 guc_vars[num_vars++] = &ConfigureNamesInt[i].gen;
21 for (i = 0; ConfigureNamesReal[i].gen.name; i++)
22 guc_vars[num_vars++] = &ConfigureNamesReal[i].gen;
23 for (i = 0; ConfigureNamesString[i].gen.name; i++)
24 guc_vars[num_vars++] = &ConfigureNamesString[i].gen;
25 for (i = 0; ConfigureNamesEnum[i].gen.name; i++)
26 guc_vars[num_vars++] = &ConfigureNamesEnum[i].gen;
27 if (guc_variables)
28 free(guc_variables);
29 guc_variables = guc_vars;
30 num_guc_variables = num_vars;
31 size_guc_variables = size_vars;
32 qsort((void *) guc_variables, num_guc_variables,
33 sizeof(struct config_generic *), guc_var_compare);
34 }
2)接下来将每个参数设置为默认值。对于guc_variables中的每个参数,initializeGUCOptions函数先将其config_generic域中的status设置为0,将reset_source、tentative_source、source设置为PGC_S_DEFAULT表示默认;stack、sourcefile设置为NULL;然后根据参数值vartype的不同类型分别调用相应的assign_hook函数(如果该参数设置了该函数),assign_hook函数用来设置boot_val,最后将boot_val赋值给reset_val和variable指向的变量,通过这样一系列的步骤就将参数设置为了默认值。
/* Load all variables with their compiled-in defaults, and initialize status fields as needed. */
for (i = 0; i < num_guc_variables; i++){
InitializeOneGUCOption(guc_variables[i]);
}
/*
\* Prevent any attempt to override the transaction modes from
\* non-interactive sources.
*/
SetConfigOption("transaction_isolation", "default",
PGC_POSTMASTER, PGC_S_OVERRIDE);
SetConfigOption("transaction_read_only", "no",
PGC_POSTMASTER, PGC_S_OVERRIDE);
下面截一段代码来说明:根据参数值vartype的不同类型分别调用相应的assign_hook函数(如果该参数设置了该函数),assign_hook函数用来设置boot_val,最后将boot_val赋值给reset_val和variable指向的变量,通过这样一系列的步骤就将参数设置为了默认值。
1 static void InitializeOneGUCOption(struct config_generic * gconf){
2 gconf->status = 0;
3 gconf->reset_source = PGC_S_DEFAULT;
4 gconf->source = PGC_S_DEFAULT;
5 gconf->stack = NULL;
6 gconf->sourcefile = NULL;
7 gconf->sourceline = 0;
8 switch (gconf->vartype)
9 {
10 case PGC_BOOL:
11 {
12 struct config_bool *conf = (struct config_bool *) gconf;
13 if (conf->assign_hook)
14 if (!(*conf->assign_hook) (conf->boot_val, true,PGC_S_DEFAULT))
15 elog(FATAL, "failed to initialize %s to %d",conf->gen.name, (int) conf->boot_val);
16 *conf->variable = conf->reset_val = conf->boot_val;
17 break;
18 }
3)通过系统调用getenv来获得环境变量PGPORT、PGDATESTYLE、PGCLIENTENCODING的值,不为空则调用SetConfigOption函数来设置这三个变量对应的参数的值。
1 /* For historical reasons, some GUC parameters can receive defaults from environment variables. Process those settings. NB: if you add or remove anything here, see also ProcessConfigFile(). */
2 env = getenv("PGPORT");
3 if (env != NULL)
4 SetConfigOption("port", env, PGC_POSTMASTER, PGC_S_ENV_VAR);
5 env = getenv("PGDATESTYLE");
6 if (env != NULL)
7 SetConfigOption("datestyle", env, PGC_POSTMASTER, PGC_S_ENV_VAR);
8 env = getenv("PGCLIENTENCODING");
9 if (env != NULL)
10 SetConfigOption("client_encoding", env, PGC_POSTMASTER, PGC_S_ENV_VAR);
4)最后,检测系统的最大安全栈深度,如果这个深度值大于100KB且不超过2MB,则用它设置max_stack_depth参数。
/* rlimit isn't exactly an "environment variable", but it behaves about the same. If we can identify the platform stack depth rlimit, increase default stack depth setting up to whatever is safe (but at most 2MB). */
stack_rlimit = get_stack_depth_rlimit();
if (stack_rlimit > 0){
int new_limit = (stack_rlimit - STACK_DEPTH_SLOP) / 1024L;
if (new_limit > 100){
char limbuf[16];
new_limit = Min(new_limit, 2048);
sprintf(limbuf, "%d", new_limit);
SetConfigOption("max_stack_depth", limbuf,
PGC_POSTMASTER, PGC_S_ENV_VAR);
}
}
配置GUC参数
如果用户启动Postmaster进程时通过命令行参数指定了一些GUC的参数值,那么Postmaster需要从命令行参数中将这些GUC参数的值解析出来并且设置到相应的GUC参数中。根据命令行设置参数主要是通过getopt和SetConfigOption这两个函数来完成的。对于getopt返回的每一个参数选项及其参数,通过一个switch语句根据参数选项的不同分别调用SetConfigOption函数设置相应的参数。
SetConfigOption函数的第一个参数为参数名;第二个参数为参数值,其值存放在getopt函数返回的optarg字符串中;第三个参数为参数类型;最后一个参数为参数来源。由于在这里Postmaster是在处理命令行参数,所以这里的参数类型和参数来源分别设置为PGC_POSTMASTER何PGC_S_ARGV。SetConfigOption函数是通过set_config_option(const char name, const char value, GucContext context, GucSource source, bool isLocal, bool changeVal)函数来实现的,其中最后2个参数统一设置为false和true。该函数首先从guc_variables指向的参数数组中搜索参数名为name的参数,如果没有找到则出错;否则将找到的参数的结构体中GucContext的值与传过来的参数context比较,判断在当前的上下文中参数是否可以设置,如果不能设置的话就报错,否则再将参数结构体中的GucSource与传过来的参数source进行比较,判断当前操作的优先级是否大于或等于先前的优先级,如果大于或等于先前优先级的话则根据具体参数值的类型将value转化为相应的数据,然后设置参数结构体中的相应数据项即可。
/*
* Parse command-line options. CAUTION: keep this in sync with
* tcop/postgres.c (the option sets should not conflict) and with the
* common help() function in main/main.c.
*/
while ((opt = getopt(argc, argv, "A:B:c:D:d:EeFf:h:ijk:lN:nOo:Pp:r:S:sTt:W:-:")) != -1)
{
switch (opt)
{
case 'A':
SetConfigOption("debug_assertions", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
case 'B':
SetConfigOption("shared_buffers", optarg, PGC_POSTMASTER, PGC_S_ARGV);
break;
读取配置文件
当完成命令行参数的设置之后,接着读配置文件重新配置参数。需要注意的是,在配置文件中设置的参数都不能修改之前通过命令行已经设置的参数,因为其优先级没有通过命令行设置的优先级高。这个过程主要是调用SelectConfigFiles(const char* userDoption, const char* progname)函数来实现的,其中第一个参数是通过命令行设置的用户的数据目录,如果没有设置会通过环境变量PG-DATA找到;第二个参数为程序名,主要用于错误处理。该函数首先在数据目录下找到配置文件,然后调用词法分析程序解析文件。对于解析到的每个参数及其参数值,调用SetConfigOption来完成参数的修改。
1 /* Locate the proper configuration files and data directory, and read postgresql.conf for the first time. */
2 if (!SelectConfigFiles(userDoption, progname))
3 ExitPostmaster(2);
通过上述三个步骤设置完参数后还要检验参数的合法性。比如,数据目录的用户ID应该等于当前进程的有效用户ID、数据目录应该禁止组用户和其他用户的一切访问、缓冲区的数量至少是允许连接的进程数的两倍并且至少为16等。如果一切合法,则将当前目录转入数据目录,然后进行后续的操作。
1 /* Verify that DataDir looks reasonable */
2 checkDataDir();
3 /* And switch working directory into it */
4 ChangeToDataDir();
5 /* Check for invalid combinations of GUC settings. */
6 if (ReservedBackends >= MaxBackends){
7 write_stderr("%s: superuser_reserved_connections must be less than max_connections\n", progname);
8 ExitPostmaster(1);
9 }
10 /* Other one-time internal sanity checks can go here, if they are fast. (Put any slow processing further down, after postmaster.pid creation.)*/
11 if (!CheckDateTokenTables()){
12 write_stderr("%s: invalid datetoken tables, please fix\n", progname);
13 ExitPostmaster(1);
14 }
在创建监听套接字之前,Postmaster需要保证当前只有一个Postmaster在运行且没有任何独立后台进程运行,这是通过CreateDataDirLockFile函数来完成的。该函数通过调用CreateLockFile函数在数据目录中创建锁文件postmaster.pid,每次Postmaster或独立后台进程Postgres启动时都会在数据目录中创建这个独一无二的文件(创建文件的模式中置O_EXCL标志位)。因此可以通过尝试去创建该文件来检测当前是否还有别的Postmaster在运行。若创建成功则说明当前没有其他Postmaster或者Postgres正在运行,写入自己的pid;若创建失败,从文件中取出创建该文件的进程的pid(若其中的pid<0说明有一个独立后台进程Postgres在运行;若pid>0说明有一个Postmaster在运行),然后通过kill系统函数检测该进程是否依然存在,若还存在则当前的启动过程就会退出。还有一种特殊情况,就是先前的Postmaste被强行终止,留下了孤立的后台进程,此时可以通过检查PostgreSQL专用的共享内存段是否依然在使用来判断,若仍然有进程使用则退出。若前面的情况都没有发生,就删除这个文件,然后再次创建该文件并写入当前进程的pid和数据目录并返回。
1 /* Create lockfile for data directory. We want to do this before we try to grab the input sockets, because the data directory interlock is more reliable than the socket-file interlock (thanks to whoever decided to put socket files in /tmp :-(). For the same reason, it's best to grab the TCP socket(s) before the Unix socket.
2 */
3 CreateDataDirLockFile(true);
当确定了只有自己在运行时,还将调用RemovePgTempFiles函数删除PGDATA/base/pgsql_tmp中的临时文件pgsql_tmp*以及非默认表空间中的临时文件。
1 /* Remove old temporary files. At this point there can be no other Postgres processes running in this directory, so this should be safe.*/
2 RemovePgTempFiles();