Наши партнеры

UnixForum






Книги по Linux (с отзывами читателей)

Библиотека сайта rus-linux.net

На главную -> MyLDP -> Тематический каталог -> Аппаратное обеспечение

Что каждый программист должен знать о памяти. Часть 6: На что еще способны программисты

Оригинал: Memory, part 6: More things programmers can do
Автор: Ulrich Drepper
Дата публикации: October 31, 2007
Перевод: М.Ульянов
Дата перевода: февраль 2010 г.

6.5.4 Политика VMA

Чтобы установить политику VMA для определенного диапазона адресов, нужно использовать следующий интерфейс:

#include <numaif.h>

long mbind(void *start, unsigned long len,
           int mode,
           unsigned long *nodemask,
           unsigned long maxnode,
           unsigned flags);

В результате регистрируется новая политика VMA для диапазона [start, start + len). Поскольку память оперирует страницами, начальный адрес должен быть выровнен по границе страницы, а значение len округлено до размера следующей страницы.

Уже знакомый нам параметр mode так же определяет политику, значение нужно выбрать из списка в разделе 6.5.1. Аналогично с set_mempolicy, параметр nodemask используется не всеми политиками. Тут ничего не изменилось.

Семантика интерфейса mbind определяется значением параметра flags. По умолчанию, если flags равен нулю, данный системный вызов просто устанавливает политику VMA для указанного диапазона адресов. Уже существующие привязки и соответствия не затрагиваются. Если этого не достаточно, можно использовать один из трех доступных на сегодня флагов для изменения поведения. Флаги могут использоваться как по отдельности, так и вместе:

MPOL_MF_STRICT

Вызов mbind завершится неудачей в случае если не все страницы находятся на узлах, определенных через nodemask. При использовании данного флага совместно с MPOL_MF_MOVE и/или с MPOL_MF_MOVEALL вызов завершится неудачей, если хоть одна страница не может быть перемещена.

MPOL_MF_MOVE

Ядро попытается переместить любую страницу адресного пространства, находящуюся на узле, не указанном в nodemask. По умолчанию перемещаются только страницы, используемые таблицами страниц исключительно текущего процесса.

MPOL_MF_MOVEALL

Данный флаг похож на MPOL_MF_MOVE, но в отличие от последнего, ядро попытается переместить все страницы, а не только используемые исключительно текущим процессом. Это приводит к последствиям для всей системы, поскольку влияет на доступ других процессов (которые могут иметь разных владельцев) к данным страницам. Поэтому для выполнения операции необходимы соответствующие привилегии (в частности, CAP_NICE).

Обратите внимание, что поддержка MPOL_MF_MOVE и MPOL_MF_MOVEALL была добавлена только в ядре Linux 2.6.16.

Вызов mbind без флагов наиболее полезен, когда нужно определить политику для только что зарезервированного адресного диапазона, прежде чем будут выделены какие-либо страницы.

void *p = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_ANON, -1, 0);
if (p != MAP_FAILED)
  mbind(p, len, mode, nodemask, maxnode, 0);

Данный код резервирует диапазон адресного пространства в len байт и устанавливает политику mode в отношении узлов памяти nodemask. Если в mmap не использовался флаг MAP_POPULATE, то к моменту вызова mbind не было выделено никакой памяти и поэтому новая политика без проблем будет применена ко всем страницам в указанной области адресного пространства.

Флаг MPOL_MF_STRICT может быть использован для определения, присутствуют ли в адресном диапазоне страницы, находящиеся на узлах, не указанных в nodemask. Никаких изменений не производится. Если все страницы "на своих местах" - то есть находятся на указанных узлах, политика VMA для области адресного пространства успешно меняется на mode.

В случае, если требуется ребалансировка памяти, может появиться необходимость в перемещении страниц с одного узла на другой. Для этого вызывается mbind с флагом MPOL_MF_MOVE, причем перемещению могут быть подвергнуты только страницы, используемые исключительно текущим процессом. Если теми же данными пользуются другие потоки или процессы, то такие страницы переместить с помощью даннного флага невозможно.

Пусть функции mbind переданы сразу два флага, MPOL_MF_STRICT и MPOL_MF_MOVE. Тогда ядро попытается переместить все страницы, находящиеся "не на тех" узлах. В случае невозможности такой операции вызов закончится неудачей. Такой способ можно применять для поиска узла (или множества узлов), способного вместить все страницы. Прежде чем подходящий узел будет найдет, понадобится сделать несколько вызовов подряд.

Использование MPOL_MF_MOVEALL оправдано только если выполнение текущего процесса - основное предназначение компьютера. Причина тому - при использовании данного флага перемещаются даже те страницы, которые хранятся сразу в нескольких таблицах страниц, и такое поведение негативно отразится на других процессах. Так что данный флаг следует применять с осторожностью.

6.5.5 Запросы информации об узлах

Интерфейс get_mempolicy можно использовать для запросов различной информации об указанном адресе.

#include <numaif.h>
long get_mempolicy(int *policy,
             const unsigned long *nmask,
             unsigned long maxnode,
             void *addr, int flags);

Когда get_mempolicy вызывается без установленных флагов в параметре flags, информация об используемой политике для адреса addr сохраняется в слове, на которое указывает policy и в битовой маске узлов, на которую указывает nmask. Если addr попадает в область адресного пространства, для которой установлена политика VMA, то возвращается информация о данной политике. Иначе возвращается информация о политике задач либо - при необходимости - о системной политике по умолчанию.

Если установлен флаг MPOL_F_NODE и addr обслуживается политикой MPOL_INTERLEAVE, то в слово, на которое указывает policy, записывается номер узла, на котором произойдет следующее выделение памяти. Эта информация впоследствии может быть использована для привязки потока, который будет выполняться на "свежевыделенной" памяти, что позволит минимизировать расходы на достижение "соседства", особенно если поток еще только предстоит создать.

Флаг MPOL_F_ADDR применяется для получения еще одного типа информации. При его установке, в слово, на которое указывает policy, записывается номер узла, на котором была выделена память для страницы, содержащей addr. Эту информацию можно использовать для принятия решений о возможном перемещении страниц; для выбора потока, который сможет работать с данной областью наиболее эффективно, и для многих других целей.

Процессор (а с ним и узел памяти), на котором выполняется поток, меняется гораздо чаще, чем выделенная для потока память. Перемещение страниц памяти происходит только в крайних случаях или в случае явных запросов на данное действие. Поток может быть назначен другому процессору в результате ребалансировки загрузок ЦПУ. В связи с этим, информация о текущем процессоре и узле быстро утрачивает актуальность. Но тем не менее, планировщик будет стараться держать поток на одном и том же процессоре, может даже на одном и том же ядре, для минимизации потерь производительности, связанных с понятием "холодного кэша". Следовательно, следить за информацией о текущем процессоре и узле может быть полезно, но все-таки нельзя забывать о том, что данная информация может измениться в любой момент.

Для запросов информации об узлах данного диапазона виртуального адресного пространства libNUMA предоставляет два интерфейса:

#include <libNUMA.h>

int NUMA_mem_get_node_idx(void *addr);
int NUMA_mem_get_node_mask(void *addr,
                           size_t size,
                           size_t __destsize,
                           memnode_set_t *dest);

NUMA_mem_get_node_mask устанавливает в dest биты всех узлов памяти, на которых выделены (или будут выделены) страницы диапазона [addr, addr+size) в соответствии с установленной для данной области политикой. А NUMA_mem_get_node_idx лишь обращается к адресу addr и возвращает номер узла памяти, на котором выделен (либо будет выделен) данный адрес. Эти интерфейсы использовать проще, чем get_mempolicy, и в общем случае, пожалуй, следует как раз их и применять.

Процессор, используемый потоком на данный момент, можно узнать, используя sched_getcpu (см. раздел 6.4.3). Основываясь на полученной информации, программа сможет определить узлы памяти, локальные по отоношению к процессору, используя интерфейс libNUMA NUMA_cpu_to_memnode:

#include <libNUMA.h>

int NUMA_cpu_to_memnode(size_t cpusetsize,
                        const cpu_set_t *cpuset,
                        size_t memnodesize,
                        memnode_set_t *
                        memnodeset);

Вызов этой функции установит (во множестве узлов памяти, на которое указывает четвертый параметр) все биты, соответствующие узлам памяти, локальным по отношению к процессорам во множестве, на которое указывает второй параметр. Так же как и сведения о процессоре, данная информация будет актуальна лишь до того момента, пока не изменится конфигурация машины (например, будут удалены либо добавлены процессоры)

Биты в объектах memnode_set_t могут быть использованы в вызовах низкоуровневых функций вроде get_mempolicy. Хотя в целом удобнее использовать другие функции libNUMA. Обратное отображение можно выполнить через:

#include <libNUMA.h>

int NUMA_memnode_to_cpu(size_t memnodesize,
                        const memnode_set_t *
                        memnodeset,
                        size_t cpusetsize,
                        cpu_set_t *cpuset);

Множество битов в итоговом cpuset определяет процессоры, локальные по отношению к узлам памяти в соответствующем битовом множестве memnodeset. В случае обоих интерфейсов программист должен помнить о том, что со временем данные сведения меняются (особенно при использовании горячей замены ЦПУ). Часто во входящем множестве установлены все биты. Казалось бы, бессмысленно, но нет: таким образом можно получить весь набор доступных процессоров, который затем можно передать в sched_getaffinity и NUMA_cpu_to_memnode, чтобы определить, к каким узлам памяти поток вообще может иметь прямой доступ.

Замечания и предложения по переводу принимаются по адресу michael@right-net.ru. М.Ульянов.


Назад Оглавление Вперед