天天看點

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

  • 虛拟檔案描述符機制的原理概述:VFD作為LRU池管理檔案描述符,并根據需要打開和關閉實際需要的OS檔案描述符。

    FD LRU池的結構如下圖所示:

  • PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

代碼解析

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"      
PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

檔案描述符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;      
typedef struct vfd
{
  int      fd;        /* current FD, or VFD_CLOSED if none */
  unsigned short fdstate;    /* bitflags for VFD's state */
  SubTransactionId create_subid;    /* for TEMPORARY fds, creating subxact */
  File    nextFree;    /* link to next free VFD, if in freelist */
  File    lruMoreRecently;  /* doubly linked recency-of-use list */
  File    lruLessRecently;
  off_t    seekPos;    /* current logical file position */
  char     *fileName;    /* name of file, or NULL for unused VFD */
  /* NB: fileName is malloc'd, and must be free'd when closing the VFD */
  int      fileFlags;    /* open(2) flags for (re)opening the file */
  int      fileMode;    /* mode to pass to open(2) */
} Vfd;      

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

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

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成員為非空

相關操作函數

  • 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成員的關系。

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

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 = 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;
}      
PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

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

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

LruDelete:将索引file的Vfd結構體從環形隊列中删除。和Delete相比,該函數将打開檔案的目前指針位置儲存到vfd,并且關閉檔案,設定vfd的狀态為無效。

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

ReleaseLruFile:删除lru池中最近最少使用的vfd,使用LruDelete函數處理,不僅從Lru中剔除還将打開檔案的目前指針位置儲存到vfd,并且關閉檔案,設定vfd的狀态為無效。

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

如果vfd中的fileName不為NULL,需要free。将fdstate設定為0x0,将該vfd節點插入空閑vfd連結清單頭部

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c

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的順序

PG虛拟檔案描述符(VFD)機制——FD LRU池:postgresql-8.4.1/src/backend/storage/file/fd.c