如何不在 linux 中打开一个文件两次?

How not to open a file twice in linux?

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

当您成功打开文件时,由fstat提交的fstat on the file. Check to see if the st_ino and st_dev of the struct stat已被记录在您的链表中。如果是,则关闭文件描述符并转到下一个文件。否则将文件描述符、文件名以及 st_inost_dev 值添加到列表中。

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

在这种情况下,考虑数据结构通常很有用。更改为不允许重复的数据结构,例如散列 table.

维护一组您以前见过的数据。我使用了 hash table for this set. As per ,使用索引节点和设备作为密钥。这允许在 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

只要不关闭成功打开的文件,就可以使用非阻塞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