代码之家  ›  专栏  ›  技术社区  ›  wick

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

  •  1
  • wick  · 技术社区  · 10 年前

    我需要检查系统上的任何其他进程是否打开了文件。正在扫描 /proc 出于性能考虑,不是一个选项。

    一种优雅的方式是阅读 i_count 内核的成员 struct inode ,对应于文件。

    我有一个用户空间中文件的fd,我怎么能得到 i_计数 共个关联 结构索引节点 从内核空间来看,有什么想法吗?

    4 回复  |  直到 10 年前
        1
  •  2
  •   Nominal Animal    10 年前

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

    写释放.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 信号),发送一个 TERM 信号,或使用其他程序打开文件。例如,如果您开始 更少的writelease.c 在另一个窗口中,写入租约程序将输出

    Process 1089 is opening the file. Releasing the lease.
    

    然后退出。

    局限性 :

    如上所述,这只能可靠地检测文件是否被另一个进程打开或截断。如果文件或其任何父目录被重命名,则不会捕获该文件。您需要将fanotify或dnotify手表添加到目录(以及直到挂载点的父目录)以捕获这些手表。当然,这些只会发生 事后诸葛亮 ,与文件租用信号不同。然而,对于原木旋转,这不应该是一个问题。

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

    当另一个进程试图在您有写租约的情况下打开文件时,无法无限期地阻止打开。(当然,您可以检测到试图打开它的进程,然后发送它 SIGKILL 杀死它,但那将是极其残忍的。事实上,我自己都没试过,所以我不确定租约在宽限期后是否会被破坏, /proc/sys/fs/lease-break-time 秒,或不。)(但是,我个人在放弃租约之前已经将文件截断为零字节。)

    即使租约所有者重命名文件,或将其移动到同一装载上的另一个目录中,打开程序仍将打开(重命名/移动的)文件。从根本上说,当租约信号发出时,开放已经在进行中,我们只能延迟几秒钟,而不是取消它。

        2
  •  1
  •   user4822941 user4822941    10 年前

    我建议你描述一下实际的问题。

    通常,由于文件可以随时打开,因此检查结果会立即失效。此外,即使没有用户空间用户,i_count也会被删除。

        3
  •  0
  •   kaiwan    10 年前

    你试过用吗 lsof公司 ,用户空间实用程序? A tutorial .

        4
  •  0
  •   Abhishek Sagar    9 年前

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

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

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

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

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

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

    这就是如何确保每个open()只有一个release()调用。

    因此,在某种程度上,它不是索引节点->icount,但filp->f_count,表示共享同一文件描述符的进程数。