Linux 内核进程的 Pagemap 文件夹是否可以被读取(每次读取 64 位)有限次?

Can the Pagemap folder of processes in the Linux kernel be read(64bit per read) a finite number of times?

我试图跟踪文件“proc/PID/pagemap”中每个物理页的写入次数。但是该文件是二进制文件,文件属性中显示的大小为 0,并且以下函数也读取 0。

    struct stat buf;
    int iRet = fstat(fd, &buf);
    if(iRet == -1)
    {
       perror("fstat error");
       exit(-1);
    }
    printf("the size of file is : %ld\n", buf.st_size);

我写了一个监控程序从一个进程的“页面映射”中读取数据一次 64 位并记录 55 位(软脏位)以检查一页是否是 written.Of 在执行此操作之前我清除了进程的 pagemap.This 方法中的所有软脏位都由 linux 内核提供,我在编码过程中的问题是当我使用文件描述符(也尝试过 fstream 指针)从 pagemap.My 获取数据时pagemap 的读取仅在我正在监视的进程完成时结束,就好像文件是 infinite.I 知道进程的逻辑地址管理是动态的但我想知道如何计算写入次数 properly.Should我在固定的时间间隔内阅读了这个无限文件的一部分?我应该阅读多少项目? T_T.

您需要如下内容:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

struct pagemap_region {
    struct pagemap_region *next;
    uintptr_t              addr;    /* First address within region */
    uintptr_t              ends;    /* First address after region */
    size_t                 pages;   /* Number of pages in this region */
    uint64_t               page[];  /* 64-bit pagemap flags per page */
};

static void free_pagemaps(struct pagemap_region *list)
{
    while (list) {
        struct pagemap_region *curr = list;

        list = curr->next;

        curr->addr = 0;
        curr->ends = 0;
        curr->pages = 0;
        free(curr);
    }
}

struct pagemap_region *get_pagemaps(const pid_t pid)
{
    struct pagemap_region *list = NULL;
    size_t   page;
    char    *line_ptr = NULL;
    size_t   line_max = 256;
    ssize_t  line_len;
    FILE    *maps;
    int      n, fd;

    page = sysconf(_SC_PAGESIZE);

    /* We reuse this for the input line buffer. */
    line_ptr = malloc(line_max);
    if (!line_ptr) {
        errno = ENOMEM;
        return NULL;
    }

    /* First, fill it with the path to the map pseudo-file. */
    if (pid > 0)
        n = snprintf(line_ptr, line_max, "/proc/%d/maps", (int)pid);
    else
        n = snprintf(line_ptr, line_max, "/proc/self/maps");
    if (n < 0 || (size_t)n + 1 >= line_max) {
        free(line_ptr);
        errno = EINVAL;
        return NULL;
    }

    /* Read the maps pseudo-file. */
    maps = fopen(line_ptr, "re"); /* Read-only, close-on-exec */
    if (!maps) {
        free(line_ptr);
        errno = ESRCH;
        return NULL;
    }
    while (1) {
        struct pagemap_region *curr;
        unsigned long  addr, ends;
        size_t pages;
        char *ptr, *end;

        line_len = getline(&line_ptr, &line_max, maps);
        if (line_len < 0)
            break;
 
        /* Start address of the region. */
        end = ptr = line_ptr;
        errno = 0;
        addr = strtoul(ptr, &end, 16);
        if (errno || end == ptr || *end != '-')
            break;

        /* End address of the region. */
        ptr = ++end;
        errno = 0;
        ends = strtoul(ptr, &end, 16);
        if (errno || end == ptr || *end != ' ')
            break;

        /* Number of pages in the region. */
        pages = (ends - addr) / page;
        if (addr + page * pages != ends || (addr % page) != 0)
            break;

        /* Allocate new region map. */
        curr = malloc(sizeof (struct pagemap_region) + pages * sizeof curr->page[0]);
        if (!curr)
            break;

        curr->addr = addr;
        curr->ends = ends;
        curr->pages = pages;

        /* Prepend to the region list. */
        curr->next = list;
        list = curr;
    }

    /* Any issues when reading the maps pseudo-file? */
    if (!feof(maps) || ferror(maps)) {
        fclose(maps);
        free(line_ptr);
        free_pagemaps(list);
        errno = EIO;
        return NULL;
    } else
    if (fclose(maps)) {
        free(line_ptr);
        free_pagemaps(list);
        errno = EIO;
        return NULL;
    }

