PostgreSQL dsm 动态共享内存管理机制

  • 源码版本: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 相关的初始化,主要逻辑如下:

  1. 计算 dsm control 结构所需要的内存大小,该值与 MaxBackends 大小有关
  2. 调用 dsm_impl_op() 函数创建用于 dsm control 结构的 dsm 共享内存,赋值给 dsm_control 变量,该变量的类型为 dsm_control_header
  3. 初始化 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 的清理。

主库逻辑:

  1. 遍历 dsm_control->nitems 数组,对其中正在使用的 item 对应的 dsm 内存进行销毁
  2. 销毁 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

文章评论

0条评论