微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决如何仅从虚拟地址获取内存段的页面大小?

Linux 可以同时拥有标准 4KiB 页面内存和 1GiB(巨大)页面内存(以及 2MiB 页面,但我不知道是否有人使用它)。

是否有从任意虚拟地址获取页面大小的标准调用?指针可能指向 4K 页或大页。

手头的问题是对一个需要基地址和区域大小需要是页面大小的倍数的函数的健全性(assert(...))检查参数,以传递给mbind .但是页面大小因系统而异。如果没有完整性检查,mbind 的返回值只会给出 Invalid argument,这对调试没有帮助。

我看过这个答案 How to get linux kernel page size programmatically 但它给出的答案假设整个系统是相同的,而且它们也是编译时常量。 getpagesize() 也做同样的事情,无论如何它都被弃用了。

解决方法

这和MMU有关,见https://unix.stackexchange.com/questions/128213/how-is-page-size-determined-in-virtual-address-space,一般整个系统/内核的page size是相等的,在内核编译时确定

,

我意识到一种方法是抓取 /proc/self/maps 并且在某处有一个关键字指示内存范围是否有大页面。我不知道它有多便携(/proc 的手册页没有说明它是什么,但我见过它)。但这似乎是一种仅通过查找指针来获取页面大小的笨拙方式。最重要的是,我不认为它表示页面大小,只是它是否是“大页面”。

,

我不是 100% 正确理解您的要求,但我会尝试一下。

用户here pagemap_get_entry 发布了一个有趣的函数Ciro Santilli。它使用 /proc/[pid]/pagemap 接口来获取与您作为输入提供的虚拟地址相对应的页表条目 (pte)。从 pte 中,您可以获得映射虚拟地址的 pfn(物理帧号)。有了这个功能,我们就可以用下面的逻辑来判断一个虚拟地址是映射到4K、2M还是1G物理页:

首先获取感兴趣的虚拟地址所属的1G虚拟页面的地址。使用该虚拟地址调用 pagemap_get_entry,如果返回的 pfn 是 218-aligned,则假设我们在 1G 物理页面上(使用 218,因为我们假设大小为物理帧为 4K=212 字节和 218*212=230=1GiB)。

否则,获取虚拟地址所在的2M virtual 页面的地址。使用它调用 pagemap_get_entry,如果返回的 pfn 是 29-aligned,那么假设我们在一个 2M 的物理页面内(再次 29*212=221=2MiB)。

否则,假设虚拟地址映射到具有 4K 物理页的 RAM 中。

对于代码,我希望它是这样的(为了完整起见,链接帖子的一部分重新发布在这里):

#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,vaddr_2m_aligned)) {
      printf("pagemap_get_entry\n");
      return 1;   
   }
   pfn_2m = entry.pfn;
   if (pagemap_get_entry(&entry,vaddr_4k_aligned)) {
      printf("pagemap_get_entry\n");
      return 1;   
   }
   pfn_4k = entry.pfn;
   if (pagemap_get_entry(&entry,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;
}

作为原始海报 explains,您必须使用 sudo 运行此程序,因为对 /proc//pagemap 具有读取权限。

在我仅支持 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

另外,我不得不提一下,当程序报告1G或2M物理页面大小时,不能保证是这种情况,但是很有可能。

最后,我发现您的问题与 mbind 有关。同样,我不确定我是否理解正确,或者这是否是一个有效的建议,但也许您可以尝试所有可能的页面大小,从最小开始直到调用成功。

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,.....) == 0) {
      return 0;
   }
   if (mbind(start_1g,size_1g,.....) == 0) {
      return 0;
   }

   return 1;
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。