共享内存初始化
Postgresql中通过CreateSharedMemoryAndSemaphores()
接口完成共享内存初始化以及共享数据结构的创建。
创建共享内存的主要流程及关键接口如下所示:
-
PGSharedMemoryCreate
:通过操作系统申请内存段 -
InitShmemAccess
:使用全局变量记录共享内存位置 -
InitShmemAllocation
:创建用于全局变量分配使用的锁 -
CreateLWLocks
:创建全局使用的锁结构 -
InitShmemIndex
:在共享内存中创建hash表,该表用来索引共享内存中的数据结构
下面具体介绍各个接口的实现:
PGSharedMemoryCreate
申请内存接口为PGShmemHeader * PGSharedMemoryCreate(Size size, int port, PGShmemHeader **shim)
, 该接口中的参数size表示要申请的共享内存总大小,该size是通过计算所有需要使用共享内存的结构的大小的总和得到,该接口中的关键代码如下:
for (;;)
{
IpcMemoryId shmid;
PGShmemHeader *oldhdr;
IpcMemoryState state;
// 申请指定大小的内存段
memAddress = InternalIpcMemoryCreate(NextShmemSegID, sysvsize);
if (memAddress) // 申请成功则跳出循环
break;
// 循环中后面的部分用于处理申请失败的情况
shmid = shmget(NextShmemSegID, sizeof(PGShmemHeader), 0);
if (shmid < 0)
{
oldhdr = NULL;
state = SHMSTATE_FOREIGN;
}
else
state = PGSharedMemoryAttach(shmid, NULL, &oldhdr);
switch (state)
{
case SHMSTATE_ANALYSIS_FAILURE:
case SHMSTATE_ATTACHED:
ereport(FATAL,
(errcode(ERRCODE_LOCK_FILE_EXISTS),
errmsg("pre-existing shared memory block (key %lu, ID %lu) is still in use",
(unsigned long) NextShmemSegID,
(unsigned long) shmid),
errhint("Terminate any old server processes associated with data directory \"%s\".",
DataDir)));
break;
case SHMSTATE_ENOENT:
/*
* To our surprise, some other process deleted since our last
* InternalIpcMemoryCreate(). Moments earlier, we would have
* seen SHMSTATE_FOREIGN. Try that same ID again.
*/
elog(LOG,
"shared memory block (key %lu, ID %lu) deleted during startup",
(unsigned long) NextShmemSegID,
(unsigned long) shmid);
break;
case SHMSTATE_FOREIGN:
NextShmemSegID++;
break;
case SHMSTATE_UNATTACHED:
/*
* The segment pertains to DataDir, and every process that had
* used it has died or detached. Zap it, if possible, and any
* associated dynamic shared memory segments, as well. This
* shouldn't fail, but if it does, assume the segment belongs
* to someone else after all, and try the next candidate.
* Otherwise, try again to create the segment. That may fail
* if some other process creates the same shmem key before we
* do, in which case we'll try the next key.
*/
if (oldhdr->dsm_control != 0)
dsm_cleanup_using_control_segment(oldhdr->dsm_control);
if (shmctl(shmid, IPC_RMID, NULL) < 0)
NextShmemSegID++;
break;
}
if (oldhdr && shmdt(oldhdr) < 0)
elog(LOG, "shmdt(%p) failed: %m", oldhdr);
}
// 初始化已经申请到的共享内存使用PGShmemHeader结构初始化内存头部
hdr = (PGShmemHeader *) memAddress;
hdr->creatorPID = getpid();
hdr->magic = PGShmemMagic;
hdr->dsm_control = 0;
/* Fill in the data directory ID info, too */
if (stat(DataDir, &statbuf) < 0)
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not stat data directory \"%s\": %m",
DataDir)));
hdr->device = statbuf.st_dev;
hdr->inode = statbuf.st_ino;
/*
* 在头部写入段大小,freeoffset指向段头之后的第一个位置,这里使用了保证字节对齐的宏
*/
hdr->totalsize = size;
hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
*shim = hdr; // 因为需要把指针的地址传递出去,因此此处使用指针的指针
/* 使用全局变量记录,以便将来使用 */
UsedShmemSegAddr = memAddress;
UsedShmemSegID = (unsigned long) NextShmemSegID;
以上代码逻辑非常简单:使用操作系统提供的共享内存申请机制进行内存申请,在InternalIpcMemoryCreate
中通过shmget
,shmat
实现(具体介绍可参考https://www.cnblogs.com/52php/p/5861372.html)。申请到内存之后,对共享内存头结构进行初始化,主要填充一些基本信息如空间总大小,创建该共享内存的进程id等,其中freeoffset指针指向头部之后的第一个可用空间,其示意图如下所示:
InitShmemAccess
该接口实现简单,使用全局变量对共享内存进行记录,ShmemSegHdr
,ShmemBase
记录内存起始地址,ShmemEnd
记录结束地址。这几个变量在今后创建子线程时需要使用
void
InitShmemAccess(void *seghdr)
{
PGShmemHeader *shmhdr = (PGShmemHeader *) seghdr;
ShmemSegHdr = shmhdr;
ShmemBase = (void *) shmhdr;
ShmemEnd = (char *) ShmemBase + shmhdr->totalsize;
}
InitShmemAllocation
该接口最重要的作用是完成共享内存spinlock初始化,该锁的作用是为了保证对共享内存的申请操作是互斥的,也就是说至此之后便可以开始对共享内存进行申请了。该锁结构(实际就是一个char)紧跟在共享内存头之后,初始化时被置为0。随后完成第一次内存申请,使用接口ShmemAlloc
,该接口通过ShmemBase
访问共享内存,从freeoffset指向位置开始向后计算申请空间,然后返回空间起始地址,同时修改freeoffset指针。
在InitShmemAllocation
中调用ShmemAlloc
为ShmemVariableCache
申请空间,申请完成后,共享内存结构变化示意图如下:
ShmemVariableCache = (VariableCache)
ShmemAlloc(sizeof(*ShmemVariableCache));
memset(ShmemVariableCache, 0, sizeof(*ShmemVariableCache));
CreateLWLocks
该接口申请LWlocks并完成初始化,每一个锁结构如下:
typedef struct LWLock
{
slock_t mutex; /* Protects LWLock and queue of PGPROCs */
bool releaseOK; /* T if ok to release waiters */
char exclusive; /* # of exclusive holders (0 or 1) */
int shared; /* # of shared holders (0..MaxBackends) */
int tranche; /* tranche ID */
struct PGPROC *head; /* head of list of waiting PGPROCs */
struct PGPROC *tail; /* tail of list of waiting PGPROCs */
/* tail is undefined when head is NULL */
} LWLock;
申请流程如下:
- 计算所需要的锁数量:
NumLWLocks()
- 计算所需要的共享内存空间大小:
LWLockShmemSize()
- 申请共享内存空间,并按顺序对锁进行逐个初始化:
LWLockInitialize
- 预留三个int大小的空间用于动态分配锁的计数器
初始化接口LWLockInitialize
如下,完成对结构体的基本赋值
void
LWLockInitialize(LWLock *lock, int tranche_id)
{
SpinLockInit(&lock->mutex);
lock->releaseOK = true; // 所空闲
lock->exclusive = 0;// 独占模式
lock->shared = 0;// 共享模式
lock->tranche = tranche_id;
lock->head = NULL;
lock->tail = NULL;
}
创建完成后共享内存结构示意图如下所示:
InitShmemIndex
创建hash表,该hash表用来记录已经申请共享内存空间的结构,实现快速检索同时避免重复申请。创建hash表的过程在以后介绍。此处创建hash表后,ShmemIndex
记录下hash表的头指针,以后每次创建共享内存数据结构时,都会率先在hash表中检索。
此时已完成共享内存数据结构的基本初始化,其结构示意图如下所示:
至此往后则可以根据具体的需要进行共享内存的申请了,postgresql在接口CreateSharedMemoryAndSemaphores
中在完成内存初始化后,依次调用以下接口完成不同功能的共享内存申请工作,针对具体接口的功能,日后再慢慢介绍吧。
/*
* Set up xlog, clog, and buffers
*/
XLOGShmemInit();
CLOGShmemInit();
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
/*
* Set up lock manager
*/
InitLocks();
/*
* Set up predicate lock manager
*/
InitPredicateLocks();
/*
* Set up process table
*/
if (!IsUnderPostmaster)
InitProcGlobal();
CreateSharedProcArray();
CreateSharedBackendStatus();
TwoPhaseShmemInit();
BackgroundWorkerShmemInit();
/*
* Set up shared-inval messaging
*/
CreateSharedInvalidationState();
/*
* Set up interprocess signaling mechanisms
*/
PMSignalShmemInit();
ProcSignalShmemInit();
CheckpointerShmemInit();
AutoVacuumShmemInit();
ReplicationSlotsShmemInit();
WalSndShmemInit();
WalRcvShmemInit();
/*
* Set up other modules that need some shared memory space
*/
BTreeShmemInit();
SyncScanShmemInit();
AsyncShmemInit();