1. main
这是整个postgres
进程的程序入口。
- 首先在
startup_hacks
函数中初始化全局的自旋锁dummy_spinlock
- 调用
save_ps_display_args
函数,使用saved_argv
保存初始的argv
地址,然后将argv的内容拷贝到新申请的内存中,并让argv指向新的内存。这样做是因为postgres(master)
进程在创建backend
进程时,为了让用户通过ps
命令可以看到不同的进程信息(包含:用户名、数据库、客户端信息等),需要将这些信息填写到原始的argv
处,以便ps
命令能够正确显示。同时因为在填入这些信息时,会破坏argv
原有的内容,而很多系统的environ
是紧挨着argv
的,这样有可能导致环境变量被破坏,因此这个函数里回分配一块新的内存,将environ
的内容拷贝过去,然后将environ
指向新内存。 - 调用
MemoryContextInit
函数创建并初始化TopMemoryContext
和ErrorContext
这两个内存管理器 - 进行
locale
设置 - 检查是否
root
用户启动,如果是就退出 - 单进程模式(调试用)调用
PostgresMain
,多进程模式调用PostmasterMain
2. PostmasterMain
这是多进程postgres(master)
进程的程序入口。
- 取进程id保存到
MyProcPid
和PostmasterPid
中,取当前时间保存到MyStartTime
中 - 设置
IsPostmasterEnvironment
为true
,表示是在多进程模式中 - 创建
PostmasterContext
内存管理器,并换为当前内存管理器 - 取当前进程二进制文件全路径保存到
my_exec_path
,取库文件全路径保存到pkglib_path
,并检查库文件路径是否能正确打开 - 设置信号处理方式:屏蔽或设置处理函数
- 调用
InitializeGUCOptions
完成配置项的初始化,主要是加载默认配置项,然后从环境变量加载配置项(详见[2.1])。 - 处理命令行启动参数,设置配置项
- 调用
SelectConfigFiles
函数加载配置文件,设置数据库的主数据目录。 - 调用
checkDataDir
检查主数据目录是否可访问,以及目录下的PG_VERSION
文件中记录的版本号是否正确 - 当前工作目录切换到主数据目录
- 调用
CheckDateTokenTables
对两个时间日期格式的保留字表进行正确性校验 - 调用
CreateDataDirLockFile
在主数据目录下创建postmaster.pid
,并在里面记录下:pid、主数据目录、启动时间和监听端口等信息。然后将该文件加入到全局的链表lock_files
中,这个列表中的文件在进程退出时会被清理 - 调用
ApplyLauncherRegister
注册1个后台worker
。这里只是指定这个worker
的主入口函数、访问权限、启动时机等信息,然后加入到一个就绪列表中,等待后续合适时机再启动进程。这里注册的worker
入口函数是ApplyLauncherMain
。看注释应该是注册了一个负责逻辑复制的work
- 调用
process_shared_preload_libraries
加载配置文件中指定(参数:shared_preload_libraries
)需要加载的插件so
- 调用
InitializeMaxBackends
初始化全局变量MaxBackends
,表示系统信息的进程数。该值 = 客户端最大连接数 + 自动vacuum进程数 + 最大后台进程数 + 1 - 注册
atexit
回调函数:CloseServerPorts
。这个函数主要就是关闭监听的所有tcp
端口(记录在ListenSocket
数组中),回收所有的unix socket
(记录在sock_paths
列表中) - 绑定配置中指定的所有
TCP
监听服务地址(ip:port),并将这些监听socket
放到全局的ListenSocket
数组中(步骤16中注册的回调函数会释放这些socket
)。同时会把监听的第一个host
记录到postmaster.pid
的第6行 - 绑定配置中指定的所有
unix socket
目录,在这些目录下创建unix socket
句柄以及对应的lock
文件句柄,并将其lock
文件加入到sock_paths
列表中(步骤16中注册的回调函数会释放这些unix socket
)。同时会把创建的第一个unix socket
记录到postmaster.pid
的第5行 - 调用
reset_shared
函数创建共享内存和信号量。该函数直接调用CreateSharedMemoryAndSemaphores
函数(详见[2.2]) - 调用
set_max_safe_fds
函数计算每个backend
进程最大能打开的文件句柄数,保持在全局变量:max_safe_fds
- 调用
set_stack_base
设置全局变量stack_base_ptr
,这个是值取该函数第1个函数局部变量的指针值,也就是set_stack_base
这个函数的栈起始地址。后面可以用这个值配合检查函数的栈起始地址是否偏移过多,也就是限制调用栈的大小 - 调用
InitPostmasterDeathWatchHandle
函数创建匿名管道,读写句柄保持在全局数组:postmaster_alive_fds
中。postgres(master)
负责写,backend
进程负责监听读,用于postgres(master)
在退出时通知唤醒backend
进程 - 调用
CreateOptsFile
函数创建$DataDir/postmaster.opts
文件,并将执行本进程的命令行保存在其中 - 调用
RemovePgTempFiles
函数删除$DataDir/base/pgsql_tmp
,$DataDir/base
和$DataDir/pg_tablspc
下的临时文件和关系对象 - 调用
RemovePromoteSignalFiles
函数删除$DataDir/promote
和$DataDir/fallback_promote
文件,猜测这两个文件用于将从实例提升为主 - 删除用于保存当前日志文件名的
$DataDir/current_logfiles
文件 - 调用
pgstat_init
函数创建一个用于收发统计信息的udp端口 - 调用
load_hba
函数,读取加载$DataDir/pg_hba.conf
文件,初始化用于客户端连接权限控制的全局对象parsed_hba_lines
列表,和用于保存这个列表的MemoryContext
对象:parsed_hba_context
- 调用
load_ident
函数,读取加载$DataDir/pg_ident.conf
文件,初始化用于维护操作系统用户到数据库用户映射关系的全局对象parsed_ident_lines
列表,和用于保存这个列表的MemoryContext
对象:parsed_ident_context
- 获取当前时间保存到全局对象:
PgStartTime
- 在
$DataDir/postmaster.pid
文件的第8行添加字符串starting
,主要是为了让pg_ctl
进程能正确看到该进程的运行状况 - 调用
maybe_start_bgworkers
根据情况启动一些后台进程 - 调用
ServerLoop
启动postgres(master)
进程的主循环:通过select
处理客户端请求,如果有新的连接请求,则调用BackendStartup
创建子进程。此外在处理完客户端连接请求后,还会检查所有的后台进程,如果发现有异常退出的后台进程,就尝试拉起
2.1 InitializeGUCOptions
这是初始化用户配置的主函数。
- 在
pg_timezone_initialize
函数中初始化时区 - 调用
build_guc_variables
函数,将所有配置项放到一个大数组guc_variables
中,并按配置项的字符串值排序 - 循环调用
InitializeOneGUCOption
函数初始化guc_variables
数组中的所有配置项,主要是加载默认值 - 设置
transaction_isolation
、transaction_read_only
和transaction_deferrable
三个配置项 - 调用
InitializeGUCOptionsFromEnvironment
函数,从环境变量中获取配置值进行设置
2.2 CreateSharedMemoryAndSemaphores
这是创建共享内存和信号量的主函数。
- 首先判断不能是
backend
进程,因为这种情况下,共享内存应该已经存在,只需要attach
上去 - 计算需要的信号量个数 =
MaxBackends
+ 辅助进程数(NUM_AUXILIARY_PROCS
) - 根据共享内存需要存储的内存,估算其大小
- 调用
PGSharedMemoryCreate
创建存放PGShmemHeader
的共享内存- 调用
CreateAnonymousSegment
创建匿名的共享内存,因为是匿名的,所以应该就是这个进程自己使用,有什么作用目前还没看到- 如果设置使用
huge page
,则从/proc/meminfo
中获取huge page
的大小,然后将共享内存的大小扩大到huge page
的整数倍,然后使用MAP_HUGETLB
标志位调用mmap
,这样就是使用了huge page
- 如果使用
huge page
失败或者设置就是不使用,则正常调用mmap
- 如果设置使用
- 将匿名的共享内存地址记录到
AnonymousShmem
,其大小记录到AnonymousShmemSize
- 注册
AnonymousShmemDetach
函数在on_shmem_exit_list
中,以便postgres(master)
退出时调用munmap
释放共享内存 - 调用
InternalIpcMemoryCreate
创建大小为sizeof(PGShmemHeader)
的共享内存- 调用
shmget
分配共享内存 - 注册
IpcMemoryDelete
函数在on_shmem_exit_list
中,以便进程退出时释放共享内存 - 调用
shmat
绑定共享内存 - 注册
IpcMemoryDetach
函数在on_shmem_exit_list
中,以便进程退出时解绑内存 - 将共享内存的
key
和id
记录到postmaster.pid
的第7行
- 调用
- 将内存映射到
PGShmemHeader
对象指针,进行复制,初始化如下字段:- creatorPID:当前的进程id
- magic:一个固定的整数值
- dsm_control:共享内存的id号,在
postgres(master)
中为0 - device:主数据目录的
dev
- inode:主数据目录的
inode
- totalsize:匿名共享内存的大小
- freeoffset:剩余空闲空间的偏移量,这里其值为
sizeof(PGShmemHeader)
- 将该共享内存的地址保存到
UsedShmemSegAddr
,将key保存到UsedShmemSegID
- 将共享内存的内容拷贝到匿名的共享内存
AnonymousShmem
中,返回AnonymousShmem
(这里为什么要同时有个AnonymousShmem
和UsedShmemSegAddr
,现在还搞不清楚)
- 调用
- 调用
InitShmemAccess
初始化3个全局变量:- ShmemSegHdr:指向上面分配的
AnonymousShmem
,表示PGShmemHeader
对象 - ShmemBase:数值上等于
ShmemSegHdr
,表示起始地址 - ShmemEnd:
AnonymousShmem
的结束地址
- ShmemSegHdr:指向上面分配的
- 调用
PGReserveSemaphores
从共享内存中预留信号量对象数组的空间。其实就是在ShmemBase
的共享内存空闲处(ShmemSegHdr->freeoffset
)预留出足够的空间,并更新ShmemSegHdr->freeoffset
。同时也会初始化3个全局变量:- numSems:已经实际创建的信号量,这里为:0
- maxSems:最大能创建的信号量,这里就是前面计算的结果
- nextSemKey:下一个信号量的key,从绑定端口号计算得到,避免不同实例冲突
- 如果是
postgres(master)
,则调用InitShmemAllocation
初始化共享内存分配机制- 从共享内存中分配1个全局自旋锁对象
ShmemLock
,并初始化。后面从共享内存中申请空间会调用ShmemAlloc
(之前都是ShmemAllocUnlocked
),此函数会对ShmemLock
加锁 - 从共享内存中分配1个全局的
ShmemVariableCache
对象,这个对象主要保存了维护OID
和XID
需要的一些变量
- 从共享内存中分配1个全局自旋锁对象
- 调用
CreateLWLocks
创建LWLock
数组,如果是postgres(master)
,要完成下面全部操作,如果是backend
进程,只需要最后一步RegisterLWLockTranches
- 首先预估需要的内存大小,然后从共享内存中分配足够的空间
- 将全局变量
MainLWLockArray
指向LWLock
数组起始地址 - 调用
InitializeLWLocks
初始化LWLock
数组 - 调用
RegisterLWLockTranches
对固定的LWLock
对象进行注册,主要是分配1个全局字符串数组LWLockTrancheArray
,数组大小为LWLockTranchesAllocated
(该数组的下标就是某类LWLock
对象的TrancheID
,而其字符串就是这个对象的名字,所以实际上表示LWLock
对象的TrancheID
到名字的映射关系)。然后注册TranchID
为64以下的固定LWLock
对象的名字
- 调用
InitShmemIndex
在共享内存中创建1个hash表,将ShmemSegHdr->index
以及全局对象ShmemIndex
指向它,后续接口绝大部分内存对象都是由这个hash表来管理 - 调用
XLOGShmemInit
函数初始化pg_control
对象和XLOG
对象 - 调用
CLOGShmemInit
函数初始化CLOG
相关对象 - 调用
CommitTsShmemInit
函数初始化CommitTs
相关对象 - 调用
SUBTRANSShmemInit
函数初始化SubTrans
相关对象 - 调用
MultiXactShmemInit
函数初始化MultiXact
相关对象 - 调用
InitBufferPool
初始化共享内存池 - 调用
InitLocks
初始化锁管理对象 - 调用
InitPredicateLocks
初始化predicate锁管理对象 - 调用
InitProcGlobal
初始化全局进程表 - 调用
CreateSharedProcArray
函数初始化ProcArray
相关对象 - 调用
CreateSharedBackendStatus
函数初始化BackendStatusArray
相关对象 - 调用
TwoPhaseShmemInit
函数初始化TwoPhaseState
相关对象 - 调用
BackgroundWorkerShmemInit
函数初始化BackgroundWorkerData
相关对象 - 调用
CreateSharedInvalidationState
函数初始化shmInvalBuffer
相关对象 - 调用
PMSignalShmemInit
函数初始化PMSignalState
相关对象 - 调用
ProcSignalShmemInit
函数初始化ProcSignalSlots
相关对象 - 调用
CheckpointerShmemInit
函数初始化CheckpointerShmem
相关对象 - 调用
AutoVacuumShmemInit
函数初始化AutoVacuumShmem
相关对象 - 调用
ReplicationSlotsShmemInit
函数初始化ReplicationSlotCtl
相关对象 - 调用
ReplicationOriginShmemInit
函数初始化replication_states_ctl
相关对象 - 调用
WalSndShmemInit
函数初始化WalSndCtl
相关对象 - 调用
WalRcvShmemInit
函数初始化WalRcv
相关对象 - 调用
ApplyLauncherShmemInit
函数初始化LogicalRepCtx
相关对象 - 调用
SnapMgrInit
函数初始化oldSnapshotControl
相关对象 - 调用
BTreeShmemInit
函数初始化btvacinfo
相关对象 - 调用
SyncScanShmemInit
函数初始化scan_locations
相关对象 - 调用
AsyncShmemInit
函数初始化asyncQueueControl
和AsyncCtl
相关对象 - 调用
BackendRandomShmemInit
函数初始化TwoPhaseState
相关对象 - 调用
dsm_postmaster_startup
创建共享内存对象dsm_control
,映射到文件/dev/shm/PostgreSQL.<handleID>
,这个HandleID
是个随机数,保存在UsedShmemSegAddr->dsm_control
字段