【问题标题】:How not to open a file twice in linux?如何不在linux中打开文件两次?
【发布时间】:2016-03-09 23:13:07
【问题描述】:

我有一个链接列表,其中包含一个 fd 和一个我用来在每个条目中打开此文件的字符串。仅当此文件尚未打开时,我才想打开并将文件添加到此列表中,因为我打开并解析了此文件并且不想执行两次。我的想法是将文件名与此列表中的每个名称进行比较,但我的程序会多次执行此操作,并且 Linux 中的一个文件可以有多个名称(软/硬链接)。我认为它不应该那么复杂,因为它很容易让操作系统检查,我是否已经使用过 inode,r? 我已经尝试过open 同一个文件,无论有没有flock,但我总是得到一个新的fd

【问题讨论】:

  • 使用stat() 获取inode(和文件系统)编号。比较 inode,而不是文件名。或者,如果您确实想要比较文件名,请使用哈希表(在某种程度上)使其更高效。
  • ...不,操作系统不会为您执行此操作。低级接口的全部意义在于提供最大程度的通用构建块,以便其他人可以在它们之上构建更高级别的接口。使这种行为自动化会限制操作——这意味着有些事情你不能做——所以这与“最大程度地通用”相反。
  • 也使用readlink 来处理符号链接。
  • @Jester,如果您正在查看已解析的 inode 编号,这将为您处理,因为您需要显式传递 AT_SYMLINK_NOFOLLOW 以获取链接本身的 inode。
  • 是的,你是对的。

标签: c linux file system-calls stat


【解决方案1】:

当您成功打开文件时,在文件上使用fstat。查看fstat提交的struct stat中的st_inost_dev是否已经记录在你的链表中。如果是这样,则关闭文件描述符并继续下一个文件。否则,将文件描述符、文件名和st_inost_dev 值添加到列表中。

您可以改用stat 在打开文件之前进行检查,但如果通常情况下该文件尚未打开,则在之后使用fstat 会稍微快一些。

【讨论】:

  • 我相信如果st_nlink 在普通文件上为 1,则您无需进行搜索。没有其他硬链接。
  • @Schwern 该文件可能还有其他符号链接。
  • @Schwern 我不会太相信链接数。
  • 正如在重复的How to identify if two file diescriptors point to the same file 中指出的那样,fstat 的优势在于它不受因在打开之间重命名文件而导致的竞争条件的影响。 (或在 stat 和 open 之间)。它可以让你检查两个fds 是否是同一个文件,不管你是如何得到它们的。
【解决方案2】:

在这种情况下,考虑您的数据结构通常很有用。更改为不允许重复的数据结构,例如哈希表。

维护一组您以前见过的数据。我为这一套使用了hash table。根据@RossRidge's answer,使用 inode 和 device 作为 key。这允许在 O(1) 时间内发现重复项。

这是一个示例实现。

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>

static int get_fd(GHashTable *fds, const char *filename, int mode) {
    int fd;
    struct stat stat;
    int keysize = 33;
    char key[keysize];  /* Two 64 bit numbers as hex and a separator */

    /* Resolve any symlinks */
    char *real_filename = realpath(filename, NULL);
    if( real_filename == NULL ) {
        printf("%s could not be resolved.\n", filename);
        return -1;
    }

    /* Open and stat */
    fd = open( real_filename, mode );
    if( fd < 0 ) {
        printf("Could not open %s: %s.\n", real_filename, strerror(errno));
        return -1;
    }
    if( fstat(fd, &stat) != 0 ) {
        printf("Could not stat %s: %s.\n", real_filename, strerror(errno));
        return -1;
    }

    /* Make a key for tracking which data we've processed.
       This uses both the inode and the device it's on.
       It could be done more efficiently as a bit field.
     */
    snprintf(key, keysize, "%lx|%lx", (long int)stat.st_ino, (long int)stat.st_dev);

    /*  See if we've already processed that */
    if( g_hash_table_contains(fds, key) ) {
        return 0;
    }
    else {
        /* Note that we've processed it */
        g_hash_table_add(fds, key);
        return fd;
    }
}


int main(int argc, char** argv) {
    int mode = O_RDONLY;
    int fd;
    GHashTable *fds = g_hash_table_new(&g_str_hash, &g_str_equal);

    for(int i = 1; i < argc; i++) {
        char *filename = argv[i];

        fd = get_fd(fds, filename, mode);
        if( fd == 0 ) {
            printf("%s has already been processed.\n", filename);
        }
        else if( fd < 0 ) {
            printf("%s could not be processed.\n", filename);
        }
        else {
            printf("%s: %d\n", filename, fd);
        }
    }
}

这是一个示例结果。

$ touch one two three
$ ln one one_link
$ ln -s two two_sym
$ ./test one* two* three*
one: 3
one_link has already been processed.
two: 5
two_sym has already been processed.
three: 7

【讨论】:

  • 实际上,OP 应该为 FSid/inode 文件 ID 使用哈希或树,而不是名称。除了符号链接,还有绑定挂载、硬链接,也许还有其他东西。但你说得对,链表是一个糟糕的选择。
  • 一个文件可以有多个名称,因此哈希表完全没用,而且问题是特定于 Linux 的。
  • @sivizius 为什么是“Linux”特定的问题?你是说Unix吗?无论哪种方式,结合罗斯确定数据唯一性的答案与我使用表格或树快速搜索数据的答案相结合,应该会产生一个既正确又快速的答案。
  • @sivizius 我已经完全修改了答案,我误解了你在找什么。它仍然使用哈希表来快速进行重复搜索,并且还考虑了罗斯的回答和彼得对密钥的评论。
【解决方案3】:

只要不关闭成功且有意打开的文件,就可以使用非阻塞flock来防止同一文件再次被锁定:

#include <unistd.h>
#include <sys/file.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <assert.h>

int openAndLock(const char* fn){
  int fd = -1;
  if(((fd = open(fn, O_RDONLY)) >= 0) && (flock(fd, LOCK_EX|LOCK_NB) == 0)){
      fprintf(stderr, "Successfully opened and locked %s\n", fn);
      return fd;
  }else{
    fprintf(stderr, "Failed to open or lock %s\n", fn);
    close(fd);
    return -1;
  }
}

int main(int argc, char** argv){
  for(int i=1; i<argc; i++){
    openAndLock(argv[i]);
  }
  return 0;
}

例子:

$ touch foo
$ ln foo bar
$ ./a.out foo foo
Successfully opened and locked foo
Failed to open or lock foo
$ ./a.out foo bar
Successfully opened and locked foo
Failed to open or lock bar

【讨论】:

  • 我已经尝试使用和不使用flock打开同一个文件,但我总是得到一个新的fd。
  • @sivizius 你试过这个程序吗?它对我有用。
  • @sivizius 另一个openflocked 文件应该成功并为您提供另一个文件描述符。 Flocking 新文件描述符应该失败(直到旧文件锁被释放)。
  • 我在汇编中尝试了类似的操作,但我得到了不同的 fd,所以:是的。
  • @sivizius 如果你想阻止另一个open 成功,你需要使用强制锁定,这很昂贵并且需要打开文件系统支持。没有它,就无法阻止另一个open(但您可以阻止另一个open+flock 对)。执行open+flock 与拥有一个inode 表并检查它本质上是一样的。区别在于flock,是操作系统而不是您维护表。
猜你喜欢
  • 2018-02-13
  • 1970-01-01
  • 1970-01-01
  • 2017-03-31
  • 2015-09-05
  • 1970-01-01
  • 2015-05-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多