之前我的一些文章都是在说Postgres的一些查询相关的代码。但是对于Postgres服务端是如何启动,后台进程是如何加载,服务端在哪里以及如何监听客户端的连接都没有一个清晰的逻辑。那么今天我来说说Postgres中的postmaster模块的代码,试着解答这些问题。

在正式讨论之前,我先说一下,代码主要涉及的是postgres源码的src/backend目录下的main,postmaster以及tcop模块。

关于postmaster这个命令,熟悉postgres的一定不会陌生。在Linux上它是postgres命令的一个软连接,而在Windows上,它直接就是postgres命令的别名。因此,话题就转换为:postgres命令的处理细节。而postgres命令,从官方手册上我们可以知道,它是启动后端服务器的命令(当然前提是你要用initdb命令先生成一个database cluster)。无论是是直接使用postgres命令启动还是用pg_ctl命令,其本质都是调用postgres命令来启动数据库的。

下面进入代码。


1.命令的入口处理

命令的入口在src/backend/main/main.c。这个main()函数所做的工作不多:

做一下基本的初始化(主要是调用MemoryContextInit函数启动error和memory management子系统,还有其他的locale设置等);
根据命令行的第一个参数分派不同的函数去处理。

我们用postgres命令来启动一个数据库的时候,虽然参数很多。最简单的是对于"–help, --version"这两个参数的处理。对于这两个参数我们只需要简单的返回一下帮助信息即可返回。对于剩下的参数,我们不急着处理,因为我们首先要根据第一个参数确定的是我们希望database工作在何种模式?

要回答这个问题的话,我们看一下src/backend/main/main.c,在main函数里,有以下代码:

	if (argc > 1 && strcmp(argv[1], "--boot") == 0)
AuxiliaryProcessMain(argc, argv); /* does not return */ --->后端子进程,bootstrap
else if (argc > 1 && strcmp(argv[1], "--describe-config") == 0)
GucInfoMain(); /* does not return */
else if (argc > 1 && strcmp(argv[1], "--single") == 0)
PostgresMain(argc, argv,
NULL, /* no dbname */
strdup(get_user_name_or_exit(progname))); /* does not return */ --->backend进程
else
PostmasterMain(argc, argv); /* does not return */ --->后台主进程

我们来一一分析。

首先是bootstrap("--boot"参数指定)模式。

这个模式大家可能比较陌生,在postgres的官方手册上也没有说明这个参数,这个参数目前只在postgres程序内部调用。这个模式下,数据库工作在启动模式下,此时数据库还不能接受外部的数据库连接,大概就是说此时数据库只是内核启动了,还没有对外部提供访问接口。那么这个模式的用处何在呢?

那就让我们把目光放远一点,看看initdb这个命令吧(代码在src/bin/initdb下)。initdb命令我们很熟悉,这个命令用来初始化一个数据库集群。而在这个过程中,我们知道会建立template1,template0和postgres这个三个初始的数据库。那么问题是,我们这三个数据库是怎么建立的,数据库的表,视图索引之类的是怎么创建的?

答案就是在调用工作在"bootstrap"模式下的postgres命令,启动一个"standalone bootstrap process"。也就是说,以"内核"模式启动postgres服务器,从而进行这一系列的数据库操作。证据何在?我们看看initdb.c:

代码调用栈如下:

main()
->initialize_data_directory()
->bootstrap_template1()

在bootstrap_template1中,有以下代码:

	snprintf(cmd, sizeof(cmd),
"\"%s\" --boot -x1 %s %s %s",
backend_exec,
data_checksums ? "-k" : "",
boot_options, talkargs);

可以说是非常清晰了。

接下来是"--describe-config"参数。

这个参数在官方手册是有定义说明的,我直接抄下来吧:

这个选项会用制表符分隔的COPY格式导出服务器的内部配置变量、描述以及默认值。设计它的目的是用于管理工具。

所以还是打印信息,只不过是打印数据库的内部的配置参数信息的,其实还是蛮实用的。

single("--single"参数指定)模式

这个很简单,选择数据库进入单用户模式。这个模式和正常的启动的差别是你是以单独的用户方式进入数据库的,不会做任何后台处理,例如自动检查点。一般是用来debug用。这里也不细说了。

不指定以上参数的话,我们进入Postmater进程。

到这里,我们终于进入正题,进入PostmasterMain()函数,进入正常启动后端的过程。这个也是我们今天讨论的重点了。


2.postmater的处理

postmaster部分的处理主函数是PostmasterMain()。这个函数的处理内容较多,我们按照步骤来解释:

1.memorycontext的建立与切换(切换到PostmasterContext)

我们知道在main模块里我们建立了顶层上下文TopMemoryContext和其子上下文ErrorContext。此处调用AllocSetContextCreate()函数建立内存上下文PostmasterContext,并调用函数MemoryContextSwitchTo()将当前上下文切换到PostmasterContext。这样如果在Postmaster模块如果出现内存相关的问题,不会影响到其他模块(这也是内存上下文模块引入的原因吧)。

2.信号处理函数(singal handler)的设置

作为一个后端主进程,其拥有很多相关的子进程。我们也知道信号(singal)是进程间通信的一种比较方便的方式。这里Postmaster也利用了这一点,注册了很多信号处理函数来处理信号。有关这部分的信号以及相关的handler我列在下面了:

3.处理GUC和一些命令行参数

这部分就比较常规了。注意这里调用InitializeGUCOptions()函数初始化一些GUC,我们还并不能从config文件中读取,因为我们还没有处理命令行参数(命令行参数可以指定一些GUC参数以及config文件)。然后我们用getopt读取命令行参数,这样以后,我们就可以读取config文件进行GUC的设置和验证参数的合法性了。

4.数据库集群(database cluster)的锁定(lock)

调用CreateDataDirLockFile()函数在数据库集群所在目录创建数据库集群的lock文件postmaster.pid。这样就能保证我们不会对同一个数据库集群"启动两次"。虽然我们也会创建socket lock文件,但是我们还是觉得数据库集群所在的目录更加可信和保险一点。

5.共享库的预加载(process_shared_preload_libraries())

我们喜欢用Postgres的一大原因就是Postgres的丰富的插件,其中很多就是通过共享库来实现的。这里就是调用process_shared_preload_libraries()函数来导入你在shared_preload_libraries参数中指定的共享库的。

6.socket的初始化

这里初始化TCP/IP socket和UNIX socket。初始化UNIX socket会在/tmp下创建socket文件。默认情况下,TCP/IP socket是禁用的,我们可以通过修改配置文件来开启。

7.共享内存和信号量的初始化(reset_shared(PostPortNumber))

这里调用函数reset_shared(PostPortNumber)来处理共享内存和信号量。详细的说,它调用各模块的共享内存的使用量估计函数,计算总共所需的共享内存的量,并申请。详细的我们可以看CreateSharedMemoryAndSemaphores()函数。

8.初始化(并未启动)数据库相关后台进程

调用SysLogger_Start()函数启动syslogger后台子进程;

分别调用pgstat_init()和autovac_init()函数初始化状态收集子进程(stats collection process)和自动清理子进程(autovacuum process)。

9.读取客户端认证的配置文件()

调用load_hba()函数和load_ident()函数读取客户端认证文件pg_hba.conf和ident.conf。

10.启动数据库(StartupDataBase()函数)

做好了上面这些配置和设置,这里终于可以进行数据库的启动操作了。这里我们调用StartupDataBase()函数(其实就是一个宏)来启动数据库集群,这里主要发挥作用的是StartupProcessMain(void)函数,这个函数相当于启动数据库的Main函数。详细的调用栈如下,有兴趣的读者可以看看:

PostmasterMain()
->StartupDataBase()
->StartChildProcess()
->AuxiliaryProcessMain()
->StartupProcessMain(void)
->StartupXLOG()

11.服务端主循环(ServerLoop())

既然数据库终于启动起来了,我们终于可以接受客户端发起的连接请求了,这里的ServerLoop()函数就是一个死循环。循环读取客户端的请求并进行相关处理。

这里有一点说明,我提个问题,是不是进入ServerLoop()之后,我们就真的可以马上接受客户端的连接了呢?或者换句话说,我们到底到什么时候才能接受客户端连接呢?标志是什么呢?

我们看PostmasterMain()函数里面关于上面的10和11的代码如下:

    StartupPID = StartupDataBase();
Assert(StartupPID != 0);
StartupStatus = STARTUP_RUNNING;
pmState = PM_STARTUP; /* Some workers may be scheduled to start now */
maybe_start_bgworker(); status = ServerLoop();

关于上面的代码,我们发现,正是由StartupDataBase()函数启动了数据库,然后在ServerLoop()函数里面接受连接。

但是,我们看到在进入ServerLoop()函数之前,pmState的值还是PM_STARTUP,而只有在PM_RUN状态,数据库才是真正的启动起来了。

我们在看下函数reaper(),这个函数是postmaster函数的一个信号处理函数,当它捕获到Startup进程正常死亡(也就是说,数据库正常启动完毕了)后,会设置pmState为PM_RUN。

因此,我们得到结论:在进入ServerLoop()函数后,只有在reaper函数捕获到Startup的正常死亡并设置pmState为PM_RUN之后,数据库才能真正的意义上接受连接。

接下来,我们再看ServerLoop()里面到底做了什么。

 PostmasterMain()
  |->ServerLoop()
  |->initMasks()
  |->for(;;)
  |->select() <--监听端口
  |->ConnCreate() <--创建connection相关的数据结构
  |->BackendStartup() <--建立后端进程backend process
  |->PostmasterRandom()
  |->fork_process()
  |->InitPostmasterChild()
  |->ClosePostmasterPorts()
  |->BackendInitialize()
  |->ProcessStartupPacket()
  |->BackendRun()
  |->PostgresMain()
|->ConnFree() <--释放connection相关的数据结构

我们再看看关于ServerLoop()中的关于socket和信号处理:


好的,今天就到这里,剩下的会继续讨论Postmaster的其他细节。

Postgres中postmaster代码解析(上)的更多相关文章

  1. Postgres中postmaster代码解析(中)

    今天我们对postmaster的以下细节进行讨论: backend的启动和client的连接请求的认证 客户端取消查询时的处理 接受pg_ctl的shutdown请求进行shutdown处理 2.与前 ...

  2. 捕捉WPF应用程序中XAML代码解析异常

    原文:捕捉WPF应用程序中XAML代码解析异常 由于WPF应用程序中XAML代码在很多时候是运行时加载处理的.比如DynamicResource,但是在编译或者运行的过程中,编写的XAML代码很可能有 ...

  3. Beam Search快速理解及代码解析

    目录 Beam Search快速理解及代码解析(上) Beam Search 贪心搜索 Beam Search Beam Search代码解析 准备初始输入 序列扩展 准备输出 总结 Beam Sea ...

  4. php文件上传代码解析

    php文件上传代码解析 is_uploaded_file()  //函数判断指定的文件是否是通过 HTTP POST 上传的,返回一个布尔值. $_FILES['upfile']['tmp_name' ...

  5. Beam Search快速理解及代码解析(上)

    Beam Search 简单介绍一下在文本生成任务中常用的解码策略Beam Search(集束搜索). 生成式任务相比普通的分类.tagging等NLP任务会复杂不少.在生成的时候,模型的输出是一个时 ...

  6. git:当本地分支中的代码和develop分支上有很多冲突,希望删掉本地分支,重新建立新的分支,怎么解决?

    git:当本地分支中的代码和develop分支上有很多冲突,希望删掉本地分支,重新建立新的分支,但是git中说你需要先解决当前的冲突,这种情况怎么解决 这种情况可以在代码编辑器中点击commit,然后 ...

  7. js上传文件带参数,并且,返回给前台文件路径,解析上传的xml文件,存储到数据库中

    ajaxfileupload.js jQuery.extend({ createUploadIframe: function(id, uri) { //create frame var frameId ...

  8. Python中sort和sorted函数代码解析

    Python中sort和sorted函数代码解析 本文研究的主要是Python中sort和sorted函数的相关内容,具体如下. 一.sort函数 sort函数是序列的内部函数 函数原型: L.sor ...

  9. VBA常用代码解析

    031 删除工作表中的空行 如果需要删除工作表中所有的空行,可以使用下面的代码. Sub DelBlankRow() DimrRow As Long DimLRow As Long Dimi As L ...

随机推荐

  1. 创建Maven项目时提示web.xml is missing and <failOnMissingWebXml> is set to true错误解决方案

    1. 右键点击Deployment Descriptor 2. 选择Generate Deployment Descriptor Stub P.S.下面顺便提一个小技巧: 创建动态web时先右键项目, ...

  2. NancyFX 第八章 内容协商

    在Web框架的范畴内,一切都是基于REST的-- 从返回包含CSS.JavaScript的网页的路由路径,到那些返回JSON数据的URL. 无论你怎么看它,两者都是必须的.我们使用一组URL来呈现UR ...

  3. 使用CMD命令编译和运行Java程序

    对于初学者来说,使用CMD命令(Unix以及类Unix系统采用Termial)来编译和运行Java的好处是让初学者直观地体会到编译(Compile)这一步骤,加深记忆.所谓编译就是将文本文件xxx.j ...

  4. python格式化输出基础知识(2)

    ---恢复内容开始--- 一:请输入名片  (姓名,年龄,职业,爱好)设计名片 name=input('你的名字')age=input('你的年龄')job=input('你的工作')hobbie=i ...

  5. MySQL多数据源笔记5-ShardingJDBC实战

    Sharding-JDBC集分库分表.读写分离.分布式主键.柔性事务和数据治理与一身,提供一站式的解决分布式关系型数据库的解决方案. 从2.x版本开始,Sharding-JDBC正式将包名.Maven ...

  6. MySQL Connector 卸载

    MySQL Connector 安装的时候有时候会遇到很多问题,有时候会卸载失败,导致无法重新安装.测试了网上各种办法,删文件,删注册表,重启,360强行删除都不是很有效.最后发现msizap比较有效 ...

  7. 数据模型LP32 ILP32 LP64 LLP64 ILP64

    各个C++实现所选择采用的基础类型的大小统称为"数据模型".有四种广泛采用的数据模型: 32 位系统: LP32 或 2/4/4(int 为 16-bit,long 和指针为 32 ...

  8. paho.mqtt.c打印日志

    mqtt中自身就带有日志系统Log.h和Log.c,这些日志文件是在客户端调用MQTTClient_create函数是初始化的,MQTTClient_create源码如下: int MQTTClien ...

  9. Vue解析四之注册变量

    判断监听的变量,如果undefined可以用$set来注册一个变量. 另外click可以是表达式,不一定必须是一个方法.

  10. 用js编解码base64

    以下是编码和解码的方法 function Base64() { // private property _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef ...