简体   繁体   English

如何仅从虚拟地址获取 memory 段的页面大小?

[英]How to get the pagesize of a memory segment just from a virtual address?

Linux can have both standard 4KiB page memory and 1GiB (huge) paged memory (and 2MiB pages, but I don't know if anyone uses that). Linux 可以同时拥有标准 4KiB 页面 memory 和 1GiB(巨大)页面 memory(和 2MiB 页面,如果有人知道的话,但我不知道)

Is there a standard call to get the page size from an arbitrary virtual address?是否有从任意虚拟地址获取页面大小的标准调用? The pointer could be pointing to 4K pages or huge pages.指针可能指向 4K 页面或大页面。

The problem at hand is to sanity ( assert(...) ) check arguments to a function that requires the base address and size of the region needs to be multiples of the page size, to be handed to mbind .手头的问题是理智( assert(...) )检查 arguments 到 function 要求基地址和区域大小需要是页面大小的倍数,才能交给mbind But the page size varies on the system.但是页面大小因系统而异。 Without sanity checking, the return value of mbind just gives Invalid argument which is not helpful for debugging.如果没有健全性检查, mbind的返回值只会给出Invalid argument ,这对调试没有帮助。

I've looked at this answer How to get linux kernel page size programmatically but it gives answers that assume that the entire system is the same, and they are also compile time constants.我已经看过这个答案How to get linux kernel page size 以编程方式但它给出的答案假设整个系统是相同的,它们也是编译时间常数。 Also getpagesize() does the same and it is deprecated anyways.getpagesize()做同样的事情,无论如何它已被弃用。

This is related to the MMU, see https://unix.stackexchange.com/questions/128213/how-is-page-size-determined-in-virtual-address-space and normally the page size is equal for the entire system / kernel, it is determined during kernel compilation这与 MMU 有关,请参阅https://unix.stackexchange.com/questions/128213/how-is-page-size-determined-in-virtual-address-space并且通常整个系统的页面大小相等/ kernel,在kernel编译时确定

I realize one way of doing it is to scrape /proc/self/maps and somewhere in there is a keyword that indicates if a memory range has huge pages or not.我意识到这样做的一种方法是抓取/proc/self/maps ,其中某处有一个关键字,指示 memory 范围是否有大页面。 I don't know how portable that is (man page for /proc doesn't say what it is, but I've seen it).我不知道它的便携性(/proc 的手册页没有说明它是什么,但我已经看到了)。 But that seems a heavy handed way of just looking up a pointer to get the page size.但这似乎是一种仅查找指针以获取页面大小的笨拙方式。 And then on top of that, I don't think it indicates page size, just whether or not it is "huge pages".然后最重要的是,我不认为它表示页面大小,只是它是否是“巨大的页面”。

I am not 100% i understand your requirements correctly, but i 'll give it a try.我不是 100% 我正确理解您的要求,但我会试一试。

There is an interesting function posted here by user Ciro Santilli , pagemap_get_entry .用户Ciro Santilli这里发布了一个有趣的 function, pagemap_get_entry It uses the /proc/[pid]/pagemap interface to get the page table entry (pte) that corresponds to the virtual address you give as input.它使用 /proc/[pid]/pagemap 接口来获取与您作为输入提供的虚拟地址相对应的页表条目 (pte)。 From the pte, you get the pfn (physical frame number) where the virtual address is mapped.从 pte 中,您可以获得映射虚拟地址的 pfn(物理帧号)。 Having this function, we can use the following logic to find out if a virtual address is mapped to 4K, 2M or 1G physical page:有了这个function,我们可以用下面的逻辑来判断一个虚拟地址是映射到4K、2M还是1G的物理页:

First, get the address of the 1G virtual page where the virtual address of interest belongs.首先,获取感兴趣的虚拟地址所属的1G虚拟页面的地址。 Call pagemap_get_entry with that virtual address and if the returned pfn is 2 18 -aligned, then assume we are on a 1G physical page (2 18 is used because we assume size of physical frame to be 4K=2 12 bytes and 2 18 *2 12 =2 30 =1GiB).使用该虚拟地址调用 pagemap_get_entry 并且如果返回的 pfn 是 2 18对齐的,则假设我们在 1G 物理页面上(使用 2 18是因为我们假设物理帧的大小为 4K=2 12字节和 2 18 *2 12 =2 30 =1GiB)。

Else, get the address of the 2M virtual page inside which the virtual address falls.否则,获取虚拟地址所在的2M虚拟页面的地址。 Call pagemap_get_entry with that and if the returned pfn is 2 9 -aligned, then assume we are inside a 2M physical page (again 2 9 *2 12 =2 21 =2MiB).调用 pagemap_get_entry 如果返回的 pfn 是 2 9对齐的,那么假设我们在一个 2M 的物理页面内(同样是 2 9 *2 12 =2 21 =2MiB)。

Else, assume that virtual address is mapped in RAM with 4K physical page.否则,假设虚拟地址映射到具有 4K 物理页面的 RAM 中。

With code, i hope it would be something like that (part of linked post is reposted here for completeness):使用代码,我希望它会是这样的(为了完整起见,链接帖子的一部分在此处重新发布):

#define _XOPEN_SOURCE 700
#include <fcntl.h> /* open */
#include <stdint.h> /* uint64_t  */
#include <stdio.h> /* printf */
#include <stdlib.h> /* size_t, malloc */
#include <unistd.h> /* pread, sysconf, getpid */
#include <sys/types.h> /* getpid */
#include <string.h> /* memset */

typedef struct {
    uint64_t pfn : 55;
    unsigned int soft_dirty : 1;
    unsigned int file_page : 1;
    unsigned int swapped : 1;
    unsigned int present : 1;
} PagemapEntry;

/* Parse the pagemap entry for the given virtual address.
 *
 * @param[out] entry      the parsed entry
 * @param[in]  pagemap_fd file descriptor to an open /proc/pid/pagemap file
 * @param[in]  vaddr      virtual address to get entry for
 * @return 0 for success, 1 for failure
 */
int pagemap_get_entry(PagemapEntry *entry, int pagemap_fd, uintptr_t vaddr)
{
    size_t nread;
    ssize_t ret;
    uint64_t data;
    uintptr_t vpn;

    vpn = vaddr / sysconf(_SC_PAGE_SIZE);
    nread = 0;
    while (nread < sizeof(data)) {
        ret = pread(pagemap_fd, ((uint8_t*)&data) + nread, sizeof(data) - nread,
                vpn * sizeof(data) + nread);
        nread += ret;
        if (ret <= 0) {
            return 1;
        }
    }
    entry->pfn = data & (((uint64_t)1 << 55) - 1);
    entry->soft_dirty = (data >> 55) & 1;
    entry->file_page = (data >> 61) & 1;
    entry->swapped = (data >> 62) & 1;
    entry->present = (data >> 63) & 1;
    return 0;
}

int main()
{
   unsigned long long PAGE_SIZE_1G = 1024*1024*1024;
   unsigned long long PAGE_SIZE_2M = 2*1024*1024;
   unsigned long long PAGE_SIZE_4K = 4*1024;
   uint64_t pfn_1g, pfn_2m, pfn_4k, pfn_original;

   char * arr = (char *)malloc(4*PAGE_SIZE_1G * sizeof(char));
   if (arr == NULL) {
      printf("malloc\n");
      return 1;
   }
   memset(arr, 1, 4*PAGE_SIZE_1G);
   uintptr_t vaddr = (uintptr_t)arr + 1024*1025*1026; // get a random virtual address
   PagemapEntry entry;

   uintptr_t vaddr_1g_aligned = vaddr & ~(PAGE_SIZE_1G - 1);
   uintptr_t vaddr_2m_aligned = vaddr & ~(PAGE_SIZE_2M - 1);
   uintptr_t vaddr_4k_aligned = vaddr & ~(PAGE_SIZE_4K - 1);

   printf("Virtual address of interest %jx\n", (uintmax_t) vaddr);
   printf("1G-aligned virtual address  %jx\n", (uintmax_t) vaddr_1g_aligned);
   printf("2M-aligned virtual address  %jx\n", (uintmax_t) vaddr_2m_aligned);
   printf("4K-aligned virtual address  %jx\n", (uintmax_t) vaddr_4k_aligned);

   char pagemap_file[BUFSIZ];
   int pagemap_fd;
   pid_t pid = getpid();

   snprintf(pagemap_file, sizeof(pagemap_file), "/proc/%ju/pagemap", (uintmax_t)pid);
   pagemap_fd = open(pagemap_file, O_RDONLY);
   if (pagemap_fd < 0) {
       return 1;
   }

   if (pagemap_get_entry(&entry, pagemap_fd, vaddr_1g_aligned)) {
      printf("pagemap_get_entry\n");
      return 1;
   }
   pfn_1g = entry.pfn;
   if (pagemap_get_entry(&entry, pagemap_fd, vaddr_2m_aligned)) {
      printf("pagemap_get_entry\n");
      return 1;   
   }
   pfn_2m = entry.pfn;
   if (pagemap_get_entry(&entry, pagemap_fd, vaddr_4k_aligned)) {
      printf("pagemap_get_entry\n");
      return 1;   
   }
   pfn_4k = entry.pfn;
   if (pagemap_get_entry(&entry, pagemap_fd, vaddr)) {
      printf("pagemap_get_entry\n");
      return 1;   
   }
   pfn_original = entry.pfn;

   printf("pfn of 1G-alignment:     %jx\n", (uintmax_t) pfn_1g);
   printf("pfn of 2M-alignment:     %jx\n", (uintmax_t) pfn_2m);
   printf("pfn of 4K-alignment:     %jx\n", (uintmax_t) pfn_4k);
   printf("pfn of original address: %jx\n", (uintmax_t) pfn_original);

   if ((pfn_1g != 0) && (pfn_1g % (1 << 18) == 0)) {
      printf("Virtual address is mapped to 1G physical page\n");
   }
   else if ((pfn_2m != 0) && (pfn_2m % (1 << 9) == 0)) {
      printf("Virtual address is mapped to 2M physical page\n");
   }
   else {
      printf("Virtual address is mapped to 4K physical page\n");
   }

   return 0;
}

As original poster explains , you have to run this program with sudo, because of read access to /proc/<pid>/pagemap.正如原始海报所解释的那样,由于对 /proc/<pid>/pagemap 的读取访问权限,您必须使用 sudo 运行该程序。

In my system that supports only 2M and 4K page sizes, i get the followings:在仅支持 2M 和 4K 页面大小的系统中,我得到以下信息:

root@debian # cat /sys/kernel/mm/transparent_hugepages/enabled
always madvise [never]
root@debian # ./physical_page_size
Virtual address of interest 7f4f9d01a810
1G-aligned virtual address  7f4f80000000
2M-aligned virtual address  7f4f9d000000
4K-aligned virtual address  7f4f9d01a000
pfn of 1G-alignment:     1809fa
pfn of 2M-alignment:     1639fa
pfn of 4K-alignment:     163a14
pfn of original address: 163a14
Virtual address is mapped to 4K physical page
root@debian # echo "always" > /sys/kernel/mm/transparent_hugepages/enabled
root@debian # ./physical_page_size
Virtual address of interest 7f978d0d2810
1G-aligned virtual address  7f9780000000
2M-aligned virtual address  7f978d000000
4K-aligned virtual address  7f978d0d2000
pfn of 1G-alignment:     137a00
pfn of 2M-alignment:     145a00
pfn of 4K-alignment:     145ad2
pfn of original address: 145ad2
Virtual address is mapped to 2M physical page

Also, i have to mention that when the program reports 1G or 2M physical page size, it is not guaranteed that this is the case, however is very highly possible.另外,我不得不提一下,当程序报告 1G 或 2M 物理页面大小时,不能保证是这种情况,但很有可能。

Finally, i see that your problem is with mbind.最后,我发现您的问题出在 mbind 上。 Again, i am not sure i understand it correctly or if this is a valid suggestion, but maybe you could try all possible page sizes starting from smallest until the call succeeds.同样,我不确定我是否理解正确,或者这是否是一个有效的建议,但也许您可以尝试所有可能的页面大小,从最小开始直到调用成功。

int wrapper(void *start, unsigned long size)
{
   unsigned long long PAGE_SIZE_4K = 4*1024;
   unsigned long long PAGE_SIZE_2M = 2*1024*1024;
   unsigned long long PAGE_SIZE_1G = 1024*1024*1024;

   void *start_4k = (void *)((unsigned long) start & ~(PAGE_SIZE_4K-1));
   void *start_2m = (void *)((unsigned long) start & ~(PAGE_SIZE_2M-1));
   void *start_1g = (void *)((unsigned long) start & ~(PAGE_SIZE_1G-1));
   unsigned long size_4k, size_2m, size_1g;
   if (size % PAGE_SIZE_4K != 0) {
      size_4k = size - (size % PAGE_SIZE_4K) + PAGE_SIZE_4K;
   }
   if (size % PAGE_SIZE_2M != 0) {
      size_2m = size - (size % PAGE_SIZE_2M) + PAGE_SIZE_2M;
   }
   if (size % PAGE_SIZE_1G != 0) {
      size_1g = size - (size % PAGE_SIZE_1G) + PAGE_SIZE_1G;
   }

   if (mbind(start_4k, size_4k, .....) == 0) {
      return 0;
   }
   if (mbind(start_2m, size_2m, .....) == 0) {
      return 0;
   }
   if (mbind(start_1g, size_1g, .....) == 0) {
      return 0;
   }

   return 1;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何获取已分配页面的新虚拟内存地址? - How to a get a new virtual memory address to an already allocated page? 如何获取从shell调用的二进制文本段的基址? - How to get the base address of the text segment of the binary invoked from shell? 从虚拟内存地址中查找物理内存地址 - Find physical memory address from virtual memory address 如何获取已初始化数据段的第一个地址 - How to get the first address of initialized data segment 如何计算进程内存的虚拟地址? - How the virtual address of the process memory is calculated? 你如何拦截正在写入一段内存的指令的地址? - how do you intercept the address of an instruction that is writing to a segment of memory? 是malloc / calloc从虚拟地址空间返回的内存地址? - Is the memory address returned by malloc/calloc from virtual address space? 如何在shmat()共享内存段中给出附加起始地址? - How to give starting address to attach to that in shmat() shared memory segment? 如何在unix中遍历虚拟内存中的bss段,数据段和整个堆栈 - how can I traverse the bss segment, data segment and the whole stack in virtual memory in unix 如何从共享内存段中获取数据? - How to grab data from a shared memory segment?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM