📄 fd.c
字号:
/*------------------------------------------------------------------------- * * fd.c * Virtual file descriptor code. * * Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * $Id: fd.c,v 1.43 1999/07/08 02:46:39 momjian Exp $ * * NOTES: * * This code manages a cache of 'virtual' file descriptors (VFDs). * The server opens many file descriptors for a variety of reasons, * including base tables, scratch files (e.g., sort and hash spool * files), and random calls to C library routines like system(3); it * is quite easy to exceed system limits on the number of open files a * single process can have. (This is around 256 on many modern * operating systems, but can be as low as 32 on others.) * * VFDs are managed as an LRU pool, with actual OS file descriptors * being opened and closed as needed. Obviously, if a routine is * opened using these interfaces, all subsequent operations must also * be through these interfaces (the File type is not a real file * descriptor). * * For this scheme to work, most (if not all) routines throughout the * server should use these interfaces instead of calling the C library * routines (e.g., open(2) and fopen(3)) themselves. Otherwise, we * may find ourselves short of real file descriptors anyway. * * This file used to contain a bunch of stuff to support RAID levels 0 * (jbod), 1 (duplex) and 5 (xor parity). That stuff is all gone * because the parallel query processing code that called it is all * gone. If you really need it you could get it from the original * POSTGRES source. *------------------------------------------------------------------------- */#include <stdio.h>#include <sys/types.h>#include <sys/file.h>#include <sys/param.h>#include <sys/stat.h>#include <errno.h>#include <string.h>#include <unistd.h>#include <fcntl.h>#include "postgres.h"#include "miscadmin.h" /* for DataDir */#include "utils/palloc.h"#include "storage/fd.h"#include "utils/elog.h"/* * Problem: Postgres does a system(ld...) to do dynamic loading. * This will open several extra files in addition to those used by * Postgres. We need to guarantee that there are file descriptors free * for ld to use. * * The current solution is to limit the number of file descriptors * that this code will allocate at one time: it leaves RESERVE_FOR_LD free. * * (Even though most dynamic loaders now use dlopen(3) or the * equivalent, the OS must still open several files to perform the * dynamic loading. Keep this here.) */#ifndef RESERVE_FOR_LD#define RESERVE_FOR_LD 10#endif/* * We need to ensure that we have at least some file descriptors * available to postgreSQL after we've reserved the ones for LD, * so we set that value here. * * I think 10 is an appropriate value so that's what it'll be * for now. */#ifndef FD_MINFREE#define FD_MINFREE 10#endif/* Debugging.... */#ifdef FDDEBUG#define DO_DB(A) A#else#define DO_DB(A) /* A */#endif#define VFD_CLOSED (-1)#define FileIsValid(file) \ ((file) > 0 && (file) < SizeVfdCache && VfdCache[file].fileName != NULL)#define FileIsNotOpen(file) (VfdCache[file].fd == VFD_CLOSED)typedef struct vfd{ signed short fd; /* current FD, or VFD_CLOSED if none */ unsigned short fdstate; /* bitflags for VFD's state *//* these are the assigned bits in fdstate: */#define FD_DIRTY (1 << 0)/* written to, but not yet fsync'd */#define FD_TEMPORARY (1 << 1)/* should be unlinked when closed */ File nextFree; /* link to next free VFD, if in freelist */ File lruMoreRecently;/* doubly linked recency-of-use list */ File lruLessRecently; long 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 opening the file */ int fileMode; /* mode to pass to open(2) */} Vfd;/* * Virtual File Descriptor array pointer and size. This grows as * needed. 'File' values are indexes into this array. * Note that VfdCache[0] is not a usable VFD, just a list header. */static Vfd *VfdCache;static Size SizeVfdCache = 0;/* * Number of file descriptors known to be in use by VFD entries. */static int nfile = 0;/* * List of stdio FILEs opened with AllocateFile. * * Since we don't want to encourage heavy use of AllocateFile, it seems * OK to put a pretty small maximum limit on the number of simultaneously * allocated files. */#define MAX_ALLOCATED_FILES 32static int numAllocatedFiles = 0;static FILE *allocatedFiles[MAX_ALLOCATED_FILES];/* * Number of temporary files opened during the current transaction; * this is used in generation of tempfile names. */static long tempFileCounter = 0;/*-------------------- * * Private Routines * * 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 * * The Least Recently Used ring is a doubly linked list that begins and * ends on element zero. Element zero is special -- it doesn't represent * a file and its "fd" field always == VFD_CLOSED. Element zero is just an * anchor that shows us the beginning/end of the ring. * Only VFD elements that are currently really open (have an FD assigned) are * in the Lru ring. Elements that are "virtually" open can be recognized * by having a non-null fileName field. * * example: * * /--less----\ /---------\ * v \ v \ * #0 --more---> LeastRecentlyUsed --more-\ \ * ^\ | | * \\less--> MostRecentlyUsedFile <---/ | * \more---/ \--less--/ * *-------------------- */static void Delete(File file);static void LruDelete(File file);static void Insert(File file);static int LruInsert(File file);static void ReleaseLruFile(void);static File AllocateVfd(void);static void FreeVfd(File file);static int FileAccess(File file);static File fileNameOpenFile(FileName fileName, int fileFlags, int fileMode);static char *filepath(char *filename);static long pg_nofile(void);static int BufFileFlush(BufFile *file);/* * pg_fsync --- same as fsync except does nothing if -F switch was given */intpg_fsync(int fd){ return disableFsync ? 0 : fsync(fd);}/* * pg_nofile: determine number of filedescriptors that fd.c is allowed to use */static longpg_nofile(void){ static long no_files = 0; if (no_files == 0) { /* need do this calculation only once */#ifndef HAVE_SYSCONF no_files = (long) NOFILE;#else no_files = sysconf(_SC_OPEN_MAX); if (no_files == -1) { elog(DEBUG, "pg_nofile: Unable to get _SC_OPEN_MAX using sysconf(); using %d", NOFILE); no_files = (long) NOFILE; }#endif if ((no_files - RESERVE_FOR_LD) < FD_MINFREE) elog(FATAL, "pg_nofile: insufficient File Descriptors in postmaster to start backend (%ld).\n" " O/S allows %ld, Postmaster reserves %d, We need %d (MIN) after that.", no_files - RESERVE_FOR_LD, no_files, RESERVE_FOR_LD, FD_MINFREE); no_files -= RESERVE_FOR_LD; } return no_files;}#if defined(FDDEBUG)static void_dump_lru(){ int mru = VfdCache[0].lruLessRecently; Vfd *vfdP = &VfdCache[mru]; char buf[2048]; sprintf(buf, "LRU: MOST %d ", mru); while (mru != 0) { mru = vfdP->lruLessRecently; vfdP = &VfdCache[mru]; sprintf(buf + strlen(buf), "%d ", mru); } sprintf(buf + strlen(buf), "LEAST"); elog(DEBUG, buf);}#endif /* FDDEBUG */static voidDelete(File file){ Vfd *vfdP; Assert(file != 0); DO_DB(elog(DEBUG, "Delete %d (%s)", file, VfdCache[file].fileName)); DO_DB(_dump_lru()); vfdP = &VfdCache[file]; VfdCache[vfdP->lruLessRecently].lruMoreRecently = vfdP->lruMoreRecently; VfdCache[vfdP->lruMoreRecently].lruLessRecently = vfdP->lruLessRecently; DO_DB(_dump_lru());}static voidLruDelete(File file){ Vfd *vfdP; int returnValue; Assert(file != 0); DO_DB(elog(DEBUG, "LruDelete %d (%s)", file, VfdCache[file].fileName)); vfdP = &VfdCache[file]; /* delete the vfd record from the LRU ring */ Delete(file); /* save the seek position */ vfdP->seekPos = (long) lseek(vfdP->fd, 0L, SEEK_CUR); Assert(vfdP->seekPos != -1); /* if we have written to the file, sync it */ if (vfdP->fdstate & FD_DIRTY) { returnValue = pg_fsync(vfdP->fd); Assert(returnValue != -1); vfdP->fdstate &= ~FD_DIRTY; } /* close the file */ returnValue = close(vfdP->fd); Assert(returnValue != -1); --nfile; vfdP->fd = VFD_CLOSED;}static voidInsert(File file){ Vfd *vfdP; Assert(file != 0); DO_DB(elog(DEBUG, "Insert %d (%s)", file, VfdCache[file].fileName)); DO_DB(_dump_lru()); vfdP = &VfdCache[file]; vfdP->lruMoreRecently = 0; vfdP->lruLessRecently = VfdCache[0].lruLessRecently; VfdCache[0].lruLessRecently = file; VfdCache[vfdP->lruLessRecently].lruMoreRecently = file; DO_DB(_dump_lru());}static intLruInsert(File file){ Vfd *vfdP; int returnValue; Assert(file != 0); DO_DB(elog(DEBUG, "LruInsert %d (%s)", file, VfdCache[file].fileName)); vfdP = &VfdCache[file]; if (FileIsNotOpen(file)) { while (nfile + numAllocatedFiles >= pg_nofile()) ReleaseLruFile(); /* * 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... */tryAgain: vfdP->fd = open(vfdP->fileName, vfdP->fileFlags, vfdP->fileMode); if (vfdP->fd < 0 && (errno == EMFILE || errno == ENFILE)) { errno = 0; ReleaseLruFile(); goto tryAgain; } if (vfdP->fd < 0) { DO_DB(elog(DEBUG, "RE_OPEN FAILED: %d", errno)); return vfdP->fd; } else { DO_DB(elog(DEBUG, "RE_OPEN SUCCESS")); ++nfile; } /* seek to the right position */ if (vfdP->seekPos != 0L) { returnValue = lseek(vfdP->fd, vfdP->seekPos, SEEK_SET); Assert(returnValue != -1); } /* Update state as appropriate for re-open (needed?) */ vfdP->fdstate &= ~FD_DIRTY; } /* * put it at the head of the Lru ring */ Insert(file); return 0;}static voidReleaseLruFile(){ DO_DB(elog(DEBUG, "ReleaseLruFile. Opened %d", nfile)); if (nfile <= 0) elog(FATAL, "ReleaseLruFile: No opened files - no one can be closed"); /* * There are opened files and so there should be at least one used vfd * in the ring. */ Assert(VfdCache[0].lruMoreRecently != 0); LruDelete(VfdCache[0].lruMoreRecently);}static FileAllocateVfd(){ Index i; File file; DO_DB(elog(DEBUG, "AllocateVfd. Size %d", SizeVfdCache)); if (SizeVfdCache == 0) { /* initialize header entry first time through */ VfdCache = (Vfd *) malloc(sizeof(Vfd)); Assert(VfdCache != NULL); MemSet((char *) &(VfdCache[0]), 0, sizeof(Vfd)); VfdCache->fd = VFD_CLOSED; SizeVfdCache = 1; } 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; if (newCacheSize < 32) newCacheSize = 32; VfdCache = (Vfd *) realloc(VfdCache, sizeof(Vfd) * newCacheSize); Assert(VfdCache != NULL); /* * 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;}static voidFreeVfd(File file){ Vfd *vfdP = &VfdCache[file]; DO_DB(elog(DEBUG, "FreeVfd: %d (%s)", file, vfdP->fileName ? vfdP->fileName : "")); if (vfdP->fileName != NULL) { free(vfdP->fileName); vfdP->fileName = NULL; } vfdP->nextFree = VfdCache[0].nextFree; VfdCache[0].nextFree = file;}/* filepath() * Convert given pathname to absolute. * (Is this actually necessary, considering that we should be cd'd * into the database directory??) */static char *filepath(char *filename){ char *buf; int len; /* Not an absolute path name? Then fill in with database path... */ if (*filename != SEP_CHAR) { len = strlen(DatabasePath) + strlen(filename) + 2; buf = (char *) palloc(len); sprintf(buf, "%s%c%s", DatabasePath, SEP_CHAR, filename); } else { buf = (char *) palloc(strlen(filename) + 1); strcpy(buf, filename); }#ifdef FILEDEBUG printf("filepath: path is %s\n", buf);#endif return buf;}static intFileAccess(File file){ int returnValue; DO_DB(elog(DEBUG, "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;}/* * Called when we get a shared invalidation message on some relation. */#ifdef NOT_USEDvoidFileInvalidate(File file){ Assert(FileIsValid(file)); if (!FileIsNotOpen(file)) LruDelete(file);}#endifstatic FilefileNameOpenFile(FileName fileName, int fileFlags, int fileMode){ File file; Vfd *vfdP; if (fileName == NULL) elog(ERROR, "fileNameOpenFile: NULL fname"); DO_DB(elog(DEBUG, "fileNameOpenFile: %s %x %o", fileName, fileFlags, fileMode)); file = AllocateVfd(); vfdP = &VfdCache[file]; while (nfile + numAllocatedFiles >= pg_nofile()) ReleaseLruFile();tryAgain: vfdP->fd = open(fileName, fileFlags, fileMode); if (vfdP->fd < 0 && (errno == EMFILE || errno == ENFILE))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -