【问题标题】:How to check if a file is already opened in C如何检查文件是否已经在C中打开
【发布时间】:2013-09-06 01:05:35
【问题描述】:

我正在开发一个多线程系统,其中可以根据文件访问权限在不同线程之间共享文件。

如何检查文件是否已被另一个线程打开?

【问题讨论】:

  • 你需要一个物理锁,或者跨线程定义一个信号量(驻留在共享内存中?)
  • 最简单的方法是保留所有打开文件的共享列表,并使其可供所有线程使用。
  • 嗯,最简单的方法就是自己跟踪它。
  • 附带说明:如果您想检查文件是否已经打开(通过任何其他进程或线程),您可以尝试获取文件写入租约(@ 987654322@)。如果文件已经被其他人打开,它将失败。这仅适用于用户拥有的普通文件。如果其他人试图打开该文件,则租约所有者将收到一个信号,并且最多有 /proc/sys/fs/lease_break_time 秒来操作文件并释放或降级租约,然后其他打开将继续进行(但它最终会,即使您重命名或取消链接文件)。
  • 无论这要解决什么问题,它实际上并没有解决它 - 你只能找出文件是否打开/没有打开 - 并且可以随时更改(甚至 你正在收集你的数据...)。因此,您得到的任何答案都会立即过时并且实际上毫无意义。使用此处找到的任何答案都是 TOCTOU bug 等待发生。根据该 Wiki 链接:“TOCTOU 竞争条件在 Unix 中在文件系统上的操作之间很常见。”不仅仅是 Unix - Windows 文件系统也是如此。

标签: c linux


【解决方案1】:

要查明一个命名文件是否已经在 上打开,您可以扫描/proc/self/fd 目录以查看该文件是否与文件描述符相关联。下面的程序勾勒出一个解决方案:

DIR *d = opendir("/proc/self/fd");
if (d) {
    struct dirent *entry;
    struct dirent *result;

    entry = malloc(sizeof(struct dirent) + NAME_MAX + 1);
    result = 0;
    while (readdir_r(d, entry, &result) == 0) {
        if (result == 0) break;
        if (isdigit(result->d_name[0])) {
            char path[NAME_MAX+1];
            char buf[NAME_MAX+1];
            snprintf(path, sizeof(path), "/proc/self/fd/%s",
                     result->d_name);
            ssize_t bytes = readlink(path, buf, sizeof(buf));
            buf[bytes] = '\0';
            if (strcmp(file_of_interest, buf) == 0) break;
        }
    }
    free(entry);
    closedir(d);
    if (result) return FILE_IS_FOUND;
}
return FILE_IS_NOT_FOUND;

从您的评论看来,您想要做的是检索现有的FILE *,如果之前对文件的fopen() 调用已经创建了一个FILE *。标准 C 库没有提供遍历所有当前打开的 FILE * 的机制。如果有这样的机制,你可以用fileno()导出它的文件描述符,然后用readlink()查询/proc/self/fd/#,如上所示。

这意味着您将需要使用数据结构来管理您打开的FILE *s。使用文件名作为键的哈希表可能对您最有用。

【讨论】:

  • 我使用的是文件指针而不是描述符。所以这个解决方案对我没有帮助
  • fileno(FILE) 宏从文件指针返回描述符。但是,我不会扫描 /proc/self/fd 只是因为它不是原子的 - 当您扫描 /proc/self/fd 时,其他线程仍然可以关闭或打开文件。
  • github.com/lsof-org/lsof查看lsof源代码。
【解决方案2】:

如果您倾向于在 shell 中执行此操作,您可以简单地使用 lsof $filename

【讨论】:

  • 这本质上是线程不安全的——只要它返回答案就可以改变。
  • @Stewart 实际上答案在运行时会发生变化......
  • @AlexisWilke 是的。 此处发布的任何检查用于检查文件是否由另一个线程或进程打开,两者都可以在检查运行时更改 之后立即无用。
【解决方案3】:

您可以使用int flock(int fd, int operation); 将文件标记为已锁定并检查它是否已锁定。

   Apply or remove an advisory lock on the open file specified by fd.
   The argument operation is one of the following:

       LOCK_SH  Place a shared lock.  More than one process may hold a
                shared lock for a given file at a given time.

       LOCK_EX  Place an exclusive lock.  Only one process may hold an
                exclusive lock for a given file at a given time.

       LOCK_UN  Remove an existing lock held by this process.

如果您在每个线程中单独打开文件,flock 应该在线程应用程序中工作: multiple threads able to get flock at the same time

有更多关于羊群的信息以及它的潜在弱点here

