Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внутренние механизмы ядра | Вперед |
Локальные переменные процессора
Переменные, закреплённые за процессором (per-CPU data). Определены в <linux/percpu.h>. Основное достоинство таких переменных в том, что если некоторую функциональность можно разумно распределить между такими переменными, то они не потребуют взаимных блокировок доступа в SMP. API, предоставляемые для работы с локальными данными процессора, на время работы с такими переменными запрещают вытеснение в режиме ядра.
Вторым свойством локальных данных процессора является то, что такие данные позволяют существенно уменьшить недостоверность данных, хранящихся в кэше. Это происходит потому, что процессоры поддерживают свои кэши в синхронизированном состоянии. Если один процессор начинает работать с данными, которые находятся в кэше другого процессора, то первый процессор должен обновить содержимое своего кэша. Постоянное аннулирование находящихся в кэше данных, именуемое перегрузкой кэша (cash thrashing), существенно снижает производительность системы (до 3-4-х раз). Использование данных, связанных с процессорами, позволяет приблизить эффективность работы с кэшем к максимально возможной, потому что в идеале каждый процессор работает только со своими данными.
Предыдущая модель
Эта модель существует со времени ядер 2.4, но она остаётся столь же функциональной и широко используется и сейчас; в этой модели локальные данные процессора представляются как массив (любой структурной сложности элементов), который индексируется номером процессора (начиная с 0 и далее...), работа этой модели базируется на вызовах:
int get_cpu(); - получить номер текущего процессора и запретить вытеснение в режиме ядра.
put_cpu(); - разрешить вытеснение в режиме ядра.
Пример работы в этой модели:
int data_percpu[] = { 0, 0, 0, 0 }; int cpu = get_cpu(); data_percpu[ cpu ]++; put_cpu();
Понятно, что поскольку запрет вытеснения в режиме ядра является принципиально важным условием, код, работающий с локальными переменными процессора, не должен переходить в блокированное состояние (по собственной инициативе). Почему код, работающий с локальными переменными процессора не должен вытесняться? :
- Если выполняющийся код вытесняется и позже восстанавливается для выполнения на другом процессоре, то значение переменной cpu больше не будет актуальным, потому что эта переменная будет содержать номер другого процессоpa.
- Если некоторый другой код вытеснит текущий, то он может параллельно обратиться к переменной data_percpu[] на том же процессоре, что соответствует состоянию гонок за ресурс.
Новая модель
Новая модель введена рассчитывая на будущие развитие, и на обслуживание весьма большого числа процессоров в системе, она упрощает работу с локальными переменными процессора, но на настоящее время ещё не так широко используется.
Статические определения (на этапе компиляции):
DEFINE_PER_CPU( type, name );
- создается переменная типа type с именем name, которая имеет отдельный экземпляр для каждого процессора в системе, если необходимо объявить такую переменную с целью избежания предупреждений компилятора, то необходимо использовать другой макрос:
DECLARE_PER_CPU( type, name );
Для работы с экземплярами этих переменных используются макросы:
- get_cpu_var( name ); - вызов возвращает L-value экземпляра указанной переменной на текущем процессоре, при этом запрещается вытеснение кода в режиме ядра.
- put_cpu_var( name ); - разрешает вытеснение.
Ещё один вызов возвращает L-value экземпляра локальной переменной другого процессора:
- per_cpu( name, int cpu ); - этот вызов не запрещает вытеснение кода в режиме ядра и не обеспечивает никаких блокировок, для его использования необходимы внешние блокировки в коде.
Пример статически определённой переменной:
DECLARE_PER_CPU( long long, xxx ); get_cpu_var( xxx )++; put_cpu_var( xxx );
Динамические определения (на этапе выполнения) — это другая группа API: динамически выделяют области фиксированного размера, закреплённые за процессором:
void *alloc_percpu( type ); void *__alloc_percpu( size_t size, size_t align ); void free_percpu( const void *data );
Функции размещения возвращают указатель на экземпляр области данных, а для работы с таким указателем вводятся вызовы, аналогичные случаю статического распределения:
- get_cpu_ptr( ptr );
- вызов возвращает указатель (типа
void*) на экземпляра указанной переменной на текущем процессоре, при
этом запрещается вытеснение кода в режиме ядра.
- put_cpu_ptr( ptr );
- разрешает вытеснение.
- per_cpu_ptr( ptr, int cpu );
- возвращает указатель на экземпляра
указанной переменной на другом процессоре.
Пример динамически определённой переменной:
long long *xxx = (long long*) alloc_percpu( long long ); ++*get_cpu_ptr( xxx ); put_cpu_var( xxx );
Требование не блокирумости кода, работающего с локальными данными процесса, остаётся актуальным и в этом случае.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Атомарные переменные и операции | Блокировки |