【问题标题】:What happens to an open file handle on Linux if the pointed file gets moved or deleted如果指向的文件被移动或删除,Linux 上打开的文件句柄会发生什么
【发布时间】:2011-01-02 23:41:05
【问题描述】:

如果指向的文件同时获取,Linux 上打开的文件句柄会发生什么:

  • 移走 -> 文件句柄是否保持有效?
  • 已删除 -> 这会导致 EBADF 指示无效的文件句柄吗?
  • 被新文件替换 -> 文件句柄是否指向这个新文件?
  • 替换为指向新文件的硬链接 -> 我的文件是否处理“跟随”此链接?
  • 替换为指向新文件的软链接 -> 我的文件句柄现在是否命中此软链接文件?

我为什么要问这样的问题:我使用的是热插拔硬件(例如 USB 设备等)。可能会发生设备(以及它的 /dev/file)被用户或另一个 Gremlin 重新连接的情况。

处理这个问题的最佳做法是什么?

【问题讨论】:

    标签: linux file file-io linux-kernel systems-programming


    【解决方案1】:

    如果文件被移动(在同一文件系统中)或重命名,则文件句柄保持打开状态,仍可用于读取和写入文件。

    如果文件被删除,文件句柄仍然打开并且仍然可以使用(这不是某些人所期望的)。在关闭最后一个句柄之前,该文件不会真正被删除。

    如果文件被新文件替换,这完全取决于如何替换。如果文件的内容被覆盖,文件句柄将仍然有效并访问新内容。如果取消链接现有文件并创建具有相同名称的新文件,或者如果使用rename() 将新文件移动到现有文件上,则与删除相同(见上文) - 即文件句柄将继续参考文件的原始版本。

    一般来说,一旦文件打开,文件就打开了,任何更改目录结构的人都无法改变它 - 他们可以移动、重命名文件或在其位置放置其他内容,它只是保持打开状态。

    在 Unix 中没有删除,只有 unlink(),这是有道理的,因为它不一定删除文件 - 只是从目录中删除链接。


    另一方面,如果底层设备消失(例如 USB 拔出),则文件句柄将不再有效,并且可能会在任何操作中产生 IO/错误。不过,您仍然必须关闭它。即使重新插入设备也是如此,因为在这种情况下保持文件打开是不明智的。

    【讨论】:

    • 我想如果删除文件的包含目录,您的第二点同样适用。是这样吗?
    • 我对一件事感兴趣:如果你使用cp命令覆盖一个文件,是第一种情况还是第二种情况?
    • "在关闭最后一个句柄之前不会真正删除文件。"很有趣。谢谢
    【解决方案2】:

    文件句柄指向一个 inode 而不是路径,因此您的大多数场景仍然按照您的假设工作,因为句柄仍然指向文件。

    具体来说,在删除场景中 - 该函数被称为“取消链接”是有原因的,它破坏了文件名(dentry)和文件之间的“链接”。当你打开一个文件,然后取消链接时,文件实际上仍然存在,直到它的引用计数变为零,也就是你关闭句柄的时候。

    编辑:在硬件的情况下,你已经打开了一个特定设备节点的句柄,如果你拔掉设备,内核将失败所有对它的访问,即使设备回来了.您必须关闭设备并重新打开它。

    【讨论】:

      【解决方案3】:

      我不确定其他操作,但至于删除:在文件的最后一个打开句柄关闭之前,删除根本不会发生(物理上,即在文件系统中)。因此,应该不可能从您的应用程序下删除文件。

      一些应用程序(没有想到的)依赖于这种行为,通过创建、打开和立即删除文件,这些文件的生命周期与应用程序一样长 - 允许其他应用程序了解第一个应用程序的生命周期无需查看流程图等。

      类似的考虑可能适用于其他东西。

      【讨论】:

        【解决方案4】:

        如果要检查文件处理程序(文件描述符)是否正常,可以调用此函数。

        /**
         * version : 1.1
         *    date : 2015-02-05
         *    func : check if the fileDescriptor is fine.
         */
        
        #include <unistd.h>
        #include <fcntl.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <fcntl.h>
        #include <errno.h>
        #include <sys/types.h>
        #include <sys/stat.h>
        #include <unistd.h>
        #include <stdio.h>
        
        /**
         * On success, zero is returned.  On error, -1  is  returned,  and  errno  is  set
         *      appropriately.
         */
        int check_fd_fine(int fd) {
            struct stat _stat;
            int ret = -1;
            if(!fcntl(fd, F_GETFL)) {
                if(!fstat(fd, &_stat)) {
                    if(_stat.st_nlink >= 1)
                        ret = 0;
                    else
                        printf("File was deleted!\n");
                }
            }
            if(errno != 0)
                perror("check_fd_fine");
            return ret;
        }
        
        int main() {
            int fd = -1;
            fd = open("/dev/ttyUSB1", O_RDONLY);
            if(fd < 0) {
                perror("open file fail");
                return -1;
            }
            // close or remove file(remove usb device)
        //  close(fd);
            sleep(5);
            if(!check_fd_fine(fd)) {
                printf("fd okay!\n");
            } else {
                printf("fd bad!\n");
            }
            close(fd);
            return 0;
        }
        

        【讨论】:

        • if(!fcntl(fd, F_GETFL)) { 检查的意义何在?我猜你正在那里寻找EBADF。 (您也可能忘记将 errno 初始化为 0)。
        • 这对我不起作用。我尝试将这种方法与open(O_WRONLY|O_APPEND) 一起使用——打开我的描述符时,st_nlink 始终保持 >= 1。
        【解决方案5】:

        已删除文件的内存信息(您给出的所有示例都是已删除文件的实例)以及磁盘上的 inode 保持存在,直到文件关闭。

        硬件被热插拔是一个完全不同的问题,如果磁盘上的 inode 或元数据发生了变化,你不应该期望你的程序能保持很长时间。

        【讨论】:

          【解决方案6】:

          下面的实验证明MarkR's answer是正确的。

          code.c:

          #include <sys/types.h>
          #include <sys/stat.h>
          #include <fcntl.h>
          #include <stdlib.h>
          #include <unistd.h>
          #include <strings.h>
          #include <stdio.h>
          
          void perror_and_exit() {
            perror(NULL);
            exit(1);
          }
          
          int main(int argc, char *argv[]) {
            int fd;
            if ((fd = open("data", O_RDONLY)) == -1) {
              perror_and_exit();
            }
            char buf[5];
            for (int i = 0; i < 5; i++) {
              bzero(buf, 5);
              if (read(fd, buf, 5) != 5) {
                perror_and_exit();
              }
              printf("line: %s", buf);
              sleep(20);
            }
            if (close(fd) != 0) {
              perror_and_exit();
            }
            return 0;
          }
          

          数据:

          1234
          1234
          1234
          1234
          1234
          

          使用gcc code.c 生成a.out。运行./a.out。当您看到以下输出时:

          line: 1234
          

          使用rm data 删除data。但是./a.out 将继续运行而不会出现错误并产生以下完整输出:

          line: 1234
          line: 1234
          line: 1234
          line: 1234
          line: 1234
          

          我在 Ubuntu 16.04.3 上做过实验。

          【讨论】:

            【解决方案7】:

            在 /proc/ 目录下,您会找到当前活动的每个进程的列表,只需找到您的 PID 和所有相关数据。一个有趣的信息是文件夹 fd/,你会发现进程当前打开的所有文件处理程序。

            最终你会发现一个指向你设备的符号链接(在 /dev/ 甚至 /proc/bus/usb/ 下),如果设备挂起,链接将失效,并且无法刷新此句柄,进程必须关闭并再次打开它(即使重新连接)

            此代码可以读取您的 PID 的链接当前状态

            #include <unistd.h>
            #include <stdio.h>
            #include <dirent.h>
            
            int main() {
                // the directory we are going to open
                DIR           *d;
            
                // max length of strings
                int maxpathlength=256;
            
                // the buffer for the full path
                char path[maxpathlength];
            
                // /proc/PID/fs contains the list of the open file descriptors among the respective filenames
                sprintf(path,"/proc/%i/fd/",getpid() );
            
                printf("List of %s:\n",path);
            
                struct dirent *dir;
                d = opendir(path);
                if (d) {
                    //loop for each file inside d
                    while ((dir = readdir(d)) != NULL) {
            
                        //let's check if it is a symbolic link
                        if (dir->d_type == DT_LNK) {
            
                            const int maxlength = 256;
            
                            //string returned by readlink()
                            char hardfile[maxlength];
            
                            //string length returned by readlink()
                            int len;
            
                            //tempath will contain the current filename among the fullpath
                            char tempath[maxlength];
            
                            sprintf(tempath,"%s%s",path,dir->d_name);
                            if ((len=readlink(tempath,hardfile,maxlength-1))!=-1) {
                                hardfile[len]='\0';
                                    printf("%s -> %s\n", dir->d_name,hardfile);
            
                            } else
                                printf("error when executing readlink() on %s\n",tempath);
            
                        }
                    }
            
                    closedir(d);
                }
                return 0;
            }
            

            这个最终代码很简单,你可以玩一下linkat函数。

            int
            open_dir(char * path)
            {
              int fd;
            
              path = strdup(path);
              *strrchr(path, '/') = '\0';
              fd = open(path, O_RDONLY | O_DIRECTORY);
              free(path);
            
              return fd;
            }
            
            int
            main(int argc, char * argv[])
            {
              int odir, ndir;
              char * ofile, * nfile;
              int status;
            
              if (argc != 3)
                return 1;
            
              odir = open_dir(argv[1]);
              ofile = strrchr(argv[1], '/') + 1;
            
              ndir = open_dir(argv[2]);
              nfile = strrchr(argv[2], '/') + 1;
            
              status = linkat(odir, ofile, ndir, nfile, AT_SYMLINK_FOLLOW);
            if (status) {
              perror("linkat failed");
            }
            
            
              return 0;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-09-14
              • 2016-09-24
              • 1970-01-01
              • 2018-05-21
              • 1970-01-01
              • 1970-01-01
              • 2019-07-24
              相关资源
              最近更新 更多