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

UnixForum





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

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра 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 );

Требование не блокирумости кода, работающего с локальными данными процесса, остаётся актуальным и в этом случае.


Предыдущий раздел: Оглавление Следующий раздел:
Атомарные переменные и операции   Блокировки