124

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

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

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

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

4

7 回答 7

178

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

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

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

一般来说,一旦文件打开,文件就打开了,没有人改变目录结构可以改变它——他们可以移动、重命名文件或将其他东西放在它的位置,它只是保持打开状态。

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


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

于 2010-01-08T22:09:20.023 回答
9

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

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

编辑:在硬件的情况下,您已经打开了特定设备节点的句柄,如果您拔下设备,内核将无法对其进行所有访问,即使设备恢复正常。您必须关闭设备并重新打开它。

于 2010-01-08T16:13:47.697 回答
5

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

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

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

于 2010-01-08T16:14:21.637 回答
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;
}
于 2015-02-05T09:47:34.277 回答
2

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

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

于 2010-01-08T16:13:24.963 回答
2

下面的实验表明MarkR 的答案是正确的。

代码.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 上做过实验。

于 2018-05-21T10:09:44.390 回答
1

在 /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;
}
于 2010-01-08T20:57:02.620 回答