从POSTGRESQL V12 后添加了一个关于内存的参数 shared_memory_type,这个参数在之前的版本是没有的并且这个参数的值为 mmap。

PostgreSQL数据库共享内存——参数shared_memory_type和huge page支持_#endif


MMAP 对于我们的POSTGRESQL的shared buffer的内存使用有什么帮助,首先我们就需要了解MMAP到底是什么。MMAP 是一种内存的映射文件的方法,我们都知道,我们的数据文件是要映射到内存中进行数据处理的,而数据在处理完毕就是脏页是需要在会写到磁盘上对应的文件块中的。

那么使用这样的方式的好处是什么,mmap对于文件和内存之间的关系是通过文件磁盘地址和进程虚拟地址空间中的一段虚拟地址的映射关系,完成内存和文件之间的数据处理。显然这样的好处在于,通过这样的映射关系,进程对内存的操作通过指针来完成,不会再需要通过系统的 read, write函数在将内存的数据写入到磁盘环境中。而更多的好处在于内核空间对这样的区域的修改也可以反馈在用户的空间中,从而实现不同进程间的文件共享。实际上我们是可以通过LINUX系统中关于PID的MAPS 来看到你当前的进程的mmap 的状态。

PostgreSQL数据库共享内存——参数shared_memory_type和huge page支持_postgresql_02

内存参数shared_memory_type

PostgreSQL数据库共享内存——参数shared_memory_type和huge page支持_共享内存_03

POSIX 共享内存函数

CreateAnonymousSegment函数

CreateAnonymousSegment函数创建匿名mmap共享内存段,先检查是否支持巨页,如果huge_pages为HUGE_PAGES_ON或HUGE_PAGES_TRY,先使用巨页来创建匿名共享内存段。

static void *CreateAnonymousSegment(Size *size) {
Size allocsize = *size;
void *ptr = MAP_FAILED;
int mmap_errno = 0;
#ifndef MAP_HUGETLB
/* PGSharedMemoryCreate should have dealt with this case */
Assert(huge_pages != HUGE_PAGES_ON);
#else
if (huge_pages == HUGE_PAGES_ON || huge_pages == HUGE_PAGES_TRY) { /* Round up the request size to a suitable large value. */
Size hugepagesize;
int mmap_flags;
GetHugePageSize(&hugepagesize, &mmap_flags);
if (allocsize % hugepagesize != 0)
allocsize += hugepagesize - (allocsize % hugepagesize);
ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE, PG_MMAP_FLAGS | mmap_flags, -1, 0);
mmap_errno = errno;
if (huge_pages == HUGE_PAGES_TRY && ptr == MAP_FAILED)
elog(DEBUG1, "mmap(%zu) with MAP_HUGETLB failed, huge pages disabled: %m", allocsize);
}
#endif

不支持巨页支持

if (ptr == MAP_FAILED && huge_pages != HUGE_PAGES_ON) {
/* Use the original size, not the rounded-up value, when falling back to non-huge pages. */
allocsize = *size;
ptr = mmap(NULL, allocsize, PROT_READ | PROT_WRITE, PG_MMAP_FLAGS, -1, 0);
mmap_errno = errno;
}
if (ptr == MAP_FAILED) {
errno = mmap_errno;
ereport(FATAL, (errmsg("could not map anonymous shared memory: %m"), (mmap_errno == ENOMEM) ? errhint("This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages. To reduce the request size (currently %zu bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing shared_buffers or max_connections.", *size) : 0));
}
*size = allocsize;
return ptr;
}

AnonymousShmemDetach函数

static void AnonymousShmemDetach(int status, Datum arg) {
/* Release anonymous shared memory block, if any. */
if (AnonymousShmem != NULL) {
if (munmap(AnonymousShmem, AnonymousShmemSize) < 0)
elog(LOG, "munmap(%p, %zu) failed: %m", AnonymousShmem, AnonymousShmemSize);
AnonymousShmem = NULL;
}
}

system v 共享内存函数

InternalIpcMemoryCreate函数通过指定的key尝试创建新的共享内存段,如果内存段存在则调用失败返回NULL;如果成功,将内存段attach到当前进程,然后返回其attached地址,最后将回调函数注册到on_shmem_exit,当detach或delete该共享内存时,on_shmem_exit会调用该回调函数。

typedef key_t IpcMemoryKey;    /* shared memory key passed to shmget(2) */
typedef int IpcMemoryId; /* shared memory ID returned by shmget(2) */

static void *InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size) {
IpcMemoryId shmid;
void *requestedAddress = NULL;
void *memAddress;
#ifdef EXEC_BACKEND
{
char *pg_shmem_addr = getenv("PG_SHMEM_ADDR");
if (pg_shmem_addr)
requestedAddress = (void *) strtoul(pg_shmem_addr, NULL, 0);
}
#endif

shmid = shmget(memKey, size, IPC_CREAT | IPC_EXCL | IPCProtection);
if (shmid < 0) {
int shmget_errno = errno;
/* Fail quietly if error indicates a collision with existing segment. One would expect EEXIST, given that we said IPC_EXCL, but perhaps we could get a permission violation instead? Also, EIDRM might occur if an old seg is slated for destruction but not gone yet. */
if (shmget_errno == EEXIST || shmget_errno == EACCES
#ifdef EIDRM
|| shmget_errno == EIDRM
#endif
)
return NULL;
if (shmget_errno == EINVAL){
shmid = shmget(memKey, 0, IPC_CREAT | IPC_EXCL | IPCProtection);
if (shmid < 0){
/* As above, fail quietly if we verify a collision */
if (errno == EEXIST || errno == EACCES
#ifdef EIDRM
|| errno == EIDRM
#endif
)
return NULL;
/* Otherwise, fall through to report the original error */
}else{
/* On most platforms we cannot get here because SHMMIN is greater than zero. However, if we do succeed in creating a zero-size segment, free it and then fall through to report the original error. */
if (shmctl(shmid, IPC_RMID, NULL) < 0)
elog(LOG, "shmctl(%d, %d, 0) failed: %m", (int) shmid, IPC_RMID);
}
}

/* Else complain and abort. */
errno = shmget_errno;
ereport(FATAL,(errmsg("could not create shared memory segment: %m"),errdetail("Failed system call was shmget(key=%lu, size=%zu, 0%o).",(unsigned long) memKey, size,IPC_CREAT | IPC_EXCL | IPCProtection),(shmget_errno == EINVAL) ? errhint("This error usually means that PostgreSQL's request for a shared memory segment exceeded your kernel's SHMMAX parameter, or possibly that it is less than your kernel's SHMMIN parameter.\nThe PostgreSQL documentation contains more information about shared memory configuration.") : 0, (shmget_errno == ENOMEM) ? errhint("This error usually means that PostgreSQL's request for a shared memory segment exceeded your kernel's SHMALL parameter. You might need to reconfigure the kernel with larger SHMALL.\nThe PostgreSQL documentation contains more information about shared memory configuration.") : 0, (shmget_errno == ENOSPC) ? errhint("This error does *not* mean that you have run out of disk space. It occurs either if all available shared memory IDs have been taken, in which case you need to raise the SHMMNI parameter in your kernel, or because the system's overall limit for shared memory has been reached.\nThe PostgreSQL documentation contains more information about shared memory configuration.") : 0));
}

/* Register on-exit routine to delete the new segment */
on_shmem_exit(IpcMemoryDelete, Int32GetDatum(shmid));
/* OK, should be able to attach to the segment */
memAddress = shmat(shmid, requestedAddress, PG_SHMAT_FLAGS);
if (memAddress == (void *) -1)
elog(FATAL, "shmat(id=%d, addr=%p, flags=0x%x) failed: %m", shmid, requestedAddress, PG_SHMAT_FLAGS);
/* Register on-exit routine to detach new segment before deleting */
on_shmem_exit(IpcMemoryDetach, PointerGetDatum(memAddress));

/* Store shmem key and ID in data directory lockfile. Format to try to keep it the same length always (trailing junk in the lockfile won't hurt, but might confuse humans). */
{
char line[64];
sprintf(line, "%9lu %9lu", (unsigned long) memKey, (unsigned long) shmid);
AddToDataDirLockFile(LOCK_FILE_LINE_SHMEM_KEY, line);
}

return memAddress;
}