【问题标题】:Why can I not mmap /proc/self/maps?为什么我不能 mmap /proc/self/maps?
【发布时间】:2021-02-16 13:37:00
【问题描述】:

具体来说:为什么我可以这样做:

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?

【问题讨论】:

  • 您可能无法映射 任何 假文件。因为那样的话内核将很难完成这项工作,因为它们不是真正的文件。
  • 是的,内核为read 动态生成内容。这不是秘密。这就是“不是真正的文件”

标签: c linux linux-kernel mmap


【解决方案1】:

它们是在您阅读它们时即时创建的。也许这会有所帮助,这是一个展示如何实现 proc 文件的教程:

https://devarea.com/linux-kernel-development-creating-a-proc-file-and-interfacing-with-user-space/

tl;dr:你给它一个名字并读写处理程序,就是这样。从内核开发人员的角度来看,Proc 文件的实现非常简单。但它们的行为不像功能齐全的文件。

至于附加问题,似乎没有办法指示文件的大小,只有读取时的 EOF。

【讨论】:

  • 很不幸 ;(
【解决方案2】:

proc“文件”并不是真正的文件,它们只是可以读取/写入的流,但它们在内存中不包含您可以映射到的物理数据。

https://tldp.org/LDP/Linux-Filesystem-Hierarchy/html/proc.html

【讨论】:

    【解决方案3】:

    正如其他人已经解释的那样,/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 句柄”)到/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:您需要重新校准您的疼痛传感器。你看,那些“不必要的”重新分配和副本正是使该方法健壮和可靠的原因。例如,考虑两个大矩阵的乘法,比如 256×256。您可以随心所欲地优化计算,但是创建其中一个矩阵的副本,以便最里面的求和积循环访问内存中连续的元素将提高效率,这是您无法以任何其他方式获得的。换句话说,复制和重新分配是你的朋友,而不是你的敌人。
    • 我很清楚这个技巧。但是不必要的副本和分配是万恶之源。
    • @Fl0wless:这不是技巧。算法优化每次都击败微优化。万恶之源是过早的优化,以及完全以错误的规模进行优化。此外,如果你真的分析上面的代码,你会发现上面的代码做的副本很少;通常没有。你只是专注于完全错误的事情,假设你的意图是编写高效可靠的 C 代码。如果不是,那么,无论如何,祝你好运。
    • 将行优先复制到列优先顺序是不是算法优化。这是一个关于数据访问模式的问题。 (至少我是这么说的)
    猜你喜欢
    • 2010-11-26
    • 1970-01-01
    • 2011-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-22
    • 2016-04-05
    • 2015-03-20
    相关资源
    最近更新 更多