Выделение памяти, связанной с определенным процессором
В современных операционных системах широко используются данные, связанные с определенными процессорами (per-CPU data). Это данные, которые являются уникальными для каждого процессора. Данные, связанные с процессорами, хранятся в массиве. Каждый элемент массива соответствует своему процессору системы. Номер процессора является индексом в этом массиве. Таким образом была реализована работа с данными, связанными с определенным процессором, в ядрах серии 2.4. В таком подходе нет ничего плохого, поэтому значительная часть
кода ядра в серии 2.6 все еще использует этот интерфейс. Данные объявляются следующим образом,
unsigned long my_percpu[NR_CPUS];
Доступ к этим данным выполняется, как показано ниже.
int cpu;
cpu = get_cpu; /* получить номер текущего процессора и запретить
вытеснение в режиме ядра */
my_percpu[cpu]++;
printk("значение данных процессора cpu=%d равно %ld\n",
cpu, my_percpu[cpu]);
put_cpu; /* разрешить вытеснение в режиме ядра */
Обратите внимание, что не нужно использовать никаких блокировок, потому что данные уникальны для каждого процессора. Поскольку никакой процессор, кроме текущего, не может обратиться к соответствующему элементу данных, то не может возникнуть и никаких проблем с конкурентным доступом, а следовательно, текущий процессор может безопасно обращаться к данным без блокировок.
Возможность вытеснения процессов в режиме ядра— единственное, из-за чего могут возникнуть проблемы. В преемптивном ядре могут возникнуть следующие две проблемы.
• Если выполняющийся код вытесняется и позже планируется для выполнения на другом процессоре, то значение переменной
cpu
больше не будет действительным, потому что эта переменная будет содержать номер другого процессоpa. (По той же причине, после получения номера текущего процессора, нельзя переходить в состояние ожидания.)
• Если некоторый другой код вытеснит текущий, то он может параллельно обратиться к переменной
my_percpu
на том же процессоре, что соответствует состоянию гонок за ресурс.
Однако все опасения напрасны, потому что вызов функции
get_cpu
, которая возвращает номер текущего процессора, также запрещает вытеснение в режиме ядра. Соответствующий вызов функции
put_cpu
разрешает вытеснение кода в режиме ядра. Обратите внимание, что функция
smp_processor_id
, которая также позволяет получить номер текущего процессора, не запрещает вытеснения кода в режиме ядра, поэтому для безопасной работы следует использовать указанный выше метод.
Новый интерфейс
percpu
В ядрах серии 2.6 предложен новый интерфейс, именуемый percpu, который служит для создания данных и работы с данными, связанными с определенным процессором. Этот интерфейс обобщает предыдущий пример. При использовании нового подхода работа с per-CPU-данными упрощается.
Рассмотренный ранее метод работы с данными, которые связаны с определенным процессором,
является вполне законным. Новый интерфейс возник из необходимости иметь более простой и мощный метод работы с данными, связанными с процессорами, на больших компьютерах с симметричной мультипроцессорностью.
Все подпрограммы объявлены в файле
<linux/percpu.h>
. Описания же находятся в файлах
mm/slab.с
и
<asm/percpu.h>
.
Работа с данными, связанными с процессорами, на этапе компиляции
Описать переменную, которая связана с определенным процессором, на этапе компиляции можно достаточно просто следующим образом.
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
). Если необходимо использовать данные, связанные с процессорами, в загружаемых модулях ядра, то нужно создать эти данные для каждого модуля отдельно или использовать динамически создаваемые данные.