Работа с данными процессоров на этапе выполнения
Работа с данными процессоров на этапе выполнения
Для динамического создания данных, связанных с процессорами, в ядре реализован специальный распределитель памяти, который имеет интерфейс, аналогичный kmalloc(). Эти функции позволяют создать экземпляр участка памяти для каждого процессора в системе. Прототипы этих функций объявлены в файле <linux/percpu.h> следующим образом.
void *alloc_percpu(type); / * макрос */
void *__alloc_percpu(size_t size, size_t align);
void free_percpu(const void*);
Функция alloc_percpu() создает экземпляр объекта заданного типа (выделяет память) для каждого процессора в системе. Эта функция является оболочкой вокруг функции __alloc_percpu(). Последняя функция принимает в качестве аргументов количество байтов памяти, которые необходимо выделить, и количество байтов, но которому необходимо выполнить выравнивание этой области памяти. Функция alloc_percpu() выполняет выравнивание по той границе, которая используется для указанного типа данных. Такое выравнивание соответствует обычному поведению, как показано в следующем примере.
struct rabid_cheetah = alloc_percpu(struct rabid_cheetah);
что аналогично следующему вызову.
struct rabid_cheetah = __alloc_percpu(sizeof(struct rabid_cheetah),
__alignof__(struct rabid_cheetah));
Оператор __alignof__ — это расширение, предоставляемое компилятором gcc, который возвращает количество байтов, по границе которого необходимо выполнять выравнивание (или рекомендуется выполнять для тех аппаратных платформ, у которых нет жестких требований к выравниванию данных в памяти). Синтаксис этого вызова такой же как и у оператора sizeof(). В примере, показанном ниже, для аппаратной платформы x86 будет возвращено значение 4.
__alignof__(unsigned long)
При передаче l-значения (левое значение, lvalue) возвращается максимально возможное выравнивание, которое может потребоваться для этого l-значения. Например, l-значение внутри структуры может иметь большее значение выравнивания, чем это необходимо для хранения того же типа данных за пределами структуры, что связано с особенностями выравнивания структур данных в памяти. Проблемы выравнивания более подробно рассмотрены в главе 19, "Переносимость".
Соответствующий вызов функции free_percpu() освобождает память, которую занимают соответствующие данные на всех процессорах.
Функции alloc_percpu() и __alloc_percpu() возвращают указатель, который используется для косвенной ссылки на динамически созданные данные, связанные с каждым процессором в системе. Для простого доступа к данным ядро предоставляет два следующих макроса.
get_cpu_ptr(ptr); /* возвращает указатель типа void на данные,
соответствующие параметру ptr, связанные с текущим процессом */
put_cpu_ptr(ptr); /* готово, разрешаем вытеснение кода в режиме ядра */
Макрос get_cpu_ptr() возвращает указатель на экземпляр данных, связанных с текущим процессором. Этот вызов также запрещает вытеснение кода в режиме ядра, которое снова разрешается вызовом функции put_cpu_ptr().
Рассмотрим пример использования этих функций. Конечно, этот пример не совсем логичный, потому что память обычно необходимо выделять один раз (например, в некоторой функции инициализации), использовать ее в разных необходимых местах, а затем освободить также один раз (например, в некоторой функции, которая вызывается при завершении работы). Тем не менее этот пример позволяет пояснить особенности использования.
void *percpu_ptr;
unsigned long *foo;
percpu_ptr = alloc_percpu(unsigned long);
if (!ptr)
/* ошибка выделения памяти ... */
foo = get_cpu_ptr(percpu_ptr);
/* работаем с данными foo ... */
put_cpu_ptr(percpu_ptr);
Еще одна функция — per_cpu_ptr() — возвращает экземпляр данных, связанных с указанным процессором.
per_cpu_ptr(ptr, cpu);
Эта функция не запрещает вытеснение в режиме ядра. Если вы "трогаете" данные, связанные с другим процессором, то, вероятно, необходимо применить блокировки.