Копии переменных для каждого процессора |
Предыдущая Содержание Следующая |
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>. Наконец, отметим, что некоторые архитектуры имеют ограниченный объём адресного пространства, доступного для по-процессорных переменных. Если вы создаёте по-процессорные переменные, вы должны попытаться сохранить их число небольшим. |
Предыдущая Содержание Следующая |