本篇博客讲解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)));}elseelog(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;
#elseAssert(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 WIN32DWORD        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

PG虚拟文件描述符(VFD)机制——封装的文件接口:postgresql-8.4.1/src/backend/storage/file/fd.c相关推荐

  1. Linux 文件描述符的概念及与文件流指针的关系

    文件描述符 我们都知道,使用open打开一个文件后都会得到一个文件描述符,而且是一个非负正数,那这个数字是怎么来的呢? 当我们打使用open打开文件时,系统会为我们指定的文件创建一个文件描述信息结构体 ...

  2. 【操作系统/OS笔记17】文件系统基本概念、文件描述符、文件系统访问、文件别名、文件系统种类

    本次笔记内容: 12.1 文件系统:总体介绍 12.2 基本概念 12.3 基本概念--文件系统和文件 12.4 基本概念--文件系统的功能 12.5 基本概念--文件和块 12.6 基本概念--文件 ...

  3. linux修改文件描述符,linux最大允许的文件描述符open files数nofile修改

    open file resource limit 是linux中process可以打开的文件句柄数量.增加这个数值需要调整两个配置: 第一步, 修改系统最大允许的文件描述符 查看当前的设置: $ ca ...

  4. java套接字创建失败_Linux的文件描述符个数限制导致创建文件(或socket)失败的问题...

    众所周知,在相应进程的/proc/$pid/fd 目录下存放了此进程所有打开的fd.当然有些可能不是本进程自己打开的,如通过fork()从父进程继承而来的.本文着着重讲述socket有关的内容.当我们 ...

  5. linux用户文件描述符2表示,Linux下文件描述符

    Linux下文件描述符 文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket.第一个打开的文件是0,第二个是1,依此类推.Unix操作系 统通常给每个进程能打开的文件数量强加一个 ...

  6. 文件描述符(多进程对同一个文件操作)

    进程文件描述符:filedescrption,实际上我们调用open打开文件后得到的一个句柄,是个整数.属于用户区用来记录文件的一些信息,如文件指针,指向系统文件描述符表的指针,保存在进程的PCB中. ...

  7. Nginx文件描述符泄露?浅析FD_CLOEXEC文件描述符标志

    很精彩的一篇文章. 1. 引子 事情是这样的,最近我们线上一个基于nginx的http服务经常报警,具体如下: accept() failed (24: Too many open files) wh ...

  8. 文件流、目录流、文件描述符总结

    文件流.目录流.文件描述符总结 宗旨:技术的学习是有限的,分享的精神是无限的. 内核为使当前进程与进程打开的文件建立联系,在进程PCB(一个结构体task_struct)中使用一个成员来指向关于打开文 ...

  9. Linux内核机制总结内存管理之用户页错误文件描述符(二十八)

    文章目录 1 用户页错误文件描述符 1.1 使用方法 1.2 技术原理 重要:本系列文章内容摘自<Linux内核深度解析>基于ARM64架构的Linux4.x内核一书,作者余华兵.系列文章 ...

最新文章

  1. Redis源码分析--lookupKey函数查看value值
  2. es 插入数据_记录一次Java导入百万级数据到Elasticsearch经历
  3. QEMU-KVM中的多线程压缩迁移技术
  4. CF720C Homework(构造)(暴力)
  5. python中、文件最重要的功能是( )和接收数据_Python基础语法14个知识点大串讲
  6. 【c语言】棋盘游戏--三子棋
  7. 父、子页面之间页面元素的获取,方法的调用
  8. volatile关键字的使用
  9. python中深拷贝和浅拷贝_**Python中的深拷贝和浅拷贝详解
  10. 修改mysql root的秘密
  11. jQuery内置函数map和each的用法
  12. 2022淘宝618超级喵运会怎么玩?2022淘宝618喵运会玩法技巧
  13. 字模提取软件的使用(pctolCD2002,基于FPGA的VGA显示汉字)
  14. 转录组测序技术及结果解读(一)——测序样品设置及选择
  15. 【聚沙成塔】Linux环境下NodeJs升级
  16. 数电学习二——逻辑代数的计算与逻辑函数
  17. 面试逻辑题(English)
  18. c刊计算机领域见刊快的期刊,见刊最快的医学核心期刊有哪些
  19. 横向扩展 纵向扩展 数据库_理解数据库扩展模式的指南
  20. postman导出,断言,批量执行

热门文章

  1. SNMP 原理与实战详解
  2. 手机QQ上传速度提升8倍秘诀:解决速度与成功率的“鱼翅”项目
  3. SpringMVC 参数映射与文件上传
  4. 数据结构中二叉树的度
  5. 分享些发表技术类文章的平台
  6. Python diag函数
  7. 【Spring Cloud Alibaba】Gateway 服务网关
  8. sql1复习笔记10
  9. php 跨站脚本,Piwigo register.php页面多个跨站脚本漏洞
  10. [递归+访问者模式]实现树状结构的节点遍历处理