PostgreSQL启动过程中的那些事七:初始化共享内存和信号三:shmem中初始化clog

pg初始化完shmem,给其加上索引"ShmemIndex"后,接着就在shmem里初始化xlog。然后依次初始化clog、subtrans、twophase、multixact。安排按clog、subtrans、multixact、twophase的顺序写,把twophase放到multixact之后是因为前面三个用了相同的算法和数据结构,连起来写可以加深印象和归类记忆,本来想把初始化clog、subtrans、multixact放到一篇文章里写,因为篇幅太长还是分开了,看的时候这几篇文章可以结合起来看。

pg事务提交日志(CLOG)是相对于事务日志(XLOG)小的独立的内存段。

事务提交日志(CLOG)和事务日志(XLOG)是什么关系呢?无论什么时候,当一个新的事务提交日志(CLOG)页被初始化为0时,会产生一个事务日志(XLOG)记录。在事务中提交或退出事务时的写事务提交日志(CLOG),为事务产生了自己的事务日志(XLOG)记录,且在redo日志上更新相关重放/重做动作;因此pg需要在事务提交日志(CLOG)里做一个没有任何额外附加的和事务日志(XLOG)里相关记录对应的记录。而且,在pg被要求记录提交事务日志(CLOG)前,事务日志(XLOG)保证通过对应的事务提交日志(CLOG)记录已经刷新,因此“在写数据前写事务日志xlog”的WAL原则对于提交已经满足,而且我们不用关心事务退出。所以pg不需要用LSN信息标记事务提交日志(CLOG)页面;pg已经有了足够的同步。

pg用简单最近最少使用算法(SLRU)管理clog、subtrans、multixact的页面缓冲池。

在一般情况下,我们希望写流量发生在最近使用的页面。读流量可能会有较大的页面跨度,但任何情况下相当少的页面数量已经足够了。因此,pg仅使用纯线性查询搜索缓冲区,而没有必要使用哈希表或别的东西。除了永远不会交换出最近页面(因为我们知道最终它会再次命中)外管理算法是LRU。

pg使用一个控制轻量锁(control LWLock)保护共享数据结构,再加上每缓冲页轻量锁(per-buffer LWLocks)为每个缓冲区/页同步IO。检查或修改任何共享状态必须持有控制锁。进程在读入或写出一个缓冲页时只需要持有工作在该该缓冲页上的每缓冲页锁就可以了 ,不需要持有控制锁。 (关于锁到并发控制时再讨论)

除了SimpleLruReadPage_ReadOnly()外“持有控制锁”在所有情况下指的是排他锁。

当在一个缓冲页上初始化I/O时,只要在释放控制锁之前获得每缓冲页锁。在完成I/O后,释放每缓冲页锁,重新获得控制锁,且更新共享状态。(在这儿死锁是不可能的,因为其它进程在同一个缓冲页上做I/O的时候,从不尝试在该缓冲页启动I/O操作。)等待I/O完成,释放控制锁,在共享模式中获得每缓冲页锁,立即释放每缓冲页锁,重新获得控制锁,且再次检查状态(因为没有持有锁的时候可能会发生意外的事情)。

使用缓冲区管理器时,有可能发生其它进程写当前正在被写出的缓冲页。这个通过重置缓冲页的page_dirty标签来处理。

上面综合性讨论了事务提交日志、其和XLOG的关系、相关的缓冲及同步时用的锁等,下来我们看方法调用流程

1先上个图,看一下函数调用过程梗概,中间略过部分细节


初始化clog方法调用流程图

2初始化xlog相关结构

话说main()->…->PostmasterMain()->…->reset_shared()-> CreateSharedMemoryAndSemaphores()->…-> CLOGShmemInit(),初始化提交事务日志(CLOG)相关数据结构ClogCtlData等,用作内存里管理和缓存提交事务日志文件(存放在"data/pg_clog"文件夹里的文件)。

在CLOGShmemInit ()函数里,首先在shmem的哈希表索引"ShmemIndex"上给事务提交日志(CLOG)增加一个HashElement和ShmemIndexEnt(entry),在shmem里根据ClogCtlData等相关结构大小调用ShmemAlloc()分配内存空间,使ShmemIndexEnt的成员location指向该空间,size成员记录该空间大小。

CLOGShmemInit()调用ShmemInitStruct(),在其中调用hash_search()在哈希表索引"ShmemIndex"中查找"CLOG Ctl",如果没有,就在shmemIndex中给"CLOG Ctl"分一个HashElement和ShmemIndexEnt(entry),在其中的Entry中写上"CLOGCtl"。返回ShmemInitStruct(),再调用ShmemAlloc()在共享内存上给"CLOGCtl"相关结构(见下面“XLog相关结构图”)分配空间,设置entry(在这儿及ShmemIndexEnt类型变量)的成员location指向该空间,size成员记录该空间大小,最后返回CLOGShmemInit(),让SlruCtlData *类型全局变量ClogCtl指向SlruCtlData *类型静态全局变量ClogCtlData,ClogCtlData的起始地址就是在shmem里给"CLOGCtl"相关结构分配的内存起始地址,设置其中ClogCtlData结构类型的成员值。相关变量、结构定义和初始化完成后数据结构图在下面。

#define ClogCtl (&ClogCtlData)

static SlruCtlData ClogCtlData;

typedef struct SlruCtlData

{

SlruShared shared;

/*

* This flag tells whether to fsync writes(true for pg_clog, false for

* pg_subtrans).

*/

bool do_fsync;

/*

* Decide which of two page numbers is"older" for truncation purposes. We

* need to use comparison of TransactionIdshere in order to do the right

* thing with wraparound XID arithmetic.

*/

bool (*PagePrecedes)(int, int);

/*

* Dir is set during SimpleLruInit and does notchange thereafter. Since

* it's always the same, it doesn't need to bein shared memory.

*/

char Dir[64];

} SlruCtlData;

typedef SlruCtlData *SlruCtl;

/*

* Shared-memorystate

*/

typedef struct SlruSharedData

{

LWLockId ControlLock;

/* Number of buffers managed by this SLRU structure */

int num_slots;

/*

* Arrays holding info for each bufferslot. Page number is undefined

* when status is EMPTY, as is page_lru_count.

*/

char **page_buffer;

SlruPageStatus*page_status;

bool *page_dirty;

int *page_number;

int *page_lru_count;

LWLockId *buffer_locks;

/*----------

* We mark a page "most recentlyused" by setting

* page_lru_count[slotno]= ++cur_lru_count;

* The oldest page is therefore the one withthe highest value of

* cur_lru_count- page_lru_count[slotno]

* The counts will eventually wrap around, butthis calculation still

* works as long as no page's age exceedsINT_MAX counts.

*----------

*/

int cur_lru_count;

/*

* latest_page_number is the page number of thecurrent end of the log;

* this is not critical data, since we use itonly to avoid swapping out

* the latest page.

*/

int latest_page_number;

} SlruSharedData;

typedef SlruSharedData *SlruShared;

下面看看初始化完"CLOG Ctl"相关结构后在内存中的结构图


初始化完clog的内 存结构图

为了精简上图,把创建shmem的哈希表索引"ShmemIndex"时创建的HCTL结构删掉了,这个结构的作用是记录创建可扩展哈希表的相关信息。增加了左边灰色底的部分,描述共享内存/shmem里各变量物理布局概览,由下往上,由低地址到高地址。其中的"CLOG Ctl"即clog的相关结构图下面分别给出,要不上面的图太大太复杂了。

CLOG相关结构图