引入虛擬檔案描述符機制的痛點:單個行程可以輕易擁有超過系統限制的打開檔案數
虛擬檔案描述符機制的原理概述:VFD作為LRU池管理檔案描述符,并根據需要打開和關閉實際需要的OS檔案描述符,

代碼決議
fd.c是PG后端代碼中存盤管理器中的一部分,此代碼管理“虛擬”檔案描述符('virtual' file descriptors, VFD)的快取,服務器出于各種原因打開許多檔案描述符,包括基表(base tables),暫存檔案(scratch files,例如,排序和哈希spool檔案),以及對諸如system(3)之類的C庫例程的隨機呼叫;單個行程可以輕易擁有超過系統限制的打開檔案數(open files), (在許多現代作業系統上,系統限制約為256,但在其他作業系統上,則可能低至32,) VFD作為LRU池進行管理,并根據需要打開和關閉實際需要的OS檔案描述符,顯然,如果使用這些介面,則所有后續操作也必須通過這些介面進行操作(檔案型別不是真實的檔案描述符,the File type is not a real file descriptor),為了使該方案起作用,整個PG資料庫服務器中的大多數(如果不是全部)例程都應使用這些介面,而不是自己呼叫C庫例程(例如,open(2)和fopen(3)),否則,我們可能仍然會發現自己缺少真實的檔案描述符,
注:該檔案過去包含一堆東西來支持RAID級別0(jbod),1(雙工 duplex)和5(異或奇偶校驗 xor parity),這些東西在本版本中全部洗掉了,因為呼叫它的并行查詢處理代碼全部洗掉了,如果您確實需要它,可以從原始POSTGRES源代碼獲取,
以下的頭檔案都在include檔案夾中的相應檔案中,而不是和源檔案處于同一檔案夾中,
#include "postgres.h"
#include "miscadmin.h"
#include "access/xact.h"
#include "catalog/pg_tablespace.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "utils/guc.h"

檔案描述符LRU池之外遺留的檔案描述符相關
#define NUM_RESERVED_FDS 10
我們必須為system(),動態加載程式dynamic loader和其他嘗試打開檔案而不使用fd.c提供的函式保留一些檔案描述符, (雖然我們可以確定我們不會獲得EMFILE,但由于任何其他行程消耗FD的緣故,我們無法保證不會獲得ENFILE,因此,嘗試不使用fd.c來打開檔案是一個壞主意, 我們無法控制所有代碼)因為這只是一個固定的設定,所以我們假設沒有這樣的代碼可以使FD長期處于開放open狀態,特別要注意的是,我們期望加載共享庫不會導致打開檔案數量的任何永久增加, (截至2004年2月,對于大多數(如果不是全部)平臺,這似乎都是正確的,)
#define FD_MINFREE 10 如果少于最小可用FD,阻塞
int max_files_per_process = 1000; 平臺限制單個行程打開的檔案數量
當許多行程執行相同的操作時,許多平臺允許單個行程打開的檔案數量超出其實際支持的數量,通過該GUC引數,DBA可以將max_safe_fds限制為小于postmaster的初始建議的作業范圍,
檔案描述符LRU池
檔案描述符LRU池--> 最大fd數量
static int max_safe_fds = 32; /* default if not changed */
為VFD條目或AllocateFile / AllocateDir操作打開的檔案描述符的最大數量, 它被初始化為一個保守值,并在引導程式bootstrap或獨立后端standalone-backend情況下保持該值, 在正常的postmaster操作中,postmaster在初始化后期呼叫set_max_safe_fds()來更新該值,然后該值由派生子行程繼承,注意:設定此變數時會考慮max_files_per_process的值,因此無需單獨測驗,
static int nfile = 0;
由VFD使用的檔案描述符fd的數量
LRU池使用陣列實作的,陣列元素是vfd結構體虛擬檔案描述符陣列的指標和大小, 陣列大小會根據需要增長, “檔案File”值是該陣列的索引,請注意,VfdCache[0]不是可用的VFD,而只是串列頭,
static Vfd *VfdCache;
static Size SizeVfdCache = 0;

LRU池的底層雖然是陣列,但是通過lruMoreRecently和lruLessRecently成員變數定位下一個vfd元素,也就是就是環形佇列,( typedef int File )這里的File被定義為Int型別,也就是陣列索引,

