从用户空间获取打开文件的引用计数(inode-> i_count)

时间:2016-05-07 07:47:06

标签: c linux linux-kernel kernel

我需要检查系统上的任何其他进程是否打开了文件。由于性能方面的考虑,扫描/proc不是一种选择。

一种优雅的方式是阅读与该文件对应的内核i_count的{​​{1}}成员。

我在用户空间中有一个fd用于文件,我怎么能从内核空间获得struct inode的{​​{1}},任何想法?

4 个答案:

答案 0 :(得分:1)

我建议你描述实际问题。

一般情况下,由于文件可以随时打开,因此检查结果会立即失效。尽管没有用户空间用户,i_count也可以被碰撞。

答案 1 :(得分:1)

这不是对所述问题的回答,而是一个示例程序,如何使用特定于Linux的文件租约来可靠地检测何时对本地文件的访问在当前机器上是独占的;也就是说,该文件未被同一台机器上的任何其他进程打开。授予写入租约后,当任何其他进程尝试打开写入租用文件时,将通知原始进程。

<强> writelease.c

#define  _POSIX_C_SOURCE 200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

#define LEASE_SIGNAL (SIGRTMIN+0)

int main(int argc, char *argv[])
{
    sigset_t  signals;
    siginfo_t info;
    int       fd, result;

    sigemptyset(&signals);
    sigaddset(&signals, LEASE_SIGNAL);
    sigaddset(&signals, SIGINT);
    sigaddset(&signals, SIGTERM);

    if (sigprocmask(SIG_BLOCK, &signals, NULL) == -1) {
        fprintf(stderr, "Cannot block signals: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s FILENAME\n", argv[0]);
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    /* Open file. Should not be interrupted, but let's be overly paranoid. */
    do {
        fd = open(argv[1], O_RDONLY | O_NOCTTY);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    /* This should not be interrupted ever, but let's again be overly paranoid. */
    do {
        result = fcntl(fd, F_SETSIG, LEASE_SIGNAL);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "%s: Cannot change signal: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    /* Try to get a write lease on the file. */
    do {
        result = fcntl(fd, F_SETLEASE, F_WRLCK);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "%s: Cannot get a write lease: %s.\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }

    printf("%s: Write lease obtained; this process (%ld) is the only one with an open description to it.\n",
           argv[1], (long)getpid());
    fflush(stdout);

    /* Wait for the first signal. */
    do {
        info.si_fd = -1;
        result = sigwaitinfo(&signals, &info);
    } while (result == -1 && errno == EINTR);
    if (result == -1) {
        fprintf(stderr, "Uh-oh. sigwaitinfo() failed; this should never occur. %s.\n", strerror(result));
        return EXIT_FAILURE;
    }

    if (result == LEASE_SIGNAL && info.si_fd == fd)
        printf("Process %ld is opening the file. Releasing the lease.\n", (long)info.si_pid);
    else
        printf("Received signal %d (%s); exiting.\n", result, strsignal(result));
    fflush(stdout);

    /* Closing the file descriptor releases the lease.
     * At exit, the kernel will do this for us, so explicit close() here
     * is not necessary. Again, just being overly pedantic and careful. */
    close(fd);

    return EXIT_SUCCESS;
}

使用

编译上述内容
gcc -std=c99 -Wall -Wextra -O2 writelease.c -o writelease

普通用户只能租用他们拥有的文件。但是,不需要完整的超级用户权限来租用任意文件; cap_lease能力就足够了。在大多数当前的Linux发行版中,您可以使用

sudo setcap cap_lease=pe writelease

将功能添加到二进制文件中;但是,这意味着任何用户都可以运行它,并对他们希望的任何文件进行写入租约。 (除非你首先审查程序,为了确保没有安全风险,你不应该这样做!但是,在你自己的系统上进行测试非常好。)

在一个终端窗口中,租用某个文件,可能在writelease.c文件上:

./writelease writelease.c

如果文件没有被任何进程打开,它将输出类似

的内容
writelease.c: Write lease obtained; this process (5782) is the only one with an open description to it.

请注意,许多编辑器(例如gedit)不会永久保持文件打开,并且可能只是使用rename()(或硬链接)将旧文件替换为新文件。那是被文件租约捕获;你需要使用fanotify或dnotify来检测那些。

如果文件已经打开(例如,less writelease.c在另一个窗口中打开),输出将会有所不同,很可能是

writelease.c: Cannot get a write lease: Resource temporarily unavailable.

如果租约成功,您可以使用 Ctrl + C (发送INT信号)中断程序,发送{{1}信号,或使用其他程序打开文件。例如,如果您在另一个窗口中启动TERM,则写入租赁程序将输出

less writelease.c

然后退出。

<强>限制

如上所述,这只能可靠地检测文件是否被另一个进程打开或截断。如果重命名该文件或其任何父目录,则不会捕获该目录。您需要将fanotify或dnotify手表添加到目录(以及直到挂载点的父目录)以捕获这些。当然,那些仅在之后发生,而不是像文件租约信号那样同步。但是,对于日志轮换,这应该不是问题。

只能租用本地文件。对于日志文件,这应该不是问题,因为它们肯定应该是本地的。 (对于远程日志记录,您应该重定向整个syslog,而不是使用网络共享来存储日志文件。)

如果另一个进程在您有写入租约时尝试打开该文件,则无法无限期阻止打开该文件。 (当然,你可以检测试图打开它的过程,并发送它Process 1089 is opening the file. Releasing the lease. 来杀死它,但那将是非常残酷的。事实上,我甚至没有尝试过,所以我是在宽限期SIGKILL秒之后,无论如何都不确定租约是否被破坏。(但我个人已经在重新租赁租约之前将文件截断为零字节。)

即使租约所有者重命名文件,或将其移动到同一安装上的另一个目录中,开启者仍将打开(重命名/移动)文件。在一个非常根本的方面,当发送租约信号时,open已经在进行中,我们只能将其延迟几秒钟,而不是真正取消它。

答案 2 :(得分:0)

您是否尝试使用用户空间实用程序 lsof A tutorial

答案 3 :(得分:0)

当在内核对象(设备文件)上调用Open()系统调用时,内核会创建一个打开的文件描述符结构:struct file *

总是,每次在设备文件上发出open()sys调用时,都会在内核中分配一个新的struct file *。

目标是,对于每个open(),驱动程序中应该只有一个释放方法调用。

如果某个进程在设备文件/内核对象上有open()sys调用问题,则会在内核中创建n个struct文件结构。

现在,如果进程是fork()/ dup(),则不会在内核中创建新的struct文件。仅增加现有结构文件结构的引用计数。此引用计数表示共享文件描述符的进程数。

发出close()时,结构文件的引用计数减少。如果它达到0,则在驱动程序中调用release方法。

确保每个open()只有一个和一个release()调用。

因此,在某种程度上,它不是inode-&gt; icount,而是filp-&gt; f_count,它表示共享相同文件描述符的进程的数量。