Работа с аппаратным кэшем и TLB-буфером в Linux

Аппаратные кэши и TLB-буферы играют важнейшую роль в повышении производительности современных компьютеров. Для уменьшения количества промахов мимо кэша и TLB — буфера разработчики ядра применяют ряд приемов.
Работа с аппаратным кэшем
Как было замечено ранее в этой главе, аппаратные кэши адресуются с помощью строк кэшей. Макрос li cache bytes возвращает размер строки кэша в байтах. В процессорах Intel, предшествовавших модели Pentium 4, этот макрос возвращал 32; для Pentium 4 он возвращает 128.
Чтобы оптимизировать процент попаданий в кэш, ядро принимает во внимание архитектуру и принимает следующие решения:
— наиболее часто используемые поля структуры данных располагаются внутри этой структуры с меньшими смещениями, чтобы они могли быть кэшированы в одной строке;
— при выделении большого количества структур ядро старается сохранить каждую структуру в памяти так, чтобы все строки кэша заполнялись равномерно.
Синхронизация кэшей выполняется микропроцессорами 80×86 автоматически, и поэтому для этих процессоров ядро Linux не выполняет сброс аппаратных кэшей. Однако оно все-таки предоставляет интерфейс для сброса кэшей процессорам, не синхронизирующим кэши.
Работа с TLB-буфером
Процессоры не могут автоматически синхронизировать свои TLB-кэши, потому что ядро, а не аппаратная часть принимает решение о дальнейшей корректности отображения между линейным адресом и физическим.
В Linux 2.6 имеется несколько методов сброса TLB-буферов, которые следует применять аккуратно, в зависимости от вида изменения таблицы страниц.
Несмотря на богатый выбор методов TLB-буфера, предлагаемых типичным ядром Linux, каждый микропроцессор обычно имеет куда более ограниченный набор ассемблерных инструкций для сброса TLB-буферов. В этом смысле одной из самых гибких платформ является UltraSPARC фирмы Sun. В противоположность ей, микропроцессоры Intel предлагают только два способа объявления содержимого TLB-буферов недействительными:
— все модели Pentium автоматически сбрасывают записи TLB-буферов, относящиеся к неглобальным страницам, когда в регистр загружается какое-либо значение;
— в Pentium Pro и более поздних моделях ассемблерная инструкция invipg делает недействительной одну запись TLB-буфера, отображающую заданный линейный адрес.
Макросы операционной системы Linux эксплуатируют данные аппаратные методы. Эти макросы являются основными составляющими архитектурно-независимых методов.
Обратите внимание, что отсутствует метод fiush tib pgtabies. Дело в том, что в архитектуре 80×86 ничего не нужно предпринимать, когда таблица страниц отсоединяется от таблицы-родителя, и поэтому функция, реализующая этот метод, пуста.
Архитектурно-независимые методы объявления TLB-буферов недействительными довольно просто расширяются на многопроцессорные системы. Функция, выполняющаяся на некотором процессоре, посылает другим процессорам межпроцессорное прерывание, которое заставляет их выполнить соответствующую функцию сброса TLB-буфера.
Вообще говоря, любое переключение процессов подразумевает смену набора активных таблиц страниц. Записи локальных TLB-буферов, имеющие отношение к старым таблицам страниц, должны быть сброшены. Это делается автоматически, когда ядро пишет адрес нового глобального каталога страниц в управляющий регистр. Однако ядро успешно избегает сброса TLB-буферов в следующих случаях:
— при переключении между обычными процессами, использующими один набор таблиц страниц;
— при переключении между обычным процессом и потоком ядра. Потоки ядра не имеют собственного набора таблиц страниц. Вместо этого они пользуются таблицами страниц, принадлежащими обычному процессу, который был последним запланирован на выполнение.
Чтобы избежать ненужного сбрасывания TLB-буферов в многопроцессорных системах, ядро прибегает к приему, называемому «ленивым» режимом TLB. В его основе лежит следующая идея: если несколько процессоров используют одни и те же таблицы страниц, и некая запись в TLB-буфере должна быть сброшена у всех процессоров, то сброс в некоторых ситуациях может быть отложен для процессоров, выполняющих потоки ядра.
TLB-буфера, которая ссылается на линейный адрес режима пользователя, потому что никакой поток ядра не обращается к адресному пространству режима пользователя.
Когда какой-нибудь процессор начинает выполнять поток ядра, ядро переводит его в «ленивый» режим TLB. Когда поступают запросы на очистку некоторых записей TLB-буфера, процессор в «ленивом» режиме TLB не сбрасывает эти записи, а запоминает, что его текущий процесс использует набор таблиц страниц, у которых записи TLB-буфера для адресов режима пользователя некорректны. Как только процессор в «ленивом» режиме TLB переключается на обычный процесс с другим набором таблиц страниц, аппаратная часть автоматически сбрасывает записи TLB-буфера, а ядро выводит процессор из «ленивого» режима TLB. Если же процессор в «ленивом» режиме TLB переключается на обычный процесс с тем же набором таблиц страниц, что у потока ядра, работавшего до него, то отложенный сброс TLB-буфера должен быть выполнен ядром. Такой «запоздалый» сброс достигается сбросом всех неглобальных записей TLB-буфера данного процессора.
Для реализации «ленивого» режима TLB требуются дополнительные структуры данных. Переменная cpu tibstate представляет собой статический массив из nr cpus структур (значением этого макроса по умолчанию является 32; он определяет максимальное количество процессоров в системе), состоящих из поля activejnm, указывающего на дескриптор памяти текущего процесса, и из флага state, принимающего значение tlbstate ok («неленивый» режим TLB) или tlbstate lazy («ленивый» режим TLB). Кроме того, каждый дескриптор памяти включает в себя поле cpu vm mask, хранящее индексы процессоров, которым следует принять межпроцессорные прерывания, относящиеся к сбросу TLB-буферов.
Когда процессор начинает выполнять поток ядра, ядро записывает в поле state его элемента cpu_tibstate значение tlbstate_lazy. Кроме того, поле cpu vm mask активного дескриптора памяти хранит индексы всех процессоров в системе, включая индекс того, который перешел в «ленивый» режим TLB. Если другой процессор захочет объявить недействительными записи TLB-буферов у всех процессоров, работающих с данным набором таблиц страниц, он доставит межпроцессорное прерывание всем процессорам, индексы которых содержатся в поле cpu vm mask соответствующего дескриптора памяти.
Когда процессор получает межпроцессорное прерывание, имеющее отношение к сбросу TLB-буфера, и убеждается, что оно затрагивает набор таблиц страниц его текущего процесса, он проверяет поле state элемента cpu tibstate на равенство значению tlbstate lazy. В этом случае ядро отказывается сбросить записи TLB-буфера и удаляет индекс процессора из поля cpu vm mask дескриптора процесса. Последствия этих действий таковы:
— пока процессор остается в «ленивом» режиме TLB, он не получает другие межпроцессорные прерывания, относящиеся к сбросу TLB-буфера;
— если процессор переключается на другой процесс, который пользуется тем же набором таблиц страниц, что и замещаемый поток ядра, то ядро вызывает функцию fiush tibo, чтобы сбросить неглобальные записи TLB-буфера этого процессора.