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();
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容