为什么我不能 mmap /proc/self/maps?

问题描述

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

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* 版本定义了一些大小和内容。它是秘密地即时生成以供阅读之类的吗?我在这里错过了什么?

编辑: 因为我可以清楚地从他们那里 read() ,有没有办法获得可能的可用字节?还是我一直读到 EOF?

解决方法

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

blog

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

关于奖金问题,似乎没有办法表明文件的大小,只有阅读时的EOF。

,

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

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

,

正如其他人已经解释过的,/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,32);
    else
        memset(data + have,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","/proc/self/maps: %s.\n",strerror(errno));
        exit(EXIT_FAILURE);
    }
    printf("/proc/self/maps: %zu bytes\n%s\n",data);

    close(selfdir);

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

    return EXIT_SUCCESS;
}

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

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

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

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

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

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

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...