繁体   English   中英

如何在 Linux 上公开类似于 /procfs 的自定义文件?

[英]How do I expose custom files similar to /procfs on Linux?

我有一个wchar_t可读块 我需要确保以下属性:

  1. 当有更新时,读者不应该阅读部分/损坏的数据
  2. 该文件在 memory 中应该是 volatile 的,这样当编写器退出时,文件就消失了
  3. 文件内容大小可变
  4. 多个阅读器可以并行读取文件,内容是否同步无关紧要,只要每个客户端都不是部分的
  5. 如果使用truncate然后write ,客户端应该只读取整个文件而不观察这样的部分操作

我如何在/procfs文件系统之外实现这样的/procfs类文件?

我正在考虑使用经典的 c Linux文件 API 并默认在/dev/shm下创建一些东西,但我发现很难有效地实现第 1 点和第 5 点。 我怎么能公开这样的文件?

典型的解决方案是在同一目录中创建一个新文件,然后在旧文件上重命名(硬链接)它。

这样,进程要么看到旧的要么看到新的,而不是混合的; 它只取决于他们打开文件的时刻。

Linux kernel 负责缓存,因此如果经常访问文件,它将位于 RAM(页面缓存)中。 但是,作者必须记住在文件退出时删除文件。


更好的方法是使用基于fcntl()的咨询记录锁(通常在整个文件上,即.l_whence = SEEK_SET.l_start = 0.l_len = 0 )。

作者将在截断和重写内容之前获取一个写/排他锁,而读者在读取内容之前将获取一个读/共享锁。

但是,这需要合作,并且编写者必须准备好无法锁定(或者抓住锁定可能需要不确定的时间)。


仅限 Linux 的方案是使用原子替换(通过重命名/硬链接)和文件租用。

(当写入进程对打开的文件有独占租约时,只要另一个进程想要打开同一个文件(inode,而不是文件名),它就会收到一个信号。它至少有几秒钟的时间来降级或释放租约,在哪一点开启者可以访问内容。)

基本上,写进程创建一个空的状态文件,并获得它的独占租约。 每当写入器接收到读取器想要访问状态文件的信号时,它将当前状态写入文件,释放租约,在与状态文件相同的目录(相同的挂载即可)中创建一个新的空文件,获得一个独占租约,并通过状态文件重命名/硬链接它。

如果状态文件的内容不是一直改变,只是周期性地改变,那么写入进程会创建一个空的状态文件,并获得对它的独占租约。 每当写入器接收到读取器想要访问(空)状态文件的信号时,它就会将当前状态写入文件,并释放租约。 然后,当写入进程的状态更新,并且还没有租约时,它会在状态文件目录中创建一个新的空文件,对其进行独占租约,并在状态文件上重命名/硬链接。

这样,状态文件总是在读者打开它之前更新,而且只有在那时。 如果同时有多个读取器,则写入器释放租约时,它们可以不间断地打开状态文件。

需要注意的是,状态信息应该收集在单个结构或类似结构中,以便将其写入状态文件是有效的。 如果没有尽快释放租约(但至少有几秒钟的反应时间),租约会自动中断,并且租约在 inode 上——文件内容——而不是文件名,所以我们仍然需要原子替换。

这是一个粗略的示例实现:

#define _POSIX_C_SOURCE  200809L
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdarg.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <signal.h>
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

#define   LEASE_SIGNAL  (SIGRTMIN+0)

static pthread_mutex_t  status_lock = PTHREAD_MUTEX_INITIALIZER;
static int              status_changed = 0;
static size_t           status_len = 0;
static char            *status = NULL;

static pthread_t        status_thread;
static char            *status_newpath = NULL;
static char            *status_path = NULL;
static int              status_fd = -1;
static int              status_errno = 0;

char *join2(const char *src1, const char *src2)
{
    const size_t  len1 = (src1) ? strlen(src1) : 0;
    const size_t  len2 = (src2) ? strlen(src2) : 0;
    char         *dst;

    dst = malloc(len1 + len2 + 1);
    if (!dst) {
        errno = ENOMEM;
        return NULL;
    }

    if (len1 > 0)
        memcpy(dst, src1, len1);
    if (len2 > 0)
        memcpy(dst+len1, src2, len2);
    dst[len1+len2] = '\0';

    return dst;
}

static void *status_worker(void *payload __attribute__((unused)))
{
    siginfo_t info;
    sigset_t  mask;
    int       err, num;

    /* This thread blocks all signals except LEASE_SIGNAL. */
    sigfillset(&mask);
    sigdelset(&mask, LEASE_SIGNAL);
    err = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (err)
        return (void *)(intptr_t)err;

    /* Mask for LEASE_SIGNAL. */
    sigemptyset(&mask);
    sigaddset(&mask, LEASE_SIGNAL);

    /* This thread can be canceled at any cancellation point. */
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

    while (1) {
        num = sigwaitinfo(&mask, &info);
        if (num == -1 && errno != EINTR)
            return (void *)(intptr_t)errno;

        /* Ignore all but the lease signals related to the status file. */
        if (num != LEASE_SIGNAL || info.si_signo != LEASE_SIGNAL || info.si_fd != status_fd)
            continue;

        /* We can be canceled at this point safely. */
        pthread_testcancel();

        /* Block cancelability for a sec, so that we maintain the mutex correctly. */
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

        pthread_mutex_lock(&status_lock);        
        status_changed = 0;

        /* Write the new status to the file. */
        if (status && status_len > 0) {
            const char        *ptr = status;
            const char *const  end = status + status_len;
            ssize_t            n;

            while (ptr < end) {
                n = write(status_fd, ptr, (size_t)(end - ptr));
                if (n > 0) {
                    ptr += n;
                } else
                if (n != -1) {
                    if (!status_errno)
                        status_errno = EIO;
                    break;
                } else
                if (errno != EINTR) {
                    if (!status_errno)
                        status_errno = errno;
                    break;
                }
            }
        }

        /* Close and release lease. */
        close(status_fd);
        status_fd = -1;

        /* After we release the mutex, we can be safely canceled again. */
        pthread_mutex_unlock(&status_lock);
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

        pthread_testcancel();
    }
}

static int start_status_worker(void)
{
    sigset_t          mask;
    int               result;
    pthread_attr_t    attrs;

    /* This thread should block LEASE_SIGNAL signals. */
    sigemptyset(&mask);
    sigaddset(&mask, LEASE_SIGNAL);
    result = pthread_sigmask(SIG_BLOCK, &mask, NULL);
    if (result)
        return errno = result;

    /* Create the worker thread. */
    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 2*PTHREAD_STACK_MIN);
    result = pthread_create(&status_thread, &attrs, status_worker, NULL);
    pthread_attr_destroy(&attrs);

    /* Ready. */
    return 0;
}

int set_status(const char *format, ...)
{
    va_list  args;
    char    *new_status = NULL;
    int      len;

    if (!format)
        return errno = EINVAL;

    va_start(args, format);
    len = vasprintf(&new_status, format, args);
    va_end(args);
    if (len < 0)
        return errno = EINVAL;

    pthread_mutex_lock(&status_lock);
    free(status);
    status = new_status;
    status_len = len;
    status_changed++;

    /* Do we already have a status file prepared? */
    if (status_fd != -1 || !status_newpath) {
        pthread_mutex_unlock(&status_lock);
        return 0;
    }

    /* Prepare the status file. */
    do {
        status_fd = open(status_newpath, O_WRONLY | O_CREAT | O_CLOEXEC, 0666);
    } while (status_fd == -1 && errno == EINTR);
    if (status_fd == -1) {
        pthread_mutex_unlock(&status_lock);
        return 0;
    }

    /* In case of failure, do cleanup. */
    do {
        /* Set lease signal. */
        if (fcntl(status_fd, F_SETSIG, LEASE_SIGNAL) == -1)
            break;

        /* Get exclusive lease on the status file. */
        if (fcntl(status_fd, F_SETLEASE, F_WRLCK) == -1)
            break;

        /* Replace status file with the new, leased one. */
        if (rename(status_newpath, status_path) == -1)
            break;

        /* Success. */
        pthread_mutex_unlock(&status_lock);
        return 0;
    } while (0);

    if (status_fd != -1) {
        close(status_fd);
        status_fd = -1;
    }
    unlink(status_newpath);

    pthread_mutex_unlock(&status_lock);
    return 0;
}


int main(int argc, char *argv[])
{
    char   *line = NULL;
    size_t  size = 0;
    ssize_t len;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *argv0 = (argc > 0 && argv[0]) ? argv[0] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
        fprintf(stderr, "       %s STATUS-FILE\n", argv0);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program maintains a pseudofile-like status file,\n");
        fprintf(stderr, "using the contents from standard input.\n");
        fprintf(stderr, "Supply an empty line to exit.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    status_path = join2(argv[1], "");
    status_newpath = join2(argv[1], ".new");
    unlink(status_path);
    unlink(status_newpath);

    if (start_status_worker()) {
        fprintf(stderr, "Cannot start status worker thread: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    if (set_status("Empty\n")) {
        fprintf(stderr, "Cannot create initial empty status: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    while (1) {
        len = getline(&line, &size, stdin);
        if (len < 1)
            break;

        line[strcspn(line, "\n")] = '\0';
        if (line[0] == '\0')
            break;

        set_status("%s\n", line);
    }

    pthread_cancel(status_thread);
    pthread_join(status_thread, NULL);

    if (status_fd != -1)
        close(status_fd);

    unlink(status_path);
    unlink(status_newpath);

    return EXIT_SUCCESS;
}

将以上内容另存为server.c ,然后使用 eg 编译

gcc -Wall -Wextra -O2 server.c -lpthread -o server

这实现了一个状态服务器,如有必要,将标准输入中的每一行存储到状态文件中。 提供一个空行退出。 例如,要使用当前目录中的文件status ,只需运行

./server status

然后,如果您使用另一个终端 window 来检查目录,您会看到它有一个名为status的文件(通常大小为零)。 但是, cat status会显示它的内容; 就像 procfs/sysfs 伪文件一样。

请注意,状态文件仅在必要时更新,并且仅适用于状态更改后的第一个读取器/访问器。 即使状态经常发生变化,这也会使写入器/服务器开销和 I/O 保持在较低水平。

上面的示例程序使用一个工作线程来捕获租约中断信号。 这是因为 pthread 互斥锁无法在信号处理程序中安全地锁定或释放( pthread_mutex_lock()等不是异步信号安全的)。 工作线程保持它的可取消性,这样当它持有互斥体时就不会被取消; 如果在此期间取消,则在释放互斥锁后将被取消。 那样小心谨慎。

此外,临时替换文件不是随机的,它只是在末尾附加.new的状态文件名。 在同一个底座上的任何地方都可以正常工作。

只要其他线程也阻塞租约中断信号,这在多线程程序中也可以正常工作。 (如果您在工作线程之后创建其他线程,它们将从主线程继承正确的信号掩码; start_status_worker()为调用线程设置信号掩码。)

我确实相信程序中的方法,但在这个实现中可能存在错误(甚至可能是 thinkos)。 如果您发现任何内容,请发表评论或编辑。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM