力扣103-二叉樹的鋸齒形層序遍歷(Java版詳細註釋題解)
技術標籤:# PostgreSQL
本篇部落格講解fd.c檔案中對C函式庫檔案操作API的相關封裝。(相關C函式庫檔案操作API參見博主linux分類下的文章)InitFileAccess函式用於在postgresql啟動時初始化VFD LRU池,並向系統註冊proc-exit勾子以確保退出時清理臨時檔案。
InitFileAccess函式在後端啟動初始化階段呼叫(normal or standalone backend),在資料庫執行過程中只能呼叫一次。主要用於VFD LRU池中的頭元素的空間,並設定LRU池的大小為1。最後註冊proc-exit勾子以幫助確保退出時臨時檔案丟棄(register proc-exit hook to ensure temp files are dropped at exit)。on_proc_exit向proc_exit()函式呼叫的函式列表中添加回調函式。
/*
* InitFileAccess --- initialize this module during backend startup
*
* This is called during either normal or standalone backend start.
* It is *not* called in the postmaster.
*/
void
InitFileAccess(void)
{
Assert(SizeVfdCache == 0); /* call me only once */
/* initialize cache header entry */
VfdCache = (Vfd *) malloc(sizeof(Vfd));
if (VfdCache == NULL)
ereport(FATAL,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd));
VfdCache->fd = VFD_CLOSED;
SizeVfdCache = 1;
/* register proc-exit hook to ensure temp files are dropped at exit */
on_proc_exit(AtProcExit_Files, 0);
}
函式on_proc_exit處於backend/storage/ipc/ipc.c檔案中
回撥函式指標型別
需要註冊的回撥函式,用於在關閉後端時清理臨時檔案(包括interXact檔案)
/*
* AtProcExit_Files
*
* on_proc_exit hook to clean up temp files during backend shutdown.
* Here, we want to clean up *all* temp files including interXact ones.
*/
static void
AtProcExit_Files(int code, Datum arg)
{
CleanupTempFiles(true);
}
BasicOpenFile — 除了可以根據需要釋放其他FD,該函式與open(2)相同
匯出該檔案供真正需要普通核心FD的地方使用,但需要證明不會耗盡FD。成功返回FD之後,呼叫者有責任確保它不會在ereport()上洩漏! 大多數使用者不應該直接呼叫該例程,而應使用VFD抽象級別,該級別提供了防止描述符洩漏以及對需要短時間開啟的檔案進行管理保護。 理想情況下,這應該是後端中open()的* only *直接呼叫。 實際上,postmaster直接呼叫open(),並且在後端啟動的早期就完成了一些直接的open()呼叫。 這樣就可以了,因為無論如何該模組都不會關閉任何開啟的檔案。
也就是該模組不使用Lru池功能,直接用C函式庫API open開啟檔案。但是如果系統fd不足,可能需要釋放lru池中的FD,並重新呼叫open。
int BasicOpenFile(FileName fileName, int fileFlags, int fileMode)
{
int fd;
tryAgain:
fd = open(fileName, fileFlags, fileMode);
if (fd >= 0)
return fd; /* success! */
if (errno == EMFILE || errno == ENFILE)
{
int save_errno = errno;
ereport(LOG,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg("out of file descriptors: %m; release and retry")));
errno = 0;
if (ReleaseLruFile())
goto tryAgain;
errno = save_errno;
}
return -1; /* failure */
}
FileAccess成功返回0,重新開啟失敗返回-1且設定errno。如果檔案沒有開啟(不擁有FD,即FD為-1),使用LruInsert函式(函式中BasicOpenFile開啟檔案,並將fd放入vfd中對應的成員中)。如果打開了且不是最近使用的vfd,需要將該vfd移動到LRU池的頭部稱為最近使用的。
static int FileAccess(File file)
{
int returnValue;
DO_DB(elog(LOG, "FileAccess %d (%s)",
file, VfdCache[file].fileName));
/*
* Is the file open? If not, open it and put it at the head of the LRU
* ring (possibly closing the least recently used file to get an FD).
*/
if (FileIsNotOpen(file))
{
returnValue = LruInsert(file);
if (returnValue != 0)
return returnValue;
}
else if (VfdCache[0].lruLessRecently != file)
{
/*
* We now know that the file is open and that it is not the last one
* accessed, so we need to move it to the head of the Lru ring.
*/
Delete(file);
Insert(file);
}
return 0;
}
PathNameOpenFile函式使用絕對路徑開啟檔案,並使用開啟模式和標誌引數。當傳入相對引數,將會使用程序工作目錄的路徑作為字首($PGDATA中儲存的路徑)。一般流程:分配VFD,使用BasicOpenFile開啟檔案,將fd與VFD關聯,將vfd插入LRU池,配置VFDD的引數(取消O_CREAT | O_TRUNC | O_EXCL模式)。
File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
{
char *fnamecopy;
File file;
Vfd *vfdP;
DO_DB(elog(LOG, "PathNameOpenFile: %s %x %o",
fileName, fileFlags, fileMode));
/*
* We need a malloc'd copy of the file name; fail cleanly if no room.
*/
fnamecopy = strdup(fileName);
if (fnamecopy == NULL)
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY),
errmsg("out of memory")));
file = AllocateVfd();
vfdP = &VfdCache[file];
while (nfile + numAllocatedDescs >= max_safe_fds)
{
if (!ReleaseLruFile())
break;
}
vfdP->fd = BasicOpenFile(fileName, fileFlags, fileMode);
if (vfdP->fd < 0)
{
FreeVfd(file);
free(fnamecopy);
return -1;
}
++nfile;
DO_DB(elog(LOG, "PathNameOpenFile: success %d",
vfdP->fd));
Insert(file);
vfdP->fileName = fnamecopy;
/* Saved flags are adjusted to be OK for re-opening file */
vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL);
vfdP->fileMode = fileMode;
vfdP->seekPos = 0;
vfdP->fdstate = 0x0;
return file;
}
FileClosse關閉檔案,如果檔案開啟,則從lru池中刪除相應的VFD節點,關閉相應的fd,減少nfile計數。如果檔案是臨時檔案則刪除臨時檔案(vfdP->fdstate & FD_TEMPORARY)。將vfd放入空閒連結串列。
void FileClose(File file)
{
Vfd *vfdP;
struct stat filestats;
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FileClose: %d (%s)",
file, VfdCache[file].fileName));
vfdP = &VfdCache[file];
if (!FileIsNotOpen(file))
{
/* remove the file from the lru ring */
Delete(file);
/* close the file */
if (close(vfdP->fd))
elog(ERROR, "could not close file \"%s\": %m", vfdP->fileName);
--nfile;
vfdP->fd = VFD_CLOSED;
}
/*
* Delete the file if it was temporary
*/
if (vfdP->fdstate & FD_TEMPORARY)
{
/* reset flag so that die() interrupt won't cause problems */
vfdP->fdstate &= ~FD_TEMPORARY;
if (log_temp_files >= 0)
{
if (stat(vfdP->fileName, &filestats) == 0)
{
if (filestats.st_size >= log_temp_files)
ereport(LOG,
(errmsg("temporary file: path \"%s\", size %lu",
vfdP->fileName,
(unsigned long) filestats.st_size)));
}
else
elog(LOG, "could not stat file \"%s\": %m", vfdP->fileName);
}
if (unlink(vfdP->fileName))
elog(LOG, "could not unlink file \"%s\": %m", vfdP->fileName);
}
/*
* Return the Vfd slot to the free list
*/
FreeVfd(file);
}
FileInvalidate函式檢查file對應vfd的fd是否為VFD_CLOSED,並從Lru池中刪除該vfd
1 FileInvalidate(File file)
2 {
3 Assert(FileIsValid(file));
4 if (!FileIsNotOpen(file))
5 LruDelete(file);
6 }
#define FileIsValid(file) \ ((file) > 0 && (file) < (int) SizeVfdCache && VfdCache[file].fileName != NULL)
#define FileIsNotOpen(file) (VfdCache[file].fd == VFD_CLOSED)
FilePrefetch-啟動檔案給定範圍的非同步讀取(initiate asynchronous read of a given range of the file)。 邏輯查詢位置logical seek position不受影響。 當前,此功能的唯一實現是使用posix_fadvise,它是完成此功能的最簡單的標準化介面。 我們將來可以使用libaio新增一個實現。 但請注意,此API不適合libaio,因為libaio希望提供一個緩衝區以供讀取。
int FilePrefetch(File file, off_t offset, int amount)
{
#if defined(USE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
int returnCode;
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FilePrefetch: %d (%s) " INT64_FORMAT " %d",
file, VfdCache[file].fileName,
(int64) offset, amount));
returnCode = FileAccess(file);
if (returnCode < 0)
return returnCode;
returnCode = posix_fadvise(VfdCache[file].fd, offset, amount,
POSIX_FADV_WILLNEED);
return returnCode;
#else
Assert(FileIsValid(file));
return 0;
#endif
}
int FileWrite(File file, char *buffer, int amount)
{
int returnCode;
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FileWrite: %d (%s) " INT64_FORMAT " %d %p",
file, VfdCache[file].fileName,
(int64) VfdCache[file].seekPos,
amount, buffer));
returnCode = FileAccess(file);
if (returnCode < 0)
return returnCode;
retry:
errno = 0;
returnCode = write(VfdCache[file].fd, buffer, amount);
/* if write didn't set errno, assume problem is no disk space */
if (returnCode != amount && errno == 0)
errno = ENOSPC;
if (returnCode >= 0)
VfdCache[file].seekPos += returnCode;
else
{
/*
* See comments in FileRead()
*/
#ifdef WIN32
DWORD error = GetLastError();
switch (error)
{
case ERROR_NO_SYSTEM_RESOURCES:
pg_usleep(1000L);
errno = EINTR;
break;
default:
_dosmaperr(error);
break;
}
#endif
/* OK to retry if interrupted */
if (errno == EINTR)
goto retry;
/* Trouble, so assume we don't know the file position anymore */
VfdCache[file].seekPos = FileUnknownPos;
}
return returnCode;
}
int FileSync(File file)
{
int returnCode;
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FileSync: %d (%s)",
file, VfdCache[file].fileName));
returnCode = FileAccess(file);
if (returnCode < 0)
return returnCode;
return pg_fsync(VfdCache[file].fd);
}
off_t FileSeek(File file, off_t offset, int whence)
{
int returnCode;
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FileSeek: %d (%s) " INT64_FORMAT " " INT64_FORMAT " %d",
file, VfdCache[file].fileName,
(int64) VfdCache[file].seekPos,
(int64) offset, whence));
if (FileIsNotOpen(file))
{
switch (whence)
{
case SEEK_SET:
if (offset < 0)
elog(ERROR, "invalid seek offset: " INT64_FORMAT,
(int64) offset);
VfdCache[file].seekPos = offset;
break;
case SEEK_CUR:
VfdCache[file].seekPos += offset;
break;
case SEEK_END:
returnCode = FileAccess(file);
if (returnCode < 0)
return returnCode;
VfdCache[file].seekPos = lseek(VfdCache[file].fd,
offset, whence);
break;
default:
elog(ERROR, "invalid whence: %d", whence);
break;
}
}
else
{
switch (whence)
{
case SEEK_SET:
if (offset < 0)
elog(ERROR, "invalid seek offset: " INT64_FORMAT,
(int64) offset);
if (VfdCache[file].seekPos != offset)
VfdCache[file].seekPos = lseek(VfdCache[file].fd,
offset, whence);
break;
case SEEK_CUR:
if (offset != 0 || VfdCache[file].seekPos == FileUnknownPos)
VfdCache[file].seekPos = lseek(VfdCache[file].fd,
offset, whence);
break;
case SEEK_END:
VfdCache[file].seekPos = lseek(VfdCache[file].fd,
offset, whence);
break;
default:
elog(ERROR, "invalid whence: %d", whence);
break;
}
}
return VfdCache[file].seekPos;
}
off_t FileTell(File file)
{
Assert(FileIsValid(file));
DO_DB(elog(LOG, "FileTell %d (%s)",
file, VfdCache[file].fileName));
return VfdCache[file].seekPos;
}
#endif