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字段