Ahududu Pi 4 üzerinde HugePages

Nov 19 2020

Ahududu pi OS 64 bit çalıştıran raspberry pi 4 üzerinde Hugepages'i yönetme konusunda yardıma ihtiyacım var.
İnternette çok güvenilir bilgi bulamadım.
İlk önce çekirdek kaynağı etkinleştirme Memory Management options --->Transparent Hugepage Supportseçeneğini yeniden derledim . Komutu çalıştırdığımda:

grep -i huge /proc/meminfo

Çıktı:

AnonHugePages:    319488 kB
ShmemHugePages:        0 kB
FileHugePages:         0 k

ve şu komutu çalıştırmak:

cat /sys/kernel/mm/transparent_hugepage/enabled

çıktı:

[always] madvise never

Bu yüzden Transparent Huge Pages (AnonHugePages) ayarlanması gerektiğini düşünüyorum. En büyük bitişik bellek yığınını mmap işlevi, c kodu kullanarak eşlemek için HugePages kullanmam gerekiyor.

mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

Bakmak https://www.man7.org/linux/man-pages/man2/mmap.2.html sarılma sayfalarını yönetmek için iki bayrak vardır: MAP_HUGETLB bayrağı ve MAP_HUGE_2MB, MAP_HUGE_1GB bayrağı.

Sorum şu: HugePages kullanmak için bu şekilde eşleme yapmalı mıyım?

mem = mmap(NULL,buf_size,PROT_READ|PROT_WRITE,MAP_SHARED,MAP_HUGETLB,fd,0);

Çekirdek yapılandırması:

CONFIG_SYS_SUPPORTS_HUGETLBFS=y
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
CONFIG_HAVE_ARCH_HUGE_VMAP=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TRANSPARENT_HUGEPAGE_ALWAYS=y
# CONFIG_TRANSPARENT_HUGEPAGE_MADVISE is not set
CONFIG_TRANSPARENT_HUGE_PAGECACHE=y
# CONFIG_HUGETLBFS is not set

Teşekkür ederim.

Yanıtlar

1 RachidK. Nov 19 2020 at 22:06

Büyük sayfalar, TLB kaçırma sayısını azaltarak uygulamaların performansını artırmanın bir yoludur. Mekanizma, bitişik standart fiziksel sayfaları (tipik boyut 4 KB) büyük bir sayfa (örn. 2 MB) içinde birleştirir. Linux bu özelliği iki farklı şekilde uygular: Şeffaf Devasa sayfalar ve açık büyük sayfalar.

Şeffaf Büyük Sayfalar

Şeffaf dev sayfalar (THP), çekirdek tarafından şeffaf bir şekilde yönetilir. Kullanıcı alanı uygulamalarının bunlar üzerinde kontrolü yoktur. Çekirdek, mümkün olduğunda büyük sayfaları ayırmak için elinden gelenin en iyisini yapar, ancak bu garanti edilmez. Dahası, THP, büyük sayfalar oluşturmak için fiziksel sayfaların birleştirilmesinden sorumlu olan khugepaged adlı temel bir "çöp toplayıcı" çekirdek daemon olarak ek yük getirebilir. Bu, çalışan uygulamaların performansları üzerinde istenmeyen etkilerle birlikte CPU süresini tüketebilir. Zaman açısından kritik uygulamalara sahip sistemlerde, genellikle THP'nin devre dışı bırakılması önerilir.

THP, önyükleme komut satırından (bu cevabın sonuna bakın) veya sysfs'deki kabuktan devre dışı bırakılabilir :

$ cat /sys/kernel/mm/transparent_hugepage/enabled always [madvise] never $ sudo sh -c "echo never > /sys/kernel/mm/transparent_hugepage/enabled"
$ cat /sys/kernel/mm/transparent_hugepage/enabled
always madvise [never]

Not : THP'nin performans değerlendirmesi / sorunları hakkında bazı ilginç makaleler bulunmaktadır:

  • Şeffaf Sarılma Sayfaları: performans etkisinin ölçülmesi ;
  • Veritabanları için Şeffaf Devasa Sayfalar Efsanesini Yerleştirme .

Açıkça büyük sayfalar

Uygulama düzeyinde (yani kullanıcı alanından) büyük sayfalar gerekiyorsa. HUGETLBFS çekirdek yapılandırması, hugetlbfs sözde dosya sistemini etkinleştirecek şekilde ayarlanmalıdır (çekirdek yapılandırıcısındaki menü şuna benzer: "Dosya sistemleri" -> "Sözde dosya sistemleri" -> "HugeTLB dosya sistemi desteği"). Çekirdek kaynak ağacında bu parametre fs / Kconfig içindedir :

config HUGETLBFS
    bool "HugeTLB file system support"
    depends on X86 || IA64 || SPARC64 || (S390 && 64BIT) || \
           SYS_SUPPORTS_HUGETLBFS || BROKEN
    help
      hugetlbfs is a filesystem backing for HugeTLB pages, based on
      ramfs. For architectures that support it, say Y here and read
      <file:Documentation/admin-guide/mm/hugetlbpage.rst> for details.

      If unsure, say N.

Örneğin, bir Ubuntu sisteminde şunları kontrol edebiliriz:

$ cat /boot/config-5.4.0-53-generic | grep HUGETLBFS
CONFIG_HUGETLBFS=y

Not : Raspberry Pi'de /proc/config.gz'nin görüntüsünü yapılandırmak ve parametreyi kontrol etmek için zcat ile aynısını yapmak mümkündür . Yapılandırma menüsü şu şekildedir: "Genel kurulum" -> "Çekirdek .config desteği" + "/proc/config.gz aracılığıyla .config'e erişimi etkinleştirin"

Bu parametre ayarlandığında, hugetlbfs sözde dosya sistemi çekirdek yapısına eklenir (cf. fs / Makefile ):

obj-$(CONFIG_HUGETLBFS)     += hugetlbfs/

The source code of hugetlbfs is located in fs/hugetlbfs/inode.c. At startup, the kernel will mount internal hugetlbfs file systems to support all the available huge page sizes for the architecture it is running on:

static int __init init_hugetlbfs_fs(void)
{
    struct vfsmount *mnt;
    struct hstate *h;
    int error;
    int i;

    if (!hugepages_supported()) {
        pr_info("disabling because there are no supported hugepage sizes\n");
        return -ENOTSUPP;
    }

    error = -ENOMEM;
    hugetlbfs_inode_cachep = kmem_cache_create("hugetlbfs_inode_cache",
                    sizeof(struct hugetlbfs_inode_info),
                    0, SLAB_ACCOUNT, init_once);
    if (hugetlbfs_inode_cachep == NULL)
        goto out;

    error = register_filesystem(&hugetlbfs_fs_type);
    if (error)
        goto out_free;

    /* default hstate mount is required */
    mnt = mount_one_hugetlbfs(&hstates[default_hstate_idx]);
    if (IS_ERR(mnt)) {
        error = PTR_ERR(mnt);
        goto out_unreg;
    }
    hugetlbfs_vfsmount[default_hstate_idx] = mnt;

    /* other hstates are optional */
    i = 0;
    for_each_hstate(h) {
        if (i == default_hstate_idx) {
            i++;
            continue;
        }

        mnt = mount_one_hugetlbfs(h);
        if (IS_ERR(mnt))
            hugetlbfs_vfsmount[i] = NULL;
        else
            hugetlbfs_vfsmount[i] = mnt;
        i++;
    }

    return 0;

 out_unreg:
    (void)unregister_filesystem(&hugetlbfs_fs_type);
 out_free:
    kmem_cache_destroy(hugetlbfs_inode_cachep);
 out:
    return error;
}

A hugetlbfs file system is a sort of RAM file system into which the kernel creates files to back the memory regions mapped by the applications.

The amount of needed huge pages can be reserved by writing the number of needed huge pages into /sys/kernel/mm/hugepages/hugepages-hugepagesize/nr_hugepages.

Then, mmap() is able to map some part of the application address space onto huge pages. Here is an example showing how to do it:

#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>

#define HP_SIZE  (2 * 1024 * 1024) // <-- Adjust with size of the supported HP size on your system

int main(void)
{
  char *addr, *addr1;

  // Map a Huge page
  addr = mmap(NULL, HP_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED| MAP_HUGETLB, -1, 0);
  if (addr == MAP_FAILED) {
    perror("mmap()");
    return 1;
  }

  printf("Mapping located at address: %p\n", addr);

  pause();

  return 0;
}

In the preceding program, the memory pointed by addr is based on huge pages. Example of usage:

$ gcc alloc_hp.c -o alloc_hp
$ ./alloc_hp mmap(): Cannot allocate memory $ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
0
$ sudo sh -c "echo 1 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages" $  cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
1
$ ./alloc_hp 
Mapping located at address: 0x7f7ef6c00000

In another terminal, the process map can be observed to verify the size of the memory page (it is blocked in pause() system call):

$ pidof alloc_hp
13009
$ cat /proc/13009/smaps
[...]
7f7ef6c00000-7f7ef6e00000 rw-s 00000000 00:0f 331939     /anon_hugepage (deleted)
Size:               2048 kB
KernelPageSize:     2048 kB   <----- The page size is 2MB
MMUPageSize:        2048 kB
[...]

In the preceding map, the file name /anon_hugepage for the huge page region is made internally by the kernel. It is marked deleted because the kernel removes the associated memory file which will make the file disappear as soon as there are no longer references on it (e.g. when the calling process ends, the underlying file is closed upon exit(), the reference counter on the file drops to 0 and the remove operation finishes to make it disappear).

Allocation of other huge page sizes

On Raspberry Pi 4B, the default huge page size is 2MB but the card supports several other huge page sizes:

$ ls -l /sys/kernel/mm/hugepages
total 0
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-1048576kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-2048kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-32768kB
drwxr-xr-x 2 root root 0 Nov 23 14:58 hugepages-64kB

To use them, it is necessary to mount a hugetlbfs type file system corresponding to the size of the desired huge page. The kernel documentation provides details on the available mount options. For example, to mount a hugetlbfs file system on /mnt/huge with 8 Huge Pages of size 64KB, the command is:

mount -t hugetlbfs -o pagesize=64K,size=512K,min_size=512K none /mnt/huge

Then it is possible to map huge pages of 64KB in a user program. The following program creates the /tmp/hpfs directory on which it mounts a hugetlbfs file system with a size of 4 huge pages of 64KB. A file named /memfile_01 is created and extended to the size of 2 huge pages. The file is mapped into memory thanks to mmap() system call. It is not passed MAP_HUGETLB flag as the provided file descriptor is for a file created on a hugetlbfs filesystem. Then, the program calls pause() to suspend its execution in order to make some observations in another terminal:

#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <fcntl.h>


#define ERR(fmt, ...) do {                            \
    fprintf(stderr,                                   \
            "ERROR@%s#%d: "fmt,                       \
             __FUNCTION__, __LINE__, ## __VA_ARGS__); \
                         } while(0)


#define HP_SIZE   (64 * 1024)
#define HPFS_DIR  "/tmp/hpfs"
#define HPFS_SIZE (4 * HP_SIZE)


int main(void)
{
void *addr;
char  cmd[256];
int   status;
int   rc;
char  mount_opts[256];
int   fd;

  rc = mkdir(HPFS_DIR, 0777);
  if (0 != rc && EEXIST != errno) {
    ERR("mkdir(): %m (%d)\n", errno);
    return 1;
  }

  snprintf(mount_opts, sizeof(mount_opts), "pagesize=%d,size=%d,min_size=%d", HP_SIZE, 2*HP_SIZE, HP_SIZE);

  rc = mount("none", HPFS_DIR, "hugetlbfs", 0, mount_opts);
  if (0 != rc) {
    ERR("mount(): %m (%d)\n", errno);
    return 1;
  }

  fd = open(HPFS_DIR"/memfile_01", O_RDWR|O_CREAT, 0777);
  if (fd < 0) {
    ERR("open(%s): %m (%d)\n", "memfile_01", errno);
    return 1;
  }

  rc = ftruncate(fd, 2 * HP_SIZE);
  if (0 != rc) {
    ERR("ftruncate(): %m (%d)\n", errno);
    return 1;
  }

  addr = mmap(NULL, 2 * HP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
  if (MAP_FAILED == addr) {
    ERR("mmap(): %m (%d)\n", errno);
    return 1;
  }

  // The file can be closed
  rc = close(fd);
  if (0 != rc) {
    ERR("close(%d): %m (%d)\n", fd, errno);
    return 1;
  }

  pause();

  return 0;

} // main

The preceding program must be run as root as it calls mount():

$ gcc mount_tlbfs.c -o mount_tlbfs $ cat /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages 
0
$ sudo sh -c "echo 8 > /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages" $ cat /sys/kernel/mm/hugepages/hugepages-64kB/nr_hugepages 
8
$ sudo ./mount_tlbfs 

In another terminal, the /proc/[pid]/smaps file can be displayed to check the huge page allocation. As soon as the program writes into the huge pages, the Lazy allocation mechanism triggers the effective allocation of the huge pages.

Cf. This article for future details

Early reservation

The huge pages are made with consecutive physical memory pages. The reservation should be done early in the system startup (especially on heavy loaded systems) as the physical memory may be so fragmented that it is sometimes impossible to allocate huge pages afterward. To reserve as early as possible, this can be done on the kernel boot command line:

hugepages=  
       [HW] Number of HugeTLB pages to allocate at boot.
       If this follows hugepagesz (below), it specifies
       the number of pages of hugepagesz to be allocated.
       If this is the first HugeTLB parameter on the command
       line, it specifies the number of pages to allocate for
       the default huge page size.  See also
       Documentation/admin-guide/mm/hugetlbpage.rst.
       Format: <integer>

hugepagesz=
        [HW] The size of the HugeTLB pages.  This is used in
        conjunction with hugepages (above) to allocate huge
        pages of a specific size at boot.  The pair
        hugepagesz=X hugepages=Y can be specified once for
        each supported huge page size. Huge page sizes are
        architecture dependent.  See also
        Documentation/admin-guide/mm/hugetlbpage.rst.
        Format: size[KMG]

transparent_hugepage=
        [KNL]
        Format: [always|madvise|never]
        Can be used to control the default behavior of the system
        with respect to transparent hugepages.
        See Documentation/admin-guide/mm/transhuge.rst
        for more details.

On Raspberry Pi, the boot command line can typically be updated in /boot/cmdline.txt and the current boot command line used by the running kernel can be seen in /proc/cmdline.

N.B.:

  • This recipe is explained in more details here and here
  • There is a user space library called libhugetlbfs which offers a layer of abstraction on top of the kernel's hugetlbfs mechanism described here. It comes with library services like get_huge_pages() and accompanying tools like hugectl. The goal of this user space service is to map the heap and text+data segments of STATICALLY linked executables into huge pages (the mapping of dynamically linked programs is not supported). All of this relies on the kernel features described in this answer.