воскресенье, 10 апреля 2011 г.

8.5. Копии переменных для каждого процессора


Копии переменных для каждого процессора
Per-CPU variables (По-Процессорные переменные) являются интересной особенностью ядра версии 2.6. Когда вы создаёте по-процессорную переменную, каждый процессор в системе получает собственную копию этой переменной. Желание сделать это может показаться странным, но оно имеет свои преимущества. Доступ к по-процессорным переменным не требует (почти) блокирования, потому что каждый процессор работает со своей собственной копией. По-процессорные переменные могут также оставаться в своих процессорных кэшах, что приводит к существенно более высокой производительности для часто обновляемых параметров.

Хороший пример использования по-процессорной переменной можно найти в сетевой подсистеме. Ядро поддерживает бесконечные счётчики, отслеживая, как много пакетов каждого типа было получено; эти счетчики могут быть обновлены тысячи раз в секунду. Вместо того, чтобы иметь дело с вопросами кэширования и блокировки, сетевые разработчики поместили счётчики статистики в по-процессорные переменные. Обновления происходят теперь без блокировки и быстро. В редких случаях, в которых пользовательское пространство делает запрос, чтобы увидеть значения счётчиков, это просто вопрос сложения версий каждого процессора и возвращения общей суммы.

Декларация по-процессорных переменных может быть найдена в . Чтобы создать по-процессорную переменную во время компиляции, используйте этот макрос:

DEFINE_PER_CPU(type, name);

Если переменная (которая будет называться name) является массивом, включается информация о типе массива. Таким образом, по-процессорный массив из трёх чисел был бы создан так:

DEFINE_PER_CPU(int[3], my_percpu_array);

По-процессорными переменными можно манипулировать почти без явного блокирования. Помните, что ядро версии 2.6 является вытесняющим; это бы не следует делать в середине критической секции, которая модифицирует по-процессорную переменную. Также будет не хорошо, если ваш процесс будет перемещён в другой процессор в середине доступа к по-процессорной переменной. По этой причине вы должны явно использовать макрос get_cpu_var для непосредственного доступа к данной переменной текущего процессора и вызвать put_cpu_var по окончании. Вызов get_cpu_var возвращает величину для прямого доступа к текущей процессорной версии переменной и отключает вытеснение. Поскольку возвращается сама переменная, она может быть напрямую использоваться для присваивания или управления. Например, один счётчик в сетевом коде увеличивается этими двумя командами:

get_cpu_var(sockets_in_use)++;
put_cpu_var(sockets_in_use);

Вы можете получить доступ другой процессорной копии переменной с помощью:

per_cpu(variable, int cpu_id);

Если вы пишете код, который включает в себя доступ к значению каждой по-процессорной переменной, вам, конечно, необходимо реализовать схему блокировки, чтобы сделать доступ безопасным.

Динамическое создание по-процессорной переменной также возможно. Эти переменные могут быть созданы так:

void *alloc_percpu(type);
void *__alloc_percpu(size_t size, size_t align);

В большинстве случаев alloc_percpu делает свою работу, вы можете вызвать __alloc_percpu в случаях, когда требуется особое выравнивание. В любом случае, по-процессорная переменная может быть возвращена системе с помощью free_percpu. Доступ к динамически созданной по-процессорной переменной осуществляется через per_cpu_ptr:

per_cpu_ptr(void *per_cpu_var, int cpu_id);

Этот макрос возвращает указатель на версию per_cpu_var, соответствующей данному cpu_id. Если вы просто читаете другую процессорную версию переменной, вы можете разыменовать указатель и что-то сделать с ней. Однако, если вы манипулируете текущей процессорной версией, то вероятно, сначала надо убедиться, что вы не можете быть передвинуты из этого процессора. Если весь доступ к по-процессорной переменной происходит с удержанием спин-блокировки, всё хорошо. Однако, обычно для блокировки вытеснения при работе с переменной вам необходимо использовать get_cpu. Таким образом, код, использующий динамические по-процессорные переменные, как правило, выглядит следующим образом:

int cpu;

cpu = get_cpu( )
ptr = per_cpu_ptr(per_cpu_var, cpu);
/* работаем с ptr */
put_cpu( );

При использовании по-процессорных переменных во время компиляции, макросы get_cpu_var и put_cpu_var заботятся об этих деталях. Динамические по-процессорные переменные требуют более явной защиты.

По-процессорные переменные могут быть экспортированы в модули, но вы должны использовать специальную версию макроса:

EXPORT_PER_CPU_SYMBOL(per_cpu_var);
EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);

Для доступа к таким переменным внутри модуля, объявите её так:

DECLARE_PER_CPU(type, name);

Использование DECLARE_PER_CPU (вместо DEFINE_PER_CPU) сообщает компилятору, что создаётся внешняя ссылка.

Если вы хотите использовать по-процессорные переменные для создания простого целочисленного счётчика, взгляните на заготовленную реализацию в . Наконец, отметим, что некоторые архитектуры имеют ограниченный объём адресного пространства, доступного для по-процессорных переменных. Если вы создаёте по-процессорные переменные, вы должны попытаться сохранить их число небольшим.

Комментариев нет:

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.