Необходимо сделать еще одно важное замечание относительно создания данных. связанных с процессорами, на этапе компиляции. Загружаемые модули не могут использовать те из них, которые объявлены не в самом модуле, потому что компоновщик создает эти данные в специальных сегментах кода (а именно, .data.percpu
). Если необходимо использовать данные, связанные с процессорами, в загружаемых модулях ядра, то нужно создать эти данные для каждого модуля отдельно или использовать динамически создаваемые данные.
Работа с данными процессоров на этапе выполнения
Для динамического создания данных, связанных с процессорами, в ядре реализован специальный распределитель памяти, который имеет интерфейс, аналогичный kmalloc()
. Эти функции позволяют создать экземпляр участка памяти для каждого процессора в системе. Прототипы этих функций объявлены в файле
следующим образом.
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);