• Если некоторый другой код вытеснит текущий, то он может параллельно обратиться к переменной my_percpu
на том же процессоре, что соответствует состоянию гонок за ресурс.
Однако все опасения напрасны, потому что вызов функции get_cpu()
, которая возвращает номер текущего процессора, также запрещает вытеснение в режиме ядра. Соответствующий вызов функции put_cpu()
разрешает вытеснение кода в режиме ядра. Обратите внимание, что функция smp_processor_id()
, которая также позволяет получить номер текущего процессора, не запрещает вытеснения кода в режиме ядра, поэтому для безопасной работы следует использовать указанный выше метод.
В ядрах серии 2.6 предложен новый интерфейс, именуемый percpu , который служит для создания данных и работы с данными, связанными с определенным процессором. Этот интерфейс обобщает предыдущий пример. При использовании нового подхода работа с per-CPU-данными упрощается.
Рассмотренный ранее метод работы с данными, которые связаны с определенным процессором, является вполне законным. Новый интерфейс возник из необходимости иметь более простой и мощный метод работы с данными, связанными с процессорами, на больших компьютерах с симметричной мультипроцессорностью.
Все подпрограммы объявлены в файле . Описания же находятся в файлах mm/slab.с
и .
Работа с данными, связанными с процессорами, на этапе компиляции
Описать переменную, которая связана с определенным процессором, на этапе компиляции можно достаточно просто следующим образом.
DEFINE_PER_CPU(type, name);
Это описание создает переменную типа type
с именем name
, которая имеет интерфейс связи с каждым процессором в системе. Если необходимо объявить соответствующую переменную с целью избежания предупреждений компилятора, то необходимо использовать следующий макрос.
DECLARE_PER_CPU(type, name);
Работать с этими переменными можно с помощью функций get_cpu_var()
и put_cpu_var()
. Вызов функции get_cpu_var()
возвращает l-значение (левый операнд, l-value) указанной переменной на текущем процессоре. Этот вызов также запрещает вытеснение кода в режиме ядра, а соответственный вызов функции put_cpu_var()
разрешает вытеснение.
get_cpu_var(name)++; /* увеличить на единицу значение переменной
name, связанное с текущим процессором */
put_cpu_var(); /* разрешить вытеснение кода в режиме ядра */
Можно также получить доступ к переменной, связанной с другим процессором.
per_cpu(name, cpu)++; /* увеличить значение переменной name
на указанном процессоре */
Использовать функцию per_cpu()
необходимо осторожно, так как этот вызов не запрещает вытеснение кода и не обеспечивает никаких блокировок. Необходимость использования блокировок при работе с данными, связанными с определенным процессором, отпадает, только если к этим данным может обращаться один процессор. Если процессоры обращаются к данным других процессоров, то необходимо использовать блокировки. Будьте осторожны! Применение блокировок рассматривается в главе 8, "Введение в синхронизацию выполнения кода ядра", и главе 9, "Средства синхронизации в ядре".
Необходимо сделать еще одно важное замечание относительно создания данных. связанных с процессорами, на этапе компиляции. Загружаемые модули не могут использовать те из них, которые объявлены не в самом модуле, потому что компоновщик создает эти данные в специальных сегментах кода (а именно, .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()
выполняет выравнивание по той границе, которая используется для указанного типа данных. Такое выравнивание соответствует обычному поведению, как показано в следующем примере.
Читать дальше