Главная » Ядро Linux » Новый интерфейс percp u

0

В ядрах  серии  2.6  предложен новый интерфейс,  именуемый percpu,  который служит для  создания данных  и работы  с данными, связанными с определенным процессором.   Этот  интерфейс  обобщает предыдущий пример. При  использовании нового подхода  работа  с per-CPU-данными упрощается.

Рассмотренный ранее  метод  работы   с данными,  которые связаны с  определенным  процессором, является вполне законным.  Новый интерфейс возник из  необходимости иметь  более  простой и  мощный метод  работы  с данными, связанными с процессорами, на  больших компьютерах с  симметричной мультипроцессорностью.

Все  подпрограммы объявлены в файле   <linux/percpu.h> .  Описания же  находятся  в файлах   mm/slab. с  и  <asm/percpu.h> .

Работа с данными, связанными

с процессорами, на этапе компиляции

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

DEFINE_PER_CPU(type, name);

Это  описание создает  переменную типа  typ e  с  именем name, которая имеет  интерфейс связи  с  каждым процессором  в  системе.  Если  необходимо объявить соответствующую  переменную  с  целью  избежания предупреждений компилятора, то необходимо использовать  следующий макрос.

DECLARE_PER_CPU(type, name);

Работать с  этими переменными можно с помощью  функций  get_cpu_va r  ()  и put_cpu_va r  ()  .  Вызов   функции  get_cpu_va r ()   возвращает 1-значенис  (левый операнд,  1-value)   указанной переменной на  текущем процессоре.  Этот  вызов  также  запрещает вытеснение  кода  в  режиме ядра,  а соответственный вызов  функции put_cpu_va r ()   разрешает вытеснение.

get_cpu_var(name)++;   /* увеличить на единицу значение переменной name, связанное с текущим процессором */

put_cpu_var();        /* разрешить вытеснение кода в режиме ядра */ Можнотакжеполучитьдоступкпеременной,связаннойсдругимпроцессором.

per_cpu(name, cpu)++;  /* увеличить значение переменной name

на указанном процессоре */

Использовать функцию per_cp u ()   необходимо осторожно, так  как  этот  вызов  не запрещает вытеснение кода  и  не  обеспечивает никаких блокировок.  Необходимость использования блокировок при  работе  с данными, связанными с определенным процессором,  отпадает,  только  если  к  этим  данным может  обращаться один  процессор. Если  процессоры обращаются к данным других  процессоров, то  необходимо использовать   блокировки.  Будьте  осторожны!   Применение  блокировок рассматривается в  главе 8,  "Введение  в синхронизацию выполнения  кода  ядра",  и  главе  9,  "Средства синхронизации в ядре".

Необходимо  сделать  еще  одно  важное  замечание относительно создания данных. связанных с процессорами, на  этапе  компиляции.  Загружаемые модули  не  могут   использовать те  из  них,  которые объявлены не  в самом  модуле,  потому  что  компоновщик  создает  эти  данные в  специальных сегментах  кода  (а  именно,   .data.percpu) . Если  необходимо использовать данные,  связанные  с  процессорами,  в  загружаемых модулях  ядра,  то  нужно   создать  эти  данные для  каждого   модуля   отдельно  или  использовать динамически  создаваемые данные.

Работа с данными процессоров на этапе выполнения

Для динамического создания данных, связанных с  процессорами, в  ядре реализован специальный распределитель памяти, который имеет интерфейс, аналогичный kmallo c () . Эти функции позволяют создать экземпляр участка памяти для каждого процессора в системе. Прототипы этих функций объявлены в  файле

<linux/percpu.h > следующим образом.

void *alloc percpu(type); /* макрос */

void *  alloc_percpu(size_t size, size_t align);

voidfree_percpu(constvoid*) ;

Функция allo c   percpu ()  создает экземпляр объекта заданного типа (выделяет память) для каждого процессора в системе. Эта функция является оболочкой вокруг функции __alloc_percp u (). Последняя функция принимает в качестве аргументов количество байтов памяти,  которые необходимо выделить, и количество байтов, но которому необходимо выполнить выравнивание этой области памяти. Функция alloc_percp u  ()  выполняет выравнивание по той границе, которая используется для указанного типа данных. Такое выравнивание соответствует обычному поведению, как показано в следующем примере.

struct rabid_cheetah = alloc_percpu(struct rabid_cheetah);,

что  аналогично следующему вызову.

struct rabid_cheetah =   alloc_percpu(sizeof (struct rabid_cheetah), alignof  (struct rabid_cheetah));

Оператор __alignof_ _  — это  расширение, предоставляемое компилятором gcc, который  возвращает количество байтов,  по  границе  которого  необходимо выполнять  выравнивание  (или   рекомендуется выполнять для  тех  аппаратных платформ, у которых нет  жестких  требований к выравниванию данных  в памяти). Синтаксис этого  вызова  такой  же  как  и  у оператора sizeo f ().  В  примере, показанном ниже, для  аппаратной платформы х86 будет  возвращено значение 4.

  alignof__ (unsigned long)

Пр и  передаче 1-значения  (левое   значение , lvalue)   возвращается  максимально возможное  выравнивание , которо е  может   потребоватьс я  для  этого   1-значения. Например,  1-значение внутри  структуры может  иметь  большее значение  выравнивания, чем  это  необходимо для  хранения того  же типа  данных  за пределами структуры, что  связано с  особенностями выравнивания  структур  данных  в  памяти. Проблемы выравнивания более  подробно рассмотрены в главе  19, "Переносимость".

Соответствующий вызов  функции   freepercp u ()   освобождает память,  которую занимают соответствующие данные  на  всех  процессорах.

Функции alloc_percp u ()   и __alloc_percp u ()   возвращают указатель, который используется для косвенной ссылки на динамически созданные данные, связанные с каждым процессором в системе. Для  простого доступа  к данным ядро  предоставляет два  следующих макроса.

get_cpu_ptr(ptr) ;  /* возвращает указатель типа void на данные, соответствующие параметру ptr, связанные с текущим процессом*/

put_cpu_ptr(ptr);  /* готово, разрешаем вытеснение кода в режиме ядра

*/

Макрос get_cpu_pt r ()  возвращает указатель на  экземпляр данных, связанных с текущим процессором.  Этот  вызов  также  запрещает вытеснение кода  в режиме ядра, которое снова  разрешается вызовом функции  put_cpu_pt r () .

Рассмотрим пример использования этих  функций. Конечно, этот  пример не совсем  логичный, потому  что  память обычно необходимо выделять один  раз  (например, в некоторой функции инициализации),  использовать ее в разных  необходимых местах, а затем  освободить также  один  раз  (например, в некоторой функции,  которая  вызывается при  завершении работы). Тем  не  менее  этот  пример позволяет пояснить особенности использования.

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);

Эта функция не  запрещает вытеснение в режиме  ядра.  Если  вы "трогаете" данные,  связанные с другим процессором, то,  вероятно, необходимо применить блокировки.

Источник: Лав,  Роберт. Разработка ядра  Linux, 2-е  издание. : Пер.  с англ.  — М.  : ООО  «И.Д.  Вильяме» 2006. — 448 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии