Postgresql源码赏析(3)--共享内存

共享内存初始化

Postgresql中通过CreateSharedMemoryAndSemaphores()接口完成共享内存初始化以及共享数据结构的创建。
创建共享内存的主要流程及关键接口如下所示:

  1. PGSharedMemoryCreate:通过操作系统申请内存段
  2. InitShmemAccess:使用全局变量记录共享内存位置
  3. InitShmemAllocation:创建用于全局变量分配使用的锁
  4. CreateLWLocks:创建全局使用的锁结构
  5. 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中通过shmgetshmat实现(具体介绍可参考https://www.cnblogs.com/52php/p/5861372.html)。申请到内存之后,对共享内存头结构进行初始化,主要填充一些基本信息如空间总大小,创建该共享内存的进程id等,其中freeoffset指针指向头部之后的第一个可用空间,其示意图如下所示:

共享内存头

InitShmemAccess

该接口实现简单,使用全局变量对共享内存进行记录,ShmemSegHdrShmemBase记录内存起始地址,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中调用ShmemAllocShmemVariableCache申请空间,申请完成后,共享内存结构变化示意图如下:

    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;

申请流程如下:

  1. 计算所需要的锁数量:NumLWLocks()
  2. 计算所需要的共享内存空间大小:LWLockShmemSize()
  3. 申请共享内存空间,并按顺序对锁进行逐个初始化:LWLockInitialize
  4. 预留三个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();

你可能感兴趣的:(Postgresql源码赏析(3)--共享内存)