    /* Reuse the line buffer for the pagemap pseudo-file path */
    if (pid > 0) 
        n = snprintf(line_ptr, line_max, "/proc/%d/pagemap", (int)pid);
    else
        n = snprintf(line_ptr, line_max, "/proc/self/pagemap");
    if (n < 0 || (size_t)n + 1 >= line_max) {
        free(line_ptr);
        free_pagemaps(list);
        errno = ENOMEM;
        return NULL;
    }
    do {
        fd = open(line_ptr, O_RDONLY | O_NOCTTY | O_CLOEXEC);
    } while (fd == -1 && errno == EINTR);
    if (fd == -1) {
        n = errno;
        free(line_ptr);
        free_pagemaps(list);
        errno = n;
        return NULL;
    }

    /* Path no longer needed. */
    free(line_ptr);
    line_ptr = NULL;
    line_max = 0;

    /* Read each pagemap section. */
    for (struct pagemap_region *curr = list; curr != NULL; curr = curr->next) {
        off_t          offset = (size_t)(curr->addr / page) * (sizeof curr->page[0]);
        unsigned char *ptr = (unsigned char *)&(curr->page[0]);
        size_t         need = curr->pages * sizeof curr->page[0];
        ssize_t        bytes;

        while (need > 0) {
            bytes = pread(fd, ptr, need, offset);
            if (bytes >= need)
                break;
            else
            if (bytes > 0) {
                ptr += bytes;
                offset += bytes;
                need -= bytes;
            } else
            if (bytes == 0) {
                /* Assume this is a region we can't access, like [VSYSCALL]; clear the rest of the bits. */
                memset(ptr, 0, need);
                break;
            } else
            if (bytes != -1 || errno != EINTR) {
                close(fd);
                free_pagemaps(list);
                errno = EIO;
                return NULL;
            }
        }
    }
    if (close(fd) == -1) {
        free_pagemaps(list);
        errno = EIO;
        return NULL;
    }

    return list;
}

int main(int argc, char *argv[])
{
    struct pagemap_region *list, *curr;
    long  pid;
    char *end;

    if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        const char *argv0 = (argc > 0 && argv && argv[1]) ? argv[1] : "(this)";
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv0);
        fprintf(stderr, "       %s PID\n", argv0);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program prints the a map of the pages of process PID;\n");
        fprintf(stderr, "R for pages in RAM, S for pages in swap space, and . for others.\n");
        fprintf(stderr, "You can use -1 for the PID of this process itself.\n");
        fprintf(stderr, "\n");
        return EXIT_SUCCESS;
    }

    end = argv[1];
    errno = 0;
    pid = strtol(argv[1], &end, 10);
    if (errno || end == argv[1] || *end) {
        fprintf(stderr, "%s: Invalid PID.\n", argv[1]);
        return EXIT_FAILURE;
    }
    if (pid != -1 && (pid < 1 || (long)(pid_t)pid != pid)) {
        fprintf(stderr, "%s: Not a valid PID.\n", argv[1]);
        return EXIT_FAILURE;
    }        
    list = get_pagemaps(pid);
    if (!list) {
        fprintf(stderr, "%s.\n", strerror(errno));
        return EXIT_FAILURE;
    }
    for (curr = list; curr != NULL; curr = curr->next) {       
        printf("Region %p - %p: %zu pages\n", (void *)(curr->addr), (void *)(curr->ends), curr->pages);
        for (uint64_t *map = curr->page; map < curr->page + curr->pages; map++) {
            if ((*map >> 63) & 1)
                putchar('R');
            else
            if ((*map >> 62) & 1)
                putchar('S');
            else
                putchar('.');
        }
        putchar('\n');
    }

    return EXIT_SUCCESS;
}

我们逐行读取/proc/PID/maps,每行构造一个struct pagemap_region;这包含起始地址、结束地址和该区域中的页数。 (不过,我懒得支持大页面;如果你这样做,请考虑解析 /proc/PID/smaps。如果一行以 0-9 或小写字母 a 开头-f,它指定一个区域;否则该行以大写字母 A-Z 开头并指定该区域的一个 属性。)

每个 struct pagemap_region 还包含每页 64 位页面映射值的空间。区域已经found/chosen——这个试了所有——之后,/proc/PID/pagemap文件被打开,使用pread()从适当的位置读取相应的数据,这就像read() ],但也将文件偏移量作为一个额外的参数。

并非所有地区都可以访问。我确实相信 [VSYSCALL] 是其中之一,但作为内核-用户空间接口,它的页面映射位无论如何都是无趣的。而不是从列表中删除这些区域,上面只是将位清除为零。

这并不是一个“完全按照这个做,只需复制并粘贴这个”的答案,而是作为一个如何开始着手解决这个问题的建议,或许可以探索一下,将结果或行为与您的结果或行为进行比较特殊需要;一种粗略的提纲,仅供初步建议。

此外,正如我一次写成的那样,它可能有严重的错误。 (如果我知道在哪里或确定,我会修复它们;只是会发生错误。)