Выбрать главу

Подведем итоги. Стек ядра занимает одну или две страницы памяти, в зависимости от конфигурации, которая выполняется перед компиляцией ядра. Следовательно, размер стека ядра может иметь диапазон от 4 до 16 Кбайт. Исторически обработчики прерываний совместно использовали стек прерванного ими процесса. При появлении стеков ядра размером в одну страницу памяти обработчикам прерываний были назначены свои стеки. В любом случае неограниченная рекурсия и использование функций вроде alloca() явно не допустимы.

Честная игра со стеком

В любой функции необходимо сокращать использование стека до минимума. Хотя не существует твердых правил, тем не менее следует поддерживать максимальный суммарный объем всех локальных переменных (также известных как автоматические переменные или переменные, выделенные в стеке) не больше нескольких сотен байтов. Опасно статически выделять большие объекты в стеке, такие как большие массивы структур. В противном случае выделение памяти в стеке будет выполняться так же, как и в пространстве пользователя. Переполнение стека происходит незаметно и обычно приводит к проблемам. Так как ядро не выполняет никакого управления стеком, то данные стека просто перепишут все, что находится за стеком. В первую очередь пострадает структура thread_info, которая расположена в самом конце стека процесса (вспомните главу 3). За пределами стека все данные ядра могут пропасть. В лучшем случае при переполнении стека произойдет сбой в работе машины. В худших случаях может произойти повреждение данных.

В связи с этим, для выделения больших объемов памяти необходимо использовать одну из динамических схем выделения памяти, которые были рассмотрены раньше в этой главе.

Отображение верхней памяти

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

Для аппаратной платформы x86 вся физическая память свыше 896 Мбайт помечается как верхняя память, и она не может автоматически или постоянно отображаться в адресное пространство ядра, несмотря на то что процессоры платформы x86 могут адресовать до 4 Гбайт физической памяти (до 64 Гбайт при наличии расширения РАЕ[66]). После выделения эти страницы должны быть отображены в логическое адресное пространство ядра. Для платформы x86 страницы верхней памяти отображаются где-то между отметками 3 и 4 Гбайт.

Постоянное отображение

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

void *kmap(struct page *page);

Эта функция работает как со страницами нижней, так и верхней памяти. Если структура page соответствует странице нижней памяти, то просто возвращается виртуальный адрес. Если страница расположена в верхней памяти, то создается постоянное отображение этой страницы памяти и возвращается полученный логический адрес. Функция kmap() может переводить процесс в состояние ожидания, поэтому ее можно вызывать только в контексте процесса.

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

void kunmap(struct page *page);

Данная функция отменяет отображение страницы памяти, связанной с параметром page.

Временное отображение

В случаях, когда необходимо создать отображение страниц памяти в адресное пространство, а текущий контекст не может переходить в состояние ожидания, ядро предоставляет функцию временного отображении (которое также называется атомарным отображением). Существует некоторое количество зарезервированных постоянных отображений, которые могут временно выполнять отображение "на лету". Ядро может автоматически отображать страницу верхней памяти в одно из зарезервированных отображений. Временное отображение может использоваться в коде, который не может переходить в состояние ожидания, как, например, контекст прерывания, потому что полученное отображение никогда не блокируется.

Установка временного отображения выполняется с помощью следующей функции.

void *kmap_atomic(struct page *page, enum km_type type);

Параметр type — это одно из значений показанного ниже перечисления, определенного в файле <asm/kmap_types.h>, которое описывает цель временного отображения.

enum km_type {

 KM_BOUNCE_READ,

 KM_SKB_SUNRPC_DATA,

 KM_SKB_DATA_SOFTIRQ,

 KM_USER0,

 KM_USER1,

 KM_BIO_SRC_IRQ,

 KM_BIO_DST_IRQ,

 KM_PTE0,

 KM_PTE1,

 KM_PTE2,

 KM_IRQ0,

 KM_IRQ1,

 KM_SOFTIRQ0,

 KM_SOFTIRQ1,

 KM_TYPE_NR

};

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

Отменить отображение можно с помощью следующей функции.

void kunmap_atomic(void *kvaddr, enum km_type type);

Эта функция также не блокирующая. На самом деле для большинства аппаратных платформ она ничего не делает, за исключением разрешения преемптивности ядра, потому что временное отображение действует только до тех пор, пока не создано новое временное отображение. Поэтому ядро просто "забывает" о вызове функции kmap_atomic(), и функции kunmap_atomic() практически ничего не нужно делать. Следующее атомарное отображение просто заменяет предыдущее.

Выделение памяти, связанной с определенным процессором

В современных операционных системах широко используются данные, связанные с определенными процессорами (per-CPU data). Это данные, которые являются уникальными для каждого процессора. Данные, связанные с процессорами, хранятся в массиве. Каждый элемент массива соответствует своему процессору системы. Номер процессора является индексом в этом массиве. Таким образом была реализована работа с данными, связанными с определенным процессором, в ядрах серии 2.4. В таком подходе нет ничего плохого, поэтому значительная часть кода ядра в серии 2.6 все еще использует этот интерфейс. Данные объявляются следующим образом,

unsigned long my_percpu[NR_CPUS];

Доступ к этим данным выполняется, как показано ниже.

int cpu;

cpu = get_cpu(); /* получить номер текущего процессора и запретить

                    вытеснение в режиме ядра */

вернуться

66

РАЕ — Physical Address Extension (расширение физической адресации). Эта функция процессоров x86 позволяет физически адресовать до 36 разрядов (64 Гбайт) памяти, несмотря на то что размер виртуального адресного пространства соответствует только 32 бит.