Подтвердить что ты не робот

Каким образом указатели percpu реализованы в ядре Linux?

В многопроцессорных системах каждое ядро ​​может иметь свои собственные переменные. Я думал, что они разные переменные в разных адресах, хотя они находятся в одном процессе и имеют одно и то же имя.

Но мне интересно, как это реализует ядро? Предоставляет ли он часть памяти для хранения всех указателей percpu и каждый раз перенаправляет указатель на определенный адрес со сдвигом или что-то еще?

4b9b3361

Ответ 1

Нормальные глобальные переменные не относятся к процессору. Автоматические переменные находятся в стеке, а разные процессоры используют разные стеки, поэтому, естественно, они получают отдельные переменные.

Я думаю, вы имеете в виду инфраструктуру переменных Linux для каждого процессора.
Большая часть магии здесь (asm-generic/percpu.h):

extern unsigned long __per_cpu_offset[NR_CPUS];

#define per_cpu_offset(x) (__per_cpu_offset[x])

/* Separate out the type, so (int[3], foo) works. */
#define DEFINE_PER_CPU(type, name) \
    __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name

/* var is in discarded region: offset to particular copy we want */
#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())

Макрос RELOC_HIDE(ptr, offset) просто передает ptr заданное смещение в байтах (независимо от типа указателя).

Что он делает?

  • При определении DEFINE_PER_CPU(int, x) в специальном разделе .data.percpu создается целое число __per_cpu_x.
  • Когда ядро ​​загружено, этот раздел загружается несколько раз - один раз на процессор (эта часть магии не находится в коде выше).
  • Массив __per_cpu_offset заполняется расстояниями между копиями. Предположим, что используются 1000 байтов данных на процессор, __per_cpu_offset[n] будет содержать 1000*n.
  • Символ per_cpu__x будет перемещен во время загрузки на CPU 0 per_cpu__x.
  • __get_cpu_var(x), при запуске на CPU 3, перейдет на *RELOC_HIDE(&per_cpu__x, __per_cpu_offset[3]). Это начинается с CPU 0 x, добавляет смещение между данными CPU 0 и CPU 3 и, в конечном итоге, разыгрывает результирующий указатель.