【讨论】:

  • 不幸的是,如果其他进程试图锁定我们的文件,也会影响它们。提问者似乎想要只线程锁。
  • 好声明。我以为他在做 fork/exec 但也许不是。
  • 这篇文章没有回答 OP 中提出的问题。
  • @JonathanBen-Avraham 对该问题的任何实际答案都立即无用 - 它只会告诉您文件 是否 打开,而不是 b>打开的。实际上尝试使用所提出问题的任何答案是TOCTOU bug
  • @AndrewHenle 我的评论的重点是,这篇文章只回答了一个退化的情况,你 1) 可以访问应用程序代码 2) 可以构建代码 3) 获得管理层的许可来更改代码 3) 拥有更改代码的许可证 4) 重新设计代码是可行的,而无需重新认证最终系统。 OP 对 TOCTOU 的情况只字未提。如果这是他的意图,那么他就不会问一个不可能的问题。我对 jxh 的帖子投了赞成票,它提供了一个只有 一个 条件的工作解决方案,即应用程序在 Linux 上运行。
【解决方案4】:

我对 Windows 上的多线程方式知之甚少,但如果您使用的是 Linux,那么您有很多选择。 Here 是一个FANTASTIC 资源。您还可以利用任何file-locking features offered inherently or explicitly by the OS(例如:fcntl)。更多关于 Linux 锁的信息 here。创建和手动管理您自己的互斥锁为您提供了比其他方式更大的灵活性。 user814064flock() 的评论看起来是完美的解决方案,但拥有选择永远不会有坏处!

添加了代码示例:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

FILE *fp;
int counter;
pthread_mutex_t fmutex = PTHREAD_MUTEX_INITIALIZER;

void *foo() {
        // pthread_mutex_trylock() checks if the mutex is
        // locked without blocking
        //int busy = pthread_mutex_trylock(&fmutex);

        // this blocks until the lock is released
        pthread_mutex_lock(&fmutex);
        fprintf(fp, "counter = %d\n", counter);
        printf("counter = %d\n", counter);
        counter++;
        pthread_mutex_unlock(&fmutex);
}

int main() {

        counter = 0;
        fp = fopen("threads.txt", "w");

        pthread_t thread1, thread2;

        if (pthread_create(&thread1, NULL, &foo, NULL))
                printf("Error creating thread 1");
        if (pthread_create(&thread2, NULL, &foo, NULL))
                printf("Error creating thread 2");

        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);

        fclose(fp);
        return 0;
}

【讨论】:

  • 已修复。谢谢和抱歉。
【解决方案5】:

如果您需要确定另一个线程是否打开了文件而不知道文件已经打开,那么您可能做错了。

在多线程应用程序中,您希望在所有线程可访问的列表中管理共同使用的资源。该列表需要以多线程安全的方式进行管理。这只是意味着您需要锁定mutex,对列表执行操作,然后解锁mutex。此外,由多个线程读取/写入文件可能非常复杂。同样,您需要锁定才能安全地执行此操作。在大多数情况下,将文件标记为“忙碌”(即线程正在使用该文件)并等待文件“就绪”(即没有线程正在使用它)会容易得多。

所以假设你有一种链表实现形式,你可以用类似的方式来搜索列表:

my_file *my_file_find(const char *filename)
{
    my_file *l, *result = NULL;

    pthread_mutex_lock(&fmutex);

    l = my_list_of_files;

    while(l != NULL)
    {
         if(strcmp(l->filename, filename) == 0)
         {
             result = l;
             break;
         }
         l = l->next;
    }

    pthread_mutex_unlock(&fmutex);

    return result;
}

如果函数返回NULL,则在搜索时没有其他线程打开文件(由于互斥锁已解锁,另一个线程可能在函数执行return之前打开了文件)。如果您需要以安全的方式打开文件(即只有一个线程可以打开文件filename),那么您需要有一个my_file_open() 函数来锁定、搜索、添加一个新的my_file 如果没有找到,然后返回新添加的my_file 指针。如果文件已经存在,那么my_file_open() 可能会返回NULL,这意味着它无法打开一个线程可以使用的文件(即另一个线程已经在使用它)。

请记住,您无法解锁搜索和添加之间的互斥锁。所以你不能使用上面的my_file_find()函数,除非你先锁定你的互斥锁(在这种情况下你可能想要递归互斥锁)。

换句话说,只有先锁定互斥锁,完成所有工作,然后解锁互斥锁,您才能搜索现有列表、扩大现有列表并缩小(也就是关闭文件)。

这适用于任何类型的资源,而不仅仅是文件。它可以是内存缓冲区、图形界面小部件、USB 端口等。

【讨论】:

    猜你喜欢
    • 2012-08-30
    • 1970-01-01
    • 2019-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多