通过 move_pages() 查询失败

Posted

技术标签:

【中文标题】通过 move_pages() 查询失败【英文标题】:Fail to query via move_pages() 【发布时间】:2019-02-06 03:53:49 【问题描述】:
#include <cstdint>
#include <iostream>
#include <numaif.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <limits>

int main(int argc, char** argv) 
    const constexpr uint64_t size = 16lu * 1024 * 1024;
    const constexpr uint32_t nPages = size / (4lu * 1024 * 1024);
    int32_t status[nPages];
    std::fill_n(status, nPages, std::numeric_limits<int32_t>::min());
    void* pages[nPages];

    auto fd = shm_open("test_shm", O_RDWR|O_CREAT, 0666);
    void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (ptr == MAP_FAILED) 
        if (fd > 0) close(fd);
        throw "failed to map hugepages";
    

    for (uint32_t i = 0; i < nPages; i++) 
        pages[i] = (char*)ptr + 4 * 1024 * 1024;
    

    if (0 != move_pages(0, nPages, pages, nullptr, status, 0)) 
        std::cout << "failed to inquiry pages because " << strerror(errno) << std::endl;
    
    else 
            for (uint32_t i = 0; i < nPages; i++) 
            std::cout << "page # " << i << " locates at numa node " << status[i] << std::endl;
        
    
    munmap(ptr, size);
    close(fd);

然后打印出来:

page # 0 locates at numa node -2
page # 1 locates at numa node -2
page # 2 locates at numa node -2
page # 3 locates at numa node -2

根据manpage,它声明:

nodes is an array of integers that specify the desired location for each page.
Each element in the array is a node number. nodes can also be NULL, in which 
case move_pages() does not move any pages but instead will return the node where 
each page currently resides, in the status array. Obtaining the status of each 
page may be necessary to determine pages that need to be moved.

为什么查询返回成功却打印负值?我的机器只有 2 个 NUMA——0 和 1。

内核版本:3.10.0-862.2.3.el7.x86_64

这是大页面的版本:

#include <cstdint>
#include <iostream>
#include <numaif.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <limits>

int main(int argc, char** argv) 
        const int32_t dst_node = strtoul(argv[1], nullptr, 10);
        const constexpr uint64_t size = 4lu * 1024 * 1024;
        const constexpr uint64_t pageSize = 2lu * 1024 * 1024;
        const constexpr uint32_t nPages = size / pageSize;
        int32_t status[nPages];
        std::fill_n(status, nPages, std::numeric_limits<int32_t>::min());
        void* pages[nPages];
        int32_t dst_nodes[nPages];
        void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_HUGETLB, -1, 0);

        if (ptr == MAP_FAILED) 
                throw "failed to map hugepages";
        
        memset(ptr, 0x41, nPages*pageSize);
        for (uint32_t i = 0; i < nPages; i++) 
                pages[i] = &((char*)ptr)[i*pageSize];
                dst_nodes[i] = dst_node;
        

        std::cout << "Before moving" << std::endl;

        if (0 != move_pages(0, nPages, pages, nullptr, status, 0)) 
            std::cout << "failed to inquiry pages because " << strerror(errno) << std::endl;
        
        else 
                for (uint32_t i = 0; i < nPages; i++) 
                        std::cout << "page # " << i << " locates at numa node " << status[i] << std::endl;
                
        

        // real move
        if (0 != move_pages(0, nPages, pages, dst_nodes, status, MPOL_MF_MOVE_ALL)) 
                std::cout << "failed to move pages because " << strerror(errno) << std::endl;
                exit(-1);
        

        const constexpr uint64_t smallPageSize = 4lu * 1024;
        const constexpr uint32_t nSmallPages = size / smallPageSize;
        void* smallPages[nSmallPages];
        int32_t smallStatus[nSmallPages] = std::numeric_limits<int32_t>::min();
        for (uint32_t i = 0; i < nSmallPages; i++) 
                smallPages[i] = &((char*)ptr)[i*smallPageSize];
        


        std::cout << "after moving" << std::endl;
        if (0 != move_pages(0, nSmallPages, smallPages, nullptr, smallStatus, 0)) 
            std::cout << "failed to inquiry pages because " << strerror(errno) << std::endl;
        
        else 
                for (uint32_t i = 0; i < nSmallPages; i++) 
                        std::cout << "page # " << i << " locates at numa node " << smallStatus[i] << std::endl;
                
        


有趣的是move_pages() 似乎理解大页面,因为在大页面移动后,我根据小页面大小进行查询,并填充预期的 NUMA ID。

【问题讨论】:

为什么将错误的页数传递给move_pages? man7.org/linux/man-pages/man2/move_pages.2.html "count 是要移动的页数。它定义了三个数组页、节点和状态的大小。" (享受当前质量的开源文档)。唯一正确的文档是来源:elixir.bootlin.com/linux/v3.10/source/mm/migrate.c#L1367,我看不到大页面的处理。为什么你认为 shm mmaped 内存将由大页面支持? @osgx 我实际上试图通过从图片中删除大页面来简化问题。 实际上你在第三个参数“pages”中调用 move_pages 是不正确的——你给出了一个指向内存的指针,但它应该是页面列表(检查我更新答案中的代码)。所有内核 API 都用于标准的“小”页面;大页面上的 move_pages 是另一个问题。请为 move_pages 调用提供带有大页面和正确页面数组的完整示例。 对于您使用 -2 更新的代码:来自 moreutils 的命令 errno 2 说“ENOENT”,手册页说“该页面不存在。”。访问每一页以进行写入,以将其从零页到实际页。零页面没有 numa 状态,只有真实页面有。 @osgx 感谢您的详细解释。还有2个问题:1.您提到所有内核API都是针对标准“小”页面的,那么我是否应该根据“小”页面大小填充pages(例如,2MB大页面在pages中将有2MB/4KB指针)? 2. 读错了页面吗?还是只有写才能使页面出错?谢谢! 【参考方案1】:

你对 shm_open 和 mmap 的使用可能不会得到你想要的大页面。

move_pages 系统调用(和 libnuma 包装器)适用于 x86_64 的 4096 字节标准页面。

并且您以错误的方式使用 move_pages 和不正确的第三个参数“页面”。它不应该是指向内存的指针;但是指向数组的指针,它本身将包含 nPages 指针:

http://man7.org/linux/man-pages/man2/move_pages.2.html

  long move_pages(int pid, unsigned long count, void **pages,
                   const int *nodes, int *status, int flags);

   pages is an array of pointers to the pages that should be moved.
   These are pointers that should be aligned to page boundaries.
   Addresses are specified as seen by the process specified by pid.

如果“页面”中没有正确的指针,您将得到 -14,根据 errno 14(来自 moreutils 包),这是 EFAULT。

//https://***.com/questions/54546367/fail-to-query-via-move-pages
//g++ 54546367.move_pages.cc -o 54546367.move_pages -lnuma -lrt
#include <cstdint>
#include <iostream>
#include <numaif.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <limits>

int main(int argc, char** argv) 
    const constexpr uint64_t size = 256lu * 1024;// * 1024;
    const constexpr uint32_t nPages = size / (4lu * 1024);
    void * pages[nPages];
    int32_t status[nPages];
    std::fill_n(status, nPages, std::numeric_limits<int32_t>::min());

//  auto fd = shm_open("test_shm", O_RDWR|O_CREAT, 0666);
//  void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    void* ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0);
    std::cout << "Ptr is " << ptr << std::endl;
    if (ptr == MAP_FAILED) 
//      if (fd > 0) close(fd);
        throw "failed to map hugepages";
    
    memset(ptr, 0x41, nPages*4096);
    for(uint32_t i = 0; i<nPages; i++) 
        pages[i] = &((char*)ptr)[i*4096];
    

    if (0 != move_pages(0, nPages, pages, nullptr, status, 0)) 
        std::cout << "failed to inquiry pages because " << strerror(errno) << std::endl;
    
    else 
        for (uint32_t i = 0; i < nPages; i++) 
            std::cout << "page # " << i << " locates at numa node " << status[i] << std::endl;
        
    
    munmap(ptr, size);
//  close(fd);

对于 NUMA 机器,它在以 taskset -c 7 ./54546367.move_pages 启动时输出相同的节点,并在 numactl -i all ./54546367.move_pages 时交错 (0 1 0 1)。

【讨论】:

对不起。太急于编码没有检查。固定的。但仍然有 -2.. 我的代码变体在启动大量 nPage(16MB 内存)时已为 numactl -i all 激活 THP:开始时的某些页面已逐页交错(0 1 0 1 0 1 0 1 ),然后是大系列的 00...00 和 11..11 用于透明大页面,然后在最后一个接一个。而且我还对所有页面进行了错误处理(通过写入访问每个页面)。

以上是关于通过 move_pages() 查询失败的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 查询:如何将布尔值 1 和 0 转换为“通过”或“失败”

Hive 查询 cli 有效,同样通过 hue 失败

通过查询字符串传递“承载”时,SignalR 身份验证失败

循环通过api请求时插入失败只执行第一个查询

查询通过 prometheus 节点导出器文本文件收集器公开的自定义指标失败

通过 Hive 元存储的 Spark SQL 查询“SHOW VIEWS IN”失败,“IN”处缺少“FUNCTIONS”