PG守護進程(Postmaster)——初始化GUC配置參數


  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)

PostmasterMain函數

  Postmaster文件夾下面的代碼主要用來創建一個服務器進程Postmaster,用來監聽用戶的連接請求,並fork子進程來處理客戶端請求,此外還包含了用來進行統計、建立系統日志的代碼。相應的代碼位置在src/backend/postmaster文件夾中。包含以下文件:Postmaster進程源文件postmaster.c、統計數據收集進程的源文件pgstat.c、預寫式日志歸檔進程的源文件pgarch.c、后台寫進程的源文件bgwrite.c、系統日志進程的源文件syslogger.c和系統自動清理進程的源文件autovacuum.c。

 1 /* Postmaster main entry point */
 2 int PostmasterMain(int argc, char *argv[]) {
 3     int            opt;
 4     int            status;
 5     char       *userDoption = NULL;
 6     int            i;
 7     MyProcPid = PostmasterPid = getpid();
 8     MyStartTime = time(NULL);
 9     IsPostmasterEnvironment = true;
10     /* for security, no dir or file created can be group or other accessible */
11     umask((mode_t) 0077);
12     /* Fire up essential subsystems: memory management */
13     MemoryContextInit();
14     /* By default, palloc() requests in the postmaster will be allocated in the PostmasterContext, which is space that can be recycled by backends. Allocated data that needs to be available to backends should be allocated in TopMemoryContext. */
15     PostmasterContext =AllocSetContextCreate(TopMemoryContext,"Postmaster",
16                                               ALLOCSET_DEFAULT_MINSIZE,
17                                               ALLOCSET_DEFAULT_INITSIZE,
18                                               ALLOCSET_DEFAULT_MAXSIZE);
19     MemoryContextSwitchTo(PostmasterContext);
20     /* Initialize paths to installation files */
21     getInstallationPaths(argv[0]);

初始化內存上下文

初始化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指向的變量,通過這樣一系列的步驟就將參數設置為了默認值。

1     /* Load all variables with their compiled-in defaults, and initialize status fields as needed. */
2     for (i = 0; i < num_guc_variables; i++){
3         InitializeOneGUCOption(guc_variables[i]);
4     }

/*
* 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參數中,這一部分代碼在Postmaster.c文件的509-674行中。根據命令行設置參數主要是通過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();

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM