[英]How do I expose custom files similar to /procfs on Linux?
我有一个wchar_t
的可读块。 我需要确保以下属性:
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.