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

Предыдущая  Содержание  Следующая V*D*V

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

 

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

 

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

 

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) сообщает компилятору, что создаётся внешняя ссылка.

 

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

Предыдущая  Содержание  Следующая