VfdCache[0]可以看成最近最少使用和最近最多使用集于一身,當使用lruMoreRecently成員進行尋址時,它就是最近最少使用,可以通過它找到真正的最近最少使用的vfd,同樣可以通過lruLessRecently成員進行尋址,找到真正最近最多使用的vfd,VfdCache[0]的fd成員永遠為VFD_CLOSED(#define VFD_CLOSED (-1) 當該VFD結構體無效時,fd值會被設定為-1),除了VfdCache[0],只有真正打開的vfd(賦值了真正的檔案描述符FD)才能放入環形佇列,
- 打開的vfd的fd成員賦值了真正的檔案描述符FD
- 虛打開("virtually" open)的vfd的fileName成員為非空
和檔案操作相關的識別符號:seekPos、fileMode
相關操作函式
* Delete - delete a file from the Lru ring
* LruDelete - remove a file from the Lru ring and close its FD
* Insert - put a file at the front of the Lru ring
* LruInsert - put a file at the front of the Lru ring and open it
* ReleaseLruFile - Release an fd by closing the last entry in the Lru ring
* AllocateVfd - grab a free (or new) file record (from VfdArray)
* FreeVfd - free a file record
Insert:將索引file的Vfd結構體插入環形佇列的front,該Vfd是lru中MostRecently(最近使用)的,主要重點在將新Vfd插入環形佇列的front位置時,更新VfdCache[0]、新Vfd、舊VfdCache[front]的lruMoreRecently成員和lruLessRecently成員的關系,

LruInsert:將索引file的Vfd結構體插入環形佇列,成功回傳0,重新打開失敗回傳-1,并設定errno,
static int
LruInsert(File file)
{
Vfd *vfdP;
Assert(file != 0);
DO_DB(elog(LOG, "LruInsert %d (%s)",
file, VfdCache[file].fileName));
vfdP = &VfdCache[file];
if (FileIsNotOpen(file))
{
while (nfile + numAllocatedDescs >= max_safe_fds)
{
if (!ReleaseLruFile())
break;
}
/*
* The open could still fail for lack of file descriptors, eg due to
* overall system file table being full. So, be prepared to release
* another FD if necessary...
*/
vfdP->fd = BasicOpenFile(vfdP->fileName, vfdP->fileFlags,
vfdP->fileMode);
if (vfdP->fd < 0)
{
DO_DB(elog(LOG, "RE_OPEN FAILED: %d", errno));
return vfdP->fd;
}
else
{
DO_DB(elog(LOG, "RE_OPEN SUCCESS"));
++nfile;
}
/* seek to the right position */
if (vfdP->seekPos != (off_t) 0)
{
off_t returnValue;
returnValue = https://www.cnblogs.com/feishujun/p/lseek(vfdP->fd, vfdP->seekPos, SEEK_SET);
Assert(returnValue != (off_t) -1);
}
}
/*
* put it at the head of the Lru ring
*/
Insert(file);
return 0;
}

需要提前處理的邏輯是檢查file中fd是否有效,即不為-1,如果無效,嘗試通過C函式庫open API獲取有效的fd(BasicOpenFile),如果目前占用的fd數量(有效的vfd占用的fd、AllocateFile占用的和AllocateDir占用的)大于最多能打開的fd,需要釋放lru池中的fd,根據vfd的seekPos來更新打開的fd的指標位置,最后將file插入環形佇列的front,
Delete:將索引file的Vfd結構體從環形佇列中洗掉,

LruDelete:將索引file的Vfd結構體從環形佇列中洗掉,和Delete相比,該函式將打開檔案的當前指標位置保存到vfd,并且關閉檔案,設定vfd的狀態為無效,

ReleaseLruFile:洗掉lru池中最近最少使用的vfd,使用LruDelete函式處理,不僅從Lru中剔除還將打開檔案的當前指標位置保存到vfd,并且關閉檔案,設定vfd的狀態為無效,

如果vfd中的fileName不為NULL,需要free,將fdstate設定為0x0,將該vfd節點插入空閑vfd鏈表頭部

AllocateVfd函式從空閑vfd鏈表中分配vfd節點,并將節點索引回傳(失敗回傳0),如果空閑vfd鏈表中沒有空閑節點,增加陣列的大小(原大小的兩倍,但不超過32),使用realloc進行空間申請,清空新申請的空間,并加新節點添加到空閑vfd鏈表中,
static File
AllocateVfd(void)
{
Index i;
File file;
DO_DB(elog(LOG, "AllocateVfd. Size %lu", SizeVfdCache));
Assert(SizeVfdCache > 0); /* InitFileAccess not called? */
if (VfdCache[0].nextFree == 0)
{
/*
* The free list is empty so it is time to increase the size of the
* array. We choose to double it each time this happens. However,
* there's not much point in starting *real* small.
*/
Size newCacheSize = SizeVfdCache * 2;
Vfd *newVfdCache;
if (newCacheSize < 32)
newCacheSize = 32;
/*
* Be careful not to clobber VfdCache ptr if realloc fails.
*/
newVfdCache = (Vfd *) realloc(VfdCache, sizeof(Vfd) * newCacheSize);
if (newVfdCache == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
VfdCache = newVfdCache;
/*
* Initialize the new entries and link them into the free list.
*/
for (i = SizeVfdCache; i < newCacheSize; i++)
{
MemSet((char *) &(VfdCache[i]), 0, sizeof(Vfd));
VfdCache[i].nextFree = i + 1;
VfdCache[i].fd = VFD_CLOSED;
}
VfdCache[newCacheSize - 1].nextFree = 0;
VfdCache[0].nextFree = SizeVfdCache;
/*
* Record the new size
*/
SizeVfdCache = newCacheSize;
}
file = VfdCache[0].nextFree;
VfdCache[0].nextFree = VfdCache[file].nextFree;
return file;
}
列印lru中的以最近使用遍歷的vfd的順序

可配置關鍵點:單個行程打開的檔案數量(max_files_per_process)

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/1169.html
標籤:PostgreSQL
