前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Postgresql源码(67)LWLock锁的内存结构与初始化

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

作者头像
mingjie
发布2022-09-19 15:01:14
6040
发布2022-09-19 15:01:14
举报
文章被收录于专栏:Postgresql源码分析

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

速查:

  • 每一把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

代码语言:javascript
复制
/*
 * 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;
}

固定锁包含什么?

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

代码语言:javascript
复制
#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)

可读性太差,总结下:

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。
代码语言:javascript
复制
|--------------------------------------------------|   <----- 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字符串只有指针,空间下面申请。
|--------------------------------------------------|
|                                                  |
| (遍历数组拿到所有动态申请锁的名字的总字符数)           | 申请空间,给上面使用。
|                                                  |
|--------------------------------------------------|

4.2 初始化

初始化位置

代码语言:javascript
复制
main
  PostmasterMain
    reset_shared
      CreateSharedMemoryAndSemaphores
        CreateLWLocks
          InitializeLWLocks     // 初始化所有锁,包括fixed lwlock 和 动态注册的named lwlock
          LWLockRegisterTranche // 注册named lwlock到统一命名体系

初始化函数:InitializeLWLocks

  • 初始化过程分为两步:初始化固定锁、初始化动态锁。
代码语言:txt
复制
- 固定锁都可以用MainLWLockArray索引到,找到后按顺序按批次给ID用LWLockInitialize初始化。
- 动态锁不能用MainLWLockArray索引到,用内存偏移寻找并初始化,找到后按LWLockNewTrancheId算ID调用LWLockInitialize初始化。      
    - LWLockNewTrancheId使用共享内存顶部的LWLockCounter,每次+1使用。
    - 固定锁208把,共有72个名字,有一些锁共享名字,也就是共享ID。初始化时会给每个锁结构LWLock添加tranche_id信息,同时将锁状态置为release。
  • 初始化最后会为4.1中的内存结构中第三、四部分添加上数据,用NamedLWLockTrancheArray指向。
  • 为了方便使用,在用LWLockTrancheNames数组,记录tranchid到tranche_name的映射。
代码语言:javascript
复制
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);
}

5 用ID查询锁名

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

  • 前48个名字在IndividualLWLockNames中。
  • 后24个名字在BuiltinTrancheNames中。

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

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

代码语言:javascript
复制
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];
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-08-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1 前言
  • 2 固定锁(FIXED LWLock)
    • 固定锁包含什么?
    • 3 动态锁(NAMED LWLock)
      • API
        • 关键变量
        • 4 统一初始化
          • 4.1 LWLock内存结构
            • 4.2 初始化
            • 5 用ID查询锁名
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档