如何在C语言中检查文件是否已经被打开

11

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

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


1
你需要一个物理锁,或者在线程之间定义一个信号量(驻留在共享内存中?) - CppLearner
3
最简单的方法是保持一个共享的所有打开文件的列表,并使其对所有线程都可用。 - Sergey Kalinichenko
2
最简单的方法是自己跟踪它。 - Duck
2
顺便提一下:如果您想检查文件是否已经被任何其他进程或线程打开,可以尝试获取文件写入租约(fcntl(fileno(stream),F_SETLEASE,F_WRLCK))。如果文件已经被其他人打开,它将失败。这仅适用于用户拥有的普通文件。如果其他人尝试打开该文件,则租赁所有者将收到一个信号,并且在/proc/sys/fs/lease_break_time秒内可以操作文件并释放或降级租约,然后才能进行其他打开(但即使您重命名或取消链接该文件,它最终也会进行)。 - Nominal Animal
1
无论这个问题旨在解决什么,它实际上并没有解决它 - 你只能找出文件是否被打开 - 而且这可能随时改变(甚至 你正在收集数据时...)。所以你得到的任何答案都是立即过时和无效的。使用在这里找到的任何答案都会发生 TOCTOU bug。根据维基链接: "TOCTOU 竞争条件在 Unix 文件系统操作之间很常见。" 不仅仅是 Unix - Windows 文件系统也一样。 - Andrew Henle
显示剩余3条评论
5个回答

4
要查看在上是否已经打开了一个指定的文件,您可以扫描/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;

根据您的评论,似乎您想要做的是如果先前通过调用fopen()创建了一个文件,则检索现有的FILE *。标准C库没有提供遍历所有当前打开的FILE *的机制。如果有这样的机制,您可以使用fileno()获取其文件描述符,然后像上面显示的那样使用readlink()查询/proc/self/fd/#
这意味着您需要使用数据结构来管理打开的FILE *。可能使用文件名作为键的哈希表对您最有用。

我正在使用文件指针而不是描述符。因此,这个解决方案对我没有帮助。 - user503403
5
有一个名为fileno(FILE)的宏,它可以从文件指针返回文件描述符。然而,我不会扫描/proc/self/fd,因为它不是原子操作——在扫描/proc/self/fd时,其他线程仍然可以关闭或打开文件。 - mvp
请在https://github.com/lsof-org/lsof上查看lsof源代码。 - Jonathan Ben-Avraham

2

您可以使用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应该可以在多线程应用程序中正常工作: 多个线程能够同时获取flock

关于flock及其潜在弱点,这里有更多信息点击此处


不幸的是,如果其他进程试图在我们的文件上放置锁定,它也会影响到它们。提问者似乎想要仅线程锁定。 - mvp
好的陈述。我认为他在做fork/exec,但可能不是。 - dcaswell
这篇帖子没有回答OP提出的问题。 - Jonathan Ben-Avraham
@JonathanBen-Avraham 任何实际回答那个问题都是立即无用的 - 它只告诉你文件是否已经被打开,而不是它是否仍然处于打开状态。尝试使用问题的任何答案实际上都是TOCTOU bug - Andrew Henle
@AndrewHenle 我评论的重点是,这篇文章只回答了一个退化情况,即您1)可以访问应用程序代码2)可以构建代码3)获得管理许可更改代码3)有许可证更改代码4)重新设计代码而无需重新认证最终系统是可行的。 OP没有提到TOCTOU情况。如果那是意图,那么他不会问一个不可能的问题。我赞成jxh的帖子,它提供了一个工作解决方案,只有一个条件,即应用程序在Linux上运行。 - Jonathan Ben-Avraham

2
如果你想在shell中执行此操作,可以简单地使用lsof $filename命令。

1
这是本质上不安全的线程 - 一旦它返回,答案就可能会改变。 - Stewart
1
@Stewart 实际上,答案在运行时可能会改变... - Alexis Wilke
@AlexisWilke 是的。这里发布的任何检查文件是否被另一个线程或进程打开的检查都可以在检查运行时更改,并且随后立即无用。 - Andrew Henle

1

我对Windows上的多线程不太了解,但如果你在Linux上,你有很多选择。这里是一个绝妙的资源。你还可以利用操作系统固有或明确提供的文件锁定功能(例如:fcntl)。更多关于Linux锁定的内容请参见此处。创建和手动管理自己的互斥锁比你通常拥有的灵活性更高。user814064关于使用flock()看起来是完美的解决方案,但保留其他选择从来不会有坏处!

添加了一个代码示例:

#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;
}

已修复。谢谢并抱歉。 - xikkub

0

如果您需要确定另一个线程是否打开了一个文件,而不是知道该文件已经被打开,那么您可能正在错误的方式中进行操作。

在多线程应用程序中,您希望管理所有线程都可以访问的公共资源列表。该列表需要以多线程安全的方式进行管理。这意味着您需要锁定一个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 端口等。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接