为什么我无法mmap /proc/self/maps?

3
具体来说,为什么我能够这样做:
FILE *fp = fopen("/proc/self/maps", "r");
char buf[513]; buf[512] = NULL;
while(fgets(buf, 512, fp) > NULL) printf("%s", buf);

但不包括这个:
int fd = open("/proc/self/maps", O_RDONLY);
struct stat s;
fstat(fd, &s); // st_size = 0 -> why?
char *file = mmap(0, s.st_size /*or any fixed size*/, PROT_READ, MAP_PRIVATE, fd, 0); // gives EINVAL for st_size (because 0) and ENODEV for any fixed block
write(1, file, st_size);

我知道/proc文件并不是真正的文件,但是它似乎对于FILE *版本有一些定义好的大小和内容。它是在读取时偷偷地动态生成的吗?我错过了什么吗?

编辑: 由于我可以从中明显读取(),是否有任何方式可以获取可能可用的字节数?还是我只能一直读到EOF?


https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html - Devolus
你可能无法 mmap 任何虚假文件。因为内核会很难使其工作,因为它们不是真正的文件。 - user253751
是的,内核会即时生成“读取”操作的内容。这并不是秘密。这就是所谓的“不真实文件”的含义。 - user253751
3个回答

2
它们在阅读时动态创建。也许这篇教程会有所帮助,它展示了如何实现 proc 文件:https://devarea.com/linux-kernel-development-creating-a-proc-file-and-interfacing-with-user-space/。简而言之,您只需给它一个名称和读写处理程序即可。从内核开发人员的角度来看,proc 文件应该非常容易实现。但是,它们不像完整功能的文件那样运作。至于奖励问题,似乎没有办法指示文件的大小,只能在读取时使用 EOF。

相当不幸 ;( - Fl0wless

1

0

正如其他人所解释的那样,/proc/sys是伪文件系统,由内核提供的数据组成,直到读取时实际上并不存在 - 内核生成该数据。由于大小不同,并且在打开文件进行读取之前确实未知,因此根本不向用户空间提供。

然而,这并不是“不幸的”。同样的情况经常发生,例如字符设备(在/dev下),管道,FIFO(命名管道)和套接字。

我们可以轻松编写一个帮助函数,使用动态内存管理完全读取伪文件。例如:

// SPDX-License-Identifier: CC0-1.0
//
#define  _POSIX_C_SOURCE  200809L
#define  _ATFILE_SOURCE
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>

/* For example main() */
#include <stdio.h>

/* Return a directory handle for a specific relative directory.
   For absolute paths and paths relative to current directory, use dirfd==AT_FDCWD.
*/
int at_dir(const int dirfd, const char *dirpath)
{
    if (dirfd == -1 || !dirpath || !*dirpath) {
        errno = EINVAL;
        return -1;
    }
    return openat(dirfd, dirpath, O_DIRECTORY | O_PATH | O_CLOEXEC);
}

/* Read the (pseudofile) contents to a dynamically allocated buffer.
   For absolute paths and paths relative to current durectory, use dirfd==AT_FDCWD.
   You can safely initialize *dataptr=NULL,*sizeptr=0 for dynamic allocation,
   or reuse the buffer from a previous call or e.g. getline().
   Returns 0 with errno set if an error occurs.  If the file is empty, errno==0.
   In all cases, remember to free (*dataptr) after it is no longer needed.
*/
size_t read_pseudofile_at(const int dirfd, const char *path, char **dataptr, size_t *sizeptr)
{
    char   *data;
    size_t  size, have = 0;
    ssize_t n;
    int     desc;

    if (!path || !*path || !dataptr || !sizeptr) {
        errno = EINVAL;
        return 0;
    }

    /* Existing dynamic buffer, or a new buffer? */
    size = *sizeptr;
    if (!size)
        *dataptr = NULL;
    data = *dataptr;

    /* Open pseudofile. */
    desc = openat(dirfd, path, O_RDONLY | O_CLOEXEC | O_NOCTTY);
    if (desc == -1) {
        /* errno set by openat(). */
        return 0;
    }

    while (1) {

        /* Need to resize buffer? */
        if (have >= size) {
            /* For pseudofiles, linear size growth makes most sense. */
            size = (have | 4095) + 4097 - 32;
            data = realloc(data, size);
            if (!data) {
                close(desc);
                errno = ENOMEM;
                return 0;
            }
            *dataptr = data;
            *sizeptr = size;
        }

        n = read(desc, data + have, size - have);
        if (n > 0) {
            have += n;
        } else
        if (n == 0) {
            break;
        } else
        if (n == -1) {
            const int  saved_errno = errno;
            close(desc);
            errno = saved_errno;
            return 0;
        } else {
            close(desc);
            errno = EIO;
            return 0;
        }
    }

    if (close(desc) == -1) {
        /* errno set by close(). */
        return 0;
    }

    /* Append zeroes - we know size > have at this point. */
    if (have + 32 > size)
        memset(data + have, 0, 32);
    else
        memset(data + have, 0, size - have);

    errno = 0;
    return have;
}

int main(void)
{
    char   *data = NULL;
    size_t  size = 0;
    size_t  len;
    int     selfdir;

    selfdir = at_dir(AT_FDCWD, "/proc/self/");
    if (selfdir == -1) {
        fprintf(stderr, "/proc/self/ is not available: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }

    len = read_pseudofile_at(selfdir, "status", &data, &size);
    if (errno) {
        fprintf(stderr, "/proc/self/status: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    printf("/proc/self/status: %zu bytes\n%s\n", len, data);

    len = read_pseudofile_at(selfdir, "maps", &data, &size);
    if (errno) {
        fprintf(stderr, "/proc/self/maps: %s.\n", strerror(errno));
        exit(EXIT_FAILURE);
    }
    printf("/proc/self/maps: %zu bytes\n%s\n", len, data);

    close(selfdir);

    free(data); data = NULL; size = 0;

    return EXIT_SUCCESS;
}

上面的示例程序打开了一个目录描述符(“atfile handle”)到/proc/self。(这样你就不需要连接字符串来构建路径。)

然后它读取了/proc/self/status的内容。如果成功,它会显示其大小(以字节为单位)和其内容。

接下来,它重用先前的缓冲区读取/proc/self/maps的内容。如果成功,它也会显示其大小和内容。

最后,由于不再需要,关闭目录描述符,并释放动态分配的缓冲区。

请注意,执行free(NULL)是完全安全的,且在read_pseudofile_at()调用之间丢弃动态缓冲区(free(data); data=NULL; size=0;)也是安全的。

由于伪文件通常很小,read_pseudofile_at()使用线性动态缓冲区增长策略。如果没有先前的缓冲区,它将从8160个字节开始,并在之后每次增加4096个字节,直到足够大。随意用您喜欢的增长策略替换它,这只是一个示例,但在实践中效果非常好,不会浪费太多内存。


谢谢,但是不得不重新分配缓冲区正是我所说的不幸之处。"不必要地"重新分配千字节的数据(随后复制文件的一小部分)只是让我个人感到痛苦(这就是为什么我首先想要将其映射到内存中)。 - Fl0wless
@Fl0wless:你需要重新校准你的疼痛感应器。你看,那些“不必要”的重新分配和复制正是使方法强大可靠的原因。例如,考虑两个大矩阵的乘法,比如256×256。你可以尽可能地优化计算,但是创建一个矩阵的副本,使得最内层的求和乘积循环访问连续的内存元素,将会给你带来一种效率提升,这是你无法通过其他方式获得的。换句话说,复制和重新分配是你的朋友,而不是敌人。 - Glärbo
我非常了解那个技巧。但是不必要的复制和分配是万恶之源。 - Fl0wless
@Fl0wless:这不是诡计。算法优化在每个时间段都能战胜微观优化。所有罪恶的根源是过早的优化,并且完全错误的尺度优化。此外,如果你实际运用代码分析工具对上述代码进行分析,你会发现该代码几乎不需要进行复制,有时甚至不需要。你只是关注了完全错误的事情,假设您的目的是编写高效可靠的C代码。如果不是这样,那么祝您好运。 - Glärbo
将行优先顺序复制到列优先顺序并不是一种算法优化。这是一个关于数据访问模式的问题。(至少这就是我所谈论的) - Fl0wless
@Fl0wless:这不是算法优化,而是一种模式,其中副本可以产生更好的性能。当副本允许强大、高效的实现时,它们并不是邪恶的。反复动态重新分配缓冲区的读取是一种强大、高效的实现方式:对其进行分析;重新分配成本消失在测量噪声中。你认为不必要的副本并非如此。因此,建议重新校准您所看到的痛苦或邪恶。 - Glärbo

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