• Postgresql源码(67)LWLock锁的内存结构与初始化


    相关:
    《Postgresql源码(40)Latch的原理分析和应用场景》
    《Postgresql源码(67)LWLock锁的内存结构与初始化》
    《Postgresql源码(68)virtualxid锁的原理和应用场景》

    速查:

    • 每一把LWLock都有名字和ID;可能有多把LWLock锁共享一个名字和ID;名字和ID是一一对应的。
    • 固定锁有208个但锁名只有72个,一些锁共享名字和ID,也可以称作一批锁:tranche。
    • 动态锁需要在申请共享内存前注册,会和固定锁一起初始化。

    全局速查变量:

    • MainLWLockArray锁结构的紧凑数组,共享内存初始化统一申请。
    • NamedLWLockTrancheArray紧凑数组,保存{id,name}结构,共享内存初始化统一申请。
    • LWLockTrancheNames指针数组,每个元素是name的指针。从0开始查询,便于查询动态锁定名。按动态锁个数单独申请在TopMemoryContext。
    • 内存结构:
      • 第一部分:动态锁ID计数器(用于分配动态锁ID)。
      • 第二部分:MainLWLockArray。
      • 第三部分:动态锁ID名称映射。
      • 第四部分:动态锁名称字符串。

    共享内存顶部的int4字节对齐到128字节上,剩下了124字节;且这个变量不常改变,不会造成cacheline miss,这124字节可以用来存点别的:)

    1 前言

    Postgresql的LWLock体系整体有两部分组成:

    • FIXED LWLock:PG内部预定好的锁。
    • NAMED LWLock:由RequestNamedLWLockTranche动态注册到共享内存中的,一般给插件使用。(插件的init函数会在共享内存初始化前运行,执行RequestNamedLWLockTranche动态注册LWLock)(main->PostmasterMain->process_shared_preload_libraries->load_libraries->load_file->internal_load_library->_PG_init->RequestNamedLWLockTranche

    所有的锁有名字,PG中叫做TrancheNames。

    • FIXED LWLock的名字有PG预定义在数组中。
    • NAMED LWLock的名字由RequestNamedLWLockTranche函数注册是添入。

    2 固定锁(FIXED LWLock)

    以LWLockShmemSize函数为线索,总结下PG到底有多少锁。

    PG14

    /*
     * Compute shmem space needed for LWLocks and named tranches.
     */
    Size
    LWLockShmemSize(void)
    {
    	Size		size;
    	int			i;
    	int			numLocks = NUM_FIXED_LWLOCKS;
    
    	/* Calculate total number of locks needed in the main array. */
    	numLocks += NumLWLocksForNamedTranches();
    
    	/* Space for the LWLock array. */
    	size = mul_size(numLocks, sizeof(LWLockPadded));
    
    	/* Space for dynamic allocation counter, plus room for alignment. */
    	size = add_size(size, sizeof(int) + LWLOCK_PADDED_SIZE);
    
    	/* space for named tranches. */
    	size = add_size(size, mul_size(NamedLWLockTrancheRequests, sizeof(NamedLWLockTranche)));
    
    	/* space for name of each tranche. */
    	for (i = 0; i < NamedLWLockTrancheRequests; i++)
    		size = add_size(size, strlen(NamedLWLockTrancheRequestArray[i].tranche_name) + 1);
    
    	/* Disallow adding any more named tranches. */
    	lock_named_request_allowed = false;
    
    	return size;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    固定锁包含什么?

    固定锁总数NUM_FIXED_LWLOCKS=208个,包含下面几类锁。

    #define NUM_BUFFER_PARTITIONS  128
    
    /* Number of partitions the shared lock tables are divided into */
    #define LOG2_NUM_LOCK_PARTITIONS  4
    #define NUM_LOCK_PARTITIONS  (1 << LOG2_NUM_LOCK_PARTITIONS)
    
    /* Number of partitions the shared predicate lock tables are divided into */
    #define LOG2_NUM_PREDICATELOCK_PARTITIONS  4
    #define NUM_PREDICATELOCK_PARTITIONS  (1 << LOG2_NUM_PREDICATELOCK_PARTITIONS)
    
    /* Offsets for various chunks of preallocated lwlocks. */
    #define BUFFER_MAPPING_LWLOCK_OFFSET	NUM_INDIVIDUAL_LWLOCKS
    #define LOCK_MANAGER_LWLOCK_OFFSET		\
    	(BUFFER_MAPPING_LWLOCK_OFFSET + NUM_BUFFER_PARTITIONS)
    #define PREDICATELOCK_MANAGER_LWLOCK_OFFSET \
    	(LOCK_MANAGER_LWLOCK_OFFSET + NUM_LOCK_PARTITIONS)
    #define NUM_FIXED_LWLOCKS \
    	(PREDICATELOCK_MANAGER_LWLOCK_OFFSET + NUM_PREDICATELOCK_PARTITIONS)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    可读性太差,总结下:
    在这里插入图片描述

    3 动态锁(NAMED LWLock)

    调用API动态申请的锁,一般是给插件使用,因为插件的init函数会在PG主进程共享内存初始化前调用。如果已经走完共享内存初始化的流程,在申请锁就没有效果了。

    API

    • RequestNamedLWLockTranche:【注册登记动态锁】共享内存初始化前,调用该函数把锁信息记录下来。注意可以这里可以注册多个内存连续的LWLock。共享内存初始化时InitializeLWLocks会把登记的锁和fixed锁一块初始化。
    • GetNamedLWLockTranche:【根据注册名字拿锁】通过锁名字查找LWLock数组。如果注册了多个锁,这里返回第一把锁的指针,多把锁是内存连续存放的。
    • NumLWLocksForNamedTranches:返回总锁个数。

    关键变量

    • NamedLWLockTrancheRequests:申请Named动态锁的次数。
    • NamedLWLockTrancheRequestArrayNamedLWLockTrancheRequest类型数组,每一次申请对应一个NamedLWLockTrancheRequestNamedLWLockTrancheRequest结构包括:
      • char tranche_name[]:锁名(可能不止一把锁,所以也可能是一批锁的名字)。
      • int num_lwlocks:本次申请锁的个数。

    4 统一初始化

    两套锁机制初始化时是要放在一起初始化的。

    4.1 LWLock内存结构

    • 内存结构第一部分都是用于动态锁的维护,记录了动态锁的数量。

    • 内存结构第二部分是锁的控制结构,208把固定锁+动态锁,每一把锁对应一个LWLockPadded结构。

    • 内存结构第三、四部分都是用于动态锁的维护,记录了动态锁的id和名字的关联、名称字符串。

    • LWLockCounter用来给动态锁赋ID,初始=72原因是固定锁虽然有208个,但只有72个名字,同名的锁会使用相同ID。

    |--------------------------------------------------|   <----- LWLockCounter(初始=72因为固定锁有72个名字,每次动态锁需要ID,+1使用)
    | (一个int对齐LWLOCK_PADDED_SIZE,记录动态申请锁数量) |   (对齐到128字节,int只有4字节,浪费124字节,int放在靠近高内存的位置)
    |--------------------------------------------------|    <----- MainLWLockArray
    |                                                  |
    | (固定锁208 + 动态锁数量) * sizeof(LWLockPadded)   |
    |                                                  |
    |--------------------------------------------------|    <----- NamedLWLockTrancheArray
    |                                                  |  
    | (动态申请锁数量) * sizeof(NamedLWLockTranche)     | NamedLWLockTranche{int trancheId, char *trancheName}
    |                                                  | 注意:trancheName字符串只有指针,空间下面申请。
    |--------------------------------------------------|
    |                                                  |
    | (遍历数组拿到所有动态申请锁的名字的总字符数)           | 申请空间,给上面使用。
    |                                                  |
    |--------------------------------------------------|
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    4.2 初始化

    初始化位置

    main
      PostmasterMain
        reset_shared
          CreateSharedMemoryAndSemaphores
            CreateLWLocks
              InitializeLWLocks     // 初始化所有锁,包括fixed lwlock 和 动态注册的named lwlock
              LWLockRegisterTranche // 注册named lwlock到统一命名体系
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    初始化函数:InitializeLWLocks

    • 初始化过程分为两步:初始化固定锁、初始化动态锁。

      • 固定锁都可以用MainLWLockArray索引到,找到后按顺序按批次给ID用LWLockInitialize初始化。
      • 动态锁不能用MainLWLockArray索引到,用内存偏移寻找并初始化,找到后按LWLockNewTrancheId算ID调用LWLockInitialize初始化。
        • LWLockNewTrancheId使用共享内存顶部的LWLockCounter,每次+1使用。
        • 固定锁208把,共有72个名字,有一些锁共享名字,也就是共享ID。
    • 初始化时会给每个锁结构LWLock添加tranche_id信息,同时将锁状态置为release。

    • 初始化最后会为4.1中的内存结构中第三、四部分添加上数据,用NamedLWLockTrancheArray指向。

    • 为了方便使用,在用LWLockTrancheNames数组,记录tranchid到tranche_name的映射。

    static void
    InitializeLWLocks(void)
    {
    	int			numNamedLocks = NumLWLocksForNamedTranches();
    	int			id;
    	int			i;
    	int			j;
    	LWLockPadded *lock;
    
    	/* Initialize all individual LWLocks in main array */
    	for (id = 0, lock = MainLWLockArray; id < NUM_INDIVIDUAL_LWLOCKS; id++, lock++)
    		LWLockInitialize(&lock->lock, id);
    
      // 共享名字、ID
    	/* Initialize buffer mapping LWLocks in main array */
    	lock = MainLWLockArray + BUFFER_MAPPING_LWLOCK_OFFSET;
    	for (id = 0; id < NUM_BUFFER_PARTITIONS; id++, lock++)
    		LWLockInitialize(&lock->lock, LWTRANCHE_BUFFER_MAPPING);
      
      // 共享名字、ID
    	/* Initialize lmgrs' LWLocks in main array */
    	lock = MainLWLockArray + LOCK_MANAGER_LWLOCK_OFFSET;
    	for (id = 0; id < NUM_LOCK_PARTITIONS; id++, lock++)
    		LWLockInitialize(&lock->lock, LWTRANCHE_LOCK_MANAGER);
    
    	/* Initialize predicate lmgrs' LWLocks in main array */
    	lock = MainLWLockArray + PREDICATELOCK_MANAGER_LWLOCK_OFFSET;
    	for (id = 0; id < NUM_PREDICATELOCK_PARTITIONS; id++, lock++)
    		LWLockInitialize(&lock->lock, LWTRANCHE_PREDICATE_LOCK_MANAGER);
    
    	/*
    	 * Copy the info about any named tranches into shared memory (so that
    	 * other processes can see it), and initialize the requested LWLocks.
    	 */
    	if (NamedLWLockTrancheRequests > 0)
    	{
    		char	   *trancheNames;
    
    		NamedLWLockTrancheArray = (NamedLWLockTranche *)
    			&MainLWLockArray[NUM_FIXED_LWLOCKS + numNamedLocks];
    
    		trancheNames = (char *) NamedLWLockTrancheArray +
    			(NamedLWLockTrancheRequests * sizeof(NamedLWLockTranche));
    		lock = &MainLWLockArray[NUM_FIXED_LWLOCKS];
    
    		for (i = 0; i < NamedLWLockTrancheRequests; i++)
    		{
    			NamedLWLockTrancheRequest *request;
    			NamedLWLockTranche *tranche;
    			char	   *name;
    
    			request = &NamedLWLockTrancheRequestArray[i];
    			tranche = &NamedLWLockTrancheArray[i];
    
    			name = trancheNames;
    			trancheNames += strlen(request->tranche_name) + 1;
    			strcpy(name, request->tranche_name);
    			tranche->trancheId = LWLockNewTrancheId();
    			tranche->trancheName = name;
    
    			for (j = 0; j < request->num_lwlocks; j++, lock++)
    				LWLockInitialize(&lock->lock, tranche->trancheId);
    		}
    	}
    }
    
    /* 初始化时会给每个锁结构添加tranche_id */
    void
    LWLockInitialize(LWLock *lock, int tranche_id)
    {
    	pg_atomic_init_u32(&lock->state, LW_FLAG_RELEASE_OK);
    	lock->tranche = tranche_id;
    	proclist_init(&lock->waiters);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    5 用ID查询锁名

    固定锁共72个名字,保存在两个数组中:IndividualLWLockNames、BuiltinTrancheNames

    • 前48个名字在IndividualLWLockNames中。

    • 后24个名字在BuiltinTrancheNames中。

    使用72以内的ID查询,会使用两个数组直接返回字符串。

    使用72以上的ID查询,返回LWLockTrancheNames对应的动态锁名,查不到就返回extension。

    static const char *
    GetLWTrancheName(uint16 trancheId)
    {
    	/* Individual LWLock? */
    	if (trancheId < NUM_INDIVIDUAL_LWLOCKS)
    		return IndividualLWLockNames[trancheId];
    
    	/* Built-in tranche? */
    	if (trancheId < LWTRANCHE_FIRST_USER_DEFINED)
    		return BuiltinTrancheNames[trancheId - NUM_INDIVIDUAL_LWLOCKS];
    
    	/*
    	 * It's an extension tranche, so look in LWLockTrancheNames[].  However,
    	 * it's possible that the tranche has never been registered in the current
    	 * process, in which case give up and return "extension".
    	 */
    	trancheId -= LWTRANCHE_FIRST_USER_DEFINED;
    
    	if (trancheId >= LWLockTrancheNamesAllocated ||
    		LWLockTrancheNames[trancheId] == NULL)
    		return "extension";
    
    	return LWLockTrancheNames[trancheId];
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
  • 相关阅读:
    CANoe-Trace窗口介绍
    用户身份标识与账号体系实践
    华为---OSPF协议优先级、开销(cost)、定时器简介及示例配置
    圆周率介绍
    开发微信网页及调试方法
    管理项目的人——日常行为
    Java之set集合的详细解析
    Python和Java哪个更好找工作?
    Kotlin编程实战——类与对象(05)
    国产操作系统生态建设,小程序技术来帮忙
  • 原文地址:https://blog.csdn.net/jackgo73/article/details/126162691