- 源码版本:PG 13.3
- 源码文件:dsm.c dsm_impl.c
PostgreSQL 是基于进程模型的数据库内核实现,进程之间的通信、数据传输通常需要借助共享内存实现。在程序运行过程中,比如并发任务需要创建多个工作进程,工作进程与 backend 进程之间的通信、数据传输需要通过动态创建的共享内存实现。在 PG 源码中,dsm (dynamic shared memory)模块就是动态共享内存,实现了共享内存的动态申请,动态释放。
dsm 内存实际上就是通过一个 handle 与一块内存进行映射,handle 是一个 unsigned int 的 32 位整数。创建 dsm 时指定一个 handle,访问 dsm 时也是通过这个 handle 进行访问。不同进程之间通过传递 handle 来告诉对方 dsm 内存在哪里,其他进程通过 handle 进行 dsm 的访问。
1. 初始化 dsm
在 postmaster 主进程启动时,调用 dsm_postmaster_startup() 函数,进行 dsm 相关的初始化,主要逻辑如下:
- 计算 dsm control 结构所需要的内存大小,该值与 MaxBackends 大小有关
- 调用 dsm_impl_op() 函数创建用于 dsm control 结构的 dsm 共享内存,赋值给 dsm_control 变量,该变量的类型为 dsm_control_header
- 初始化 maxitems 和 nitems,maxitems 表示 PG 所有进程能够申请的 dsm 最大数量,nitems 表示当前已使用的数量,每一次 dsm 申请,对应一个 dsm_control_item
dsm_control_header 结构如下:
typedef struct dsm_control_header
{
uint32 magic;
uint32 nitems;
uint32 maxitems;
dsm_control_item item[FLEXIBLE_ARRAY_MEMBER];
} dsm_control_header;
dsm_control_item 结构如下:
typedef struct dsm_control_item
{
dsm_handle handle;
uint32 refcnt; /* 2+ = active, 1 = moribund, 0 = gone */
void *impl_private_pm_handle; /* only needed on Windows */
bool pinned;
} dsm_control_item;
函数调用关系如下:
main()
PostmasterMain()
reset_shared()
CreateSharedMemoryAndSemaphores()
dsm_postmaster_startup()
2. 清理 dsm
在 postmaster 主进程关闭时,会调用 dsm_postmaster_shutdown() 函数进行 dsm 的清理。
主库逻辑:
- 遍历 dsm_control->nitems 数组,对其中正在使用的 item 对应的 dsm 内存进行销毁
- 销毁 dsm_control 自身对应的 dsm 内存
清理操作在 shmem exit 时通过回调函数进行调用,参考
dsm_postmaster_startup()
{
...
on_shmem_exit(dsm_postmaster_shutdown, PointerGetDatum(shim));
...
}
3. dsm 使用流程
在 pg 13.3 的源码中,一个使用 dsm 的场景是并行工作进程,其主要逻辑如下:
- 执行器在初始化并行执行计划时,调用 dsm_create() 创建 dsm 共享内存。创建工作进程时,将 dsm 对应的 handle 作为参数传递给 worker 进程,调用关系如下:
ExecGather()
ExecInitParallelPlan()
InitializeParallelDSM()
dsm_create()
LaunchParallelWorkers()
worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(pcxt->seg));
- 子进程(worker进程)调用 dsm_attach() 函数挂载到共享内存,子进程执行结束时,调用 dsm_detach() 函数解除挂载的 dsm 共享内存,调用关系如下:
ParallelWorkerMain()
seg = dsm_attach(DatumGetUInt32(main_arg));
ParallelQueryMain(seg)
area = dsa_attach_in_place(area_space, seg);
dsa_detach(area)
dsm_detach(area->segment_maps[i].segment);
4. dsm 主要函数
4.1 dsm_create()
dsm_create(),该函数有两个参数,要申请的共享内存大小 size 和 flag,返回 dsm_segment 类型的指针。主要逻辑是调用 dsm_impl_op()函数,传入 DSM_OP_CREATE 参数创建一个新的共享内存,创建成功后在 dsm_control->item 数组中找到一个可用的 slot 记录该共享内存。
4.2 dsm_attach()
dsm_attach(),参数为 dsm_handle,一般 A 进程创建了一个共享内存,把其 dsm_handle 传递给 B 进程,B 进程调用 dsm_attach() 函数进行共享内存的挂载。主要逻辑是根据 dsm_handle 在 dsm_control->item 数组中找到对应的 item,将其引用计数加 1,调用 dsm_impl_op() 函数,传入 DSM_OP_ATTACH 参数进行共享内存的挂载。该函数不能重复调用,即不支持重复挂载。
4.3 dsm_detach()
dsm_detach(),参数为 dsm_segment 指针,用于对共享内存解除挂载。主要逻辑是调用dsm_impl_op()函数,传入 DSM_OP_DETACH 参数对共享内存解除挂载,dsm_control->item 数组中对应的 item 其引用计数减 1,根据引用计数判断如果没有其他进程挂载该共享内存,则调用 dsm_impl_op() 函数,传入 DSM_OP_DESTROY 参数进行共享内存的销毁。
4.4 dsm_pin_mapping()
dsm_pin_mapping() 函数,让 dsm 生命周期变成 session 级的, seg->resowner 指向资源拥有者,比如当前事务结束,会调用 ResourceOwnerRelease() 函数释放相关的资源,如果seg->resowner 不为空,那么 seg 对应的 dsm 也会被释放。如果设置 seg->resowner 为 NULL,事务结束释放资源时不会解除 dsm 挂载,直到 session 结束。
CommitTransaction()
ResourceOwnerRelease()
ResourceOwnerReleaseInternal()
dsm_detach()
session 结束时自动释放 dsm 逻辑:
proc_exit()
proc_exit_prepare()
shmem_exit()
dsm_backend_shutdown()
dsm_detach()
4.5 dsm_unpin_mapping()
与 dsm_pin_mapping() 函数相反,让 dsm 生命周期变成非 session 级的,即当前的 CurrentResourceOwner 操作结束就会解除 dsm 的挂载。主要逻辑就是设置 seg->resowner 为 CurrentResourceOwner,把 seg 加到当前 CurrentResourceOwner 的资源列表中去。
4.6 dsm_pin_segment()
dsm_pin_segment()函数,设置 dsm_control->item[seg->control_slot].pinned 为 true,同时引用计数加 1。
4.7 dsm_unpin_segment()
dsm_unpin_segment() 函数,将 dsm_control->item[seg->control_slot].pinned 设置为 false,引用计数减 1,如果引用计数为 1 时(即没有其他进程挂载),销毁该共享内存。
4.8 on_dsm_detach()
on_dsm_detach()函数,注册回调函数,在执行 dsm_detach()函数时执行回调函数。
5. 动态共享内存 dsm 底层实现
dsm 最终通过调用 dsm_impl_op() 函数实现动态共享内存的创建,挂载,解除挂载和销毁操作,操作类型对应的枚举类型如下:
typedef enum
{
DSM_OP_CREATE,
DSM_OP_ATTACH,
DSM_OP_DETACH,
DSM_OP_DESTROY
} dsm_op;
不同的操作系统内核支持的共享内存实现方式不同,PG 的 dsm 支持如下四种动态共享内存实现方法,如下:
- posix
- sysv
- mmap
- windows
5.1 dsm posix 实现方式
dsm posix 主要逻辑在 dsm_impl_posix() 函数中实现,共享内存对应的文件名称为 /PostgreSQL.${handle}
- DSM_OP_CREATE 对应 shm_open(),ftruncate(),posix_fallocate(),mmap()
- DSM_OP_ATTACH 对应 shm_open(),mmap()
- DSM_OP_DETACH 对应 munmap()
- DSM_OP_DESTROY 对应 munmap() 和 shm_unlink()
5.2 dsm sysv 实现方式
dsm sysv 主要逻辑在 dsm_impl_sysv() 函数中实现,handle 转成 key_t 类型,作为 shmget() 函数的参数。
- DSM_OP_CREATE 对应 shmget(),shmat()
- DSM_OP_ATTACH 对应 shmget(),shmctl(),shmat()
- DSM_OP_DETACH 对应 shmget(),shmdt()
- DSM_OP_DESTROY 对应 shmget(),shmdt() 和 shmctl()
5.3 dsm mmap 实现方式
dsm mmap 主要逻辑在 dsm_impl_mmap() 函数中实现,共享内存对应的文件名称为 pg_dynshmem/mmap.${handle}
- DSM_OP_CREATE 对应 open(),write(),mmap(),close()
- DSM_OP_ATTACH 对应 open(),mmap(),close()
- DSM_OP_DETACH 对应 munmap()
- DSM_OP_DESTROY 对应 munmap(),unlink()
5.4 dsm windows 实现方式
dsm windows 主要逻辑在 dsm_impl_windows() 函数中实现,共享内存对应的文件名称为 Global/PostgreSQL.${handle}
- DSM_OP_CREATE 对应 CreateFileMapping(),MapViewOfFile(),VirtualQuery()
- DSM_OP_ATTACH 对应 OpenFileMapping(),MapViewOfFile(),VirtualQuery()
- DSM_OP_DETACH 对应 UnmapViewOfFile(),CloseHandle()
- DSM_OP_DESTROY 对应 UnmapViewOfFile(),CloseHandle()
6. dsm 相关参数
- dynamic_shared_memory_type,动态共享内存的类型,可选值 posix、sysv、mmap、windows