如何以编程方式获取特定地址的页面大小?
作者:互联网
我正在寻找一种实现获取地址的功能的方法,并告诉该地址使用的页面大小.一种解决方案在/ proc // smaps中的段中查找地址,然后返回“ KernelPageSize:”的值.该解决方案非常慢,因为它涉及线性读取文件,该文件可能很长.我需要一个更快,更有效的解决方案.
为此有系统调用吗? (int getpagesizefromaddr(void * addr);)
如果没有,有没有办法推断页面大小?
解决方法:
许多Linux体系结构都支持“大页面”,有关详细信息,请参见Documentation/vm/hugetlbpage.txt.例如,在x86-64上,sysconf(_SC_PAGESIZE)报告页面大小为4096,但也可以使用2097152字节的大页面.从应用程序的角度来看,这很少有关系.内核完全有能力根据需要从一种页面类型转换为另一种页面类型,而用户空间应用程序不必为此担心.
但是,对于特定的工作负载,性能收益是巨大的.这就是为什么开发了透明的大页面支持(请参见Documentation/vm/transhuge.txt)的原因.这在虚拟环境(即工作负载在来宾环境中运行)中特别明显.用于madvise()的新建议标志MADV_HUGEPAGE和MADV_NOHUGEPAGE允许应用程序告知内核其首选项,因此mmap(… MAP_HUGETLB …)不是获得这些性能优势的唯一方法.
我个人认为Eldad的来宾与在来宾环境中运行的工作负载有关,重点是在进行基准测试时观察页面映射类型(正常页面或大页面),以找到针对特定工作负载的最有效配置.
让我们通过显示一个真实的示例huge.c消除所有误解:
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#define PAGES 1024
int main(void)
{
FILE *in;
void *ptr;
size_t page;
page = (size_t)sysconf(_SC_PAGESIZE);
ptr = mmap(NULL, PAGES * page, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, (off_t)0);
if (ptr == MAP_FAILED) {
fprintf(stderr, "Cannot map %ld pages (%ld bytes): %s.\n", (long)PAGES, (long)PAGES * page, strerror(errno));
return 1;
}
/* Dump /proc/self/smaps to standard out. */
in = fopen("/proc/self/smaps", "rb");
if (!in) {
fprintf(stderr, "Cannot open /proc/self/smaps: %s.\n", strerror(errno));
return 1;
}
while (1) {
char *line, buffer[1024];
line = fgets(buffer, sizeof buffer, in);
if (!line)
break;
if ((line[0] >= '0' && line[0] <= '9') ||
(line[0] >= 'a' && line[0] <= 'f') ||
(strstr(line, "Page")) ||
(strstr(line, "Size")) ||
(strstr(line, "Huge"))) {
fputs(line, stdout);
continue;
}
}
fclose(in);
return 0;
}
如果可能的话,以上方法使用大页面分配1024个页面. (在x86-64上,一个大页面是2个MiB或512个普通页面,因此这应该分配两个大页面的价值,即4 MiB,私有匿名内存.如果在不同的体系结构上运行,请调整PAGES常量.)
通过验证/ proc / sys / vm / nr_hugepages大于零来确保启用大页面.在大多数系统上,它默认为零,因此您需要提高它,例如使用
sudo sh -c 'echo 10 > /proc/sys/vm/nr_hugepages'
它告诉内核保留10个大页面的池(x86-64上为20 MiB).
编译并运行上述程序,
gcc -W -Wall -O3 huge.c -o huge && ./huge
并且您将获得缩写的/ proc / PID / smaps输出.在我的机器上,有趣的部分包含
2aaaaac00000-2aaaab000000 rw-p 00000000 00:0c 21613022 /anon_hugepage (deleted)
Size: 4096 kB
AnonHugePages: 0 kB
KernelPageSize: 2048 kB
MMUPageSize: 2048 kB
这显然不同于典型零件,例如
01830000-01851000 rw-p 00000000 00:00 0 [heap]
Size: 132 kB
AnonHugePages: 0 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
完整的/ proc / self / smaps文件的确切格式在man 5 proc
中进行了描述,并且很容易解析.请注意,这是内核生成的伪文件,因此永远不会本地化.空格字符是HT(代码9)和SP(代码32),换行符是LF(代码10).
我推荐的方法是维护描述映射的结构,例如
struct region {
size_t start; /* first in region at (void *)start */
size_t length; /* last in region at (void *)(start + length - 1) */
size_t pagesize; /* KernelPageSize field */
};
struct maps {
size_t length; /* of /proc/self/smaps */
unsigned long hash; /* fast hash, say DJB XOR */
size_t count; /* number of regions */
pthread_rwlock_t lock; /* region array lock */
struct region *region;
};
仅当一个线程有可能检查区域数组而另一个线程正在更新或替换该数组时才需要锁定成员.
这个想法是,以所需的时间间隔读取/ proc / self / smaps伪文件,并计算出快速,简单的哈希(或CRC).如果长度和哈希值匹配,则假定映射没有更改,然后重用现有信息.否则,将采取写锁定(请记住,信息已过时),解析映射信息,并生成新的区域数组.
如果是多线程的,则锁定成员允许多个并发读取器,但可以防止使用废弃的区域数组.
注意:在计算哈希值时,您还可以计算地图项的数量,因为属性行均以大写ASCII字母(A-Z,代码65至90)开头.换句话说,以小写的十六进制数字(0-9,代码48至57,或a-f,代码97至102)开头的行数是所描述的存储区域数.
C库提供的功能中,mmap()
、munmap()
、mremap()
、madvise()
(和posix_madvise()),mprotect()
、malloc()
、calloc()
、realloc()
、free()
、brk()
和sbrk()
可能会更改内存映射(尽管我不确定这列表包含了所有内容).可以插入这些库调用,并在每次(成功)调用之后更新内存区域列表.这应该允许应用程序依赖于存储区域结构来获取准确的信息.
就个人而言,我会将此功能创建为预加载库(使用LD_PRELOAD加载).这样一来,只需几行代码即可轻松插入上述函数:插入的函数调用原始函数,如果成功,则调用内部函数,该内部函数从/ proc / self / smaps重新加载内存区域信息.应注意调用原始的内存管理功能,并保持errno不变;否则应该很简单.我个人也避免使用库函数(包括string.h)来解析字段,但是无论如何我还是非常小心.
插入的库显然还可以提供查询特定地址(例如pagesizeat())的页面大小的功能. (如果您的应用程序导出的弱版本始终以errno == ENOTSUP返回-1,则您的预加载库可以覆盖它,而您不必担心是否加载了预加载库-如果没有,则该函数只会返回一个错误.)
有什么问题吗
标签:huge-pages,c-3,linux 来源: https://codeday.me/bug/20191122/2058174.html