РАЗБОР
Статья
Кэш-память процессора - устройство и оптимизация
[ обложка статьи ]
Режим чтения
Быстродействие компьютера зависит в первую очередь от кол-ва ядер CPU, и ёмкости его кэш-памяти. Два этих показателя играют ключевую роль, причём если о процессоре говорят сейчас чуть-ли не из каждого утюга, то кэш не заслуженно остаётся в тени. В данном обзоре мы восстановим справедливость, и рассмотрим подсистему кэш-памяти со всех её ракурсов, начиная от процессора и заканчивая софт-составляющей.
Оглавление:
1. Аппаратная часть
• многоуровневая архитектура L1,2,3 и когерентность
• пишем софт для сбора информации о кэшах
• организация кэшей - Direct Mapped, Full associative, Set associative
2. Программная модель
• режимы - WriteBack, WriteThrough, Uncacheable
• регистры FIXED и MTRR
• записи PTE и таблица РАТ
3. Заключение
1. Аппаратная часть
Географически кэш-память находится в корпусе Package ЦП, и аппаратно управляется встроенным кэш-контроллером. На него возложена весьма ответственная задача по интеллектуальной загрузке и освобождении линеек, которые копируются по требованию "On Demand" из внешней ОЗУ. На всех современных системах размер одной линейки кэша жёстко зафиксирован значением 64-байта, поскольку испокон веков шина памяти DDR 64-битная = 8 байт за раз, плюс повторов "Burst Length" тоже BL=8. Таким образом, мин.порция чтения/записи данных из ОЗУ равна строго 64-байта, которые занимают одну линейку кэша.
1.1. Многоуровневая архитектура L1,2,3 и когерентность
Известно, что процессоры имеют несколько уровней кэша - отдельно L1 инструкций + L1 данных, индивидуальный для каждого ядра кэш L2, и общий для всех ядер L3, который называют ещё LLC от "Low Level Cache". Если в синхронной динамической памяти DDR-SDRAM для хранения 1-бита используется конденсатор, то в статической кэш-памяти SRAM 1 ячейка собирается уже из 6-10 транзисторов, которые образуют триггер. Поэтому кэш во много раз дороже SDRAM. В общую иерархию памяти входят так-же ОЗУ и жёсткие диски, а сводная таблица всего семейства выглядит так:
Как видим, чем ниже уровень, тем больше размер, но такая цепочка порождает проблему согласованности данных, что на англ.манер звучит как "когерентность". В качестве примера можно привести ситуацию, когда 2 и более субъекта обращаются к одному участку ОЗУ. Если первый изменил память (что-нибудь записал в неё), то эти изменения должны тут-же покинуть кэш и отобразиться в общей ОЗУ, иначе остальные участники прочитают уже невалидное содержимое, что может привести к ошибкам. В таких случаях приходится выбирать или быстродействие, или поддержку когерентности, т.к. записывать каждый чих в медленную память довольно накладно.
В контексте кэша фигурирует так-же и понятие "Inclusive & Exclusive Cache":
1. Инклюзивный - это политика, при которой данные в кэше верхнего уровня L1 обязательно дублируются и в кэшах нижних уровней L2, L3. Если данные найдены в L1, они берутся оттуда, иначе промах и возможно их можно найти в инклюзивном L2, от куда именно копируются в L1, а не вырезаются. Аналогично если из L2 вытесняются данные, ЦП отправляет сигнал обратной Back-инвалидации в L1, чтобы удалить эту строку и в нём.
Из преимуществ инклюзивного кэша можно выделить упрощение когерентности в многоядерных системах. Когда одно ядро меняет данные, соседние ядра тоже должны обновить свои кэши. Если общий L3 инклюзивный, контроллеру достаточно проверить только его. Если нужной информации в L3 нет, значит её гарантированно нет и в приватных L1,2 всех остальных ядер. Это избавляет от необходимости опрашивать кэши каждого ядра в отдельности.
А из недостатков - нерациональное использование объёма, т.к. часть ёмкости нижнего кэша тратится на дублирование данных из верхнего уровня. К примеру, пусть L1 имеет размер 32 КБ, а L2 = 256 КБ, тогда полезная суммарная ёмкость связки будет 256, а не 288, т.к. 32 КБ в L2 хранят точную ксерокопию из L1.
2. Эксклюзивный кэш - это когда данные могут находиться либо в L1, либо в L2, но никогда в обоих сразу. Такой кэш всегда содержит самую свежую порцию данных. При вытеснении из L1, линейки именно перемещаются в L2, а не копируются - это позволяет использовать суммарный объём обоих кэшей, но требует доп.операций по транспорту данных между уровнями, что увеличивает задержку "Latency".
1.2. Инструкция CPUID - информация о кэшах
Чтобы далее не щупать вслепую коня в вакууме, напишем небольшой софт, который через инструкцию
cpuid соберёт информацию о свойствах кэш-памяти ЦП. Значит вендора и название модели возвращают три последовательных листа eax=0x80000002,3,4. В свою очередь топологию ядер и наличия гипертрейдинга отдаёт eax=0x0B с аргументами ecx=0,1, разрядность физ/вирт адресов eax=0x80000008, а непосредственно характеристики кэшей eax=0x04 с аргументами ecx=0,1,2,3 (аналог для AMD 0x8000001D).Оформив выхлоп всех этих листов в форточку получим такую картину где видно, что у разных процессоров отличается не только структура кэшей, но и кол-во выделенных для физ.адреса линий. Так, при 36-битном адресе ЦП может поддерживать плашки ОЗУ до 2^36=64 ГБ (если унесёт контроллер на мат.плате), а при 39-битном 2^39=512 ГБ. Шина у серверных ЦП типа Xeon-6 может быть вообще 44,48,52-битная, что позволяет адресовать уже исполинские 2^44=16 ТБ и более. При этом виртуальный адрес для х64 обычно 48-битный, а это макс 2^48=256 ТБ (по 128 ядру ОС и пользователю)., хотя для 5-уровневого пейджинга он расширяется до 57-бит. От разрядности физ.адреса зависят и размеры тегов кэш-памяти, о которых пойдёт речь далее.
Обратите внимание, что инклюзивным, как правило, является только последний кэш L3 для поддержки когерентности между ядрами. А вообще эту информацию возвращает лист
CPUID.EAX=4, и в спеке указывается только флаг "Inclusive" да/нет, а про "Exclusive" ни слова, хотя он подразумевается неявно. Поэтому мне пришлось допросить нейросеть по этому поводу, и как оказалось современные процики используют гибридный вариант NINE, что означает Non-Inclusive_Non-Exclusive. То-есть если не указан флаг Inclusive(1), в зависимости от обстоятельств ЦП сам выбирает наиболее подходящий режим.1.3. Организация кэшей: Direct-Mapped, Fullу-associative, Set-associative
Эти термины описывают структуру кэш-памяти, т.е. то, как именно данные из основной памяти DRAM распределяются по 64-байтным линейкам кэша:
1. Direct-Mapped, или прямое отображение. Каждому адресу в DRAM соответствует строго одна конкретная строка в кэше. Поскольку размер кэша намного меньше ОЗУ, то адрес вычисляется по модулю общего кол-ва линеек. Так, если ёмкость кэша всего 4 линейки (как на схеме ниже), то в нулевую будут сбрасываться строки 0,4,8,12 из ОЗУ, а в линейку(2) соответственно строки памяти 2,6,10,14. При такой схеме ЦП всегда знает, где именно в кэше находятся данные, но когда наступит момент загрузки новой порции, контроллеру придётся выгрузить конкретно взятую линейку, даже если она и хранит актуальную информацию.
2. Full Associative, или полностью ассоциативный кэш. Любой блок из DRAM может быть записан в абсолютно любую строку кэша. Самый простой в реализации, и в то-же время неэффективный вариант, т.к. при поиске данных процессору нужно будет перебирать буквально все кэш-линейки. Зато проблемы Direct-кэша по выгрузке полезных данных здесь уже нет - в качестве жертвы можно выбирать любую Cache-Line.
3. Set-Associative, или множественно-ассоциативный. Компромиссный вариант. Кэш делится на несколько наборов "Sets", и строка из DRAM привязывается к конкретному набору, однако внутри него может занять любую линейку. По сути это тот-же Direct-Mapped, только всё пространство кэша разделено на несколько отдельно адресуемых блоков Set. Кол-во кэш-линеек в одном наборе определяет показатель "Way" (путь, см.утилиту выше). Именно эту архитектуру используют сейчас все современные процессоры.
Чтобы дать определение пути Way, представим отель. Тогда каждый номер в отеле это "Set", и в каждом номере по несколько кроватей "Way". Когда администратор отеля (кэш-контроллер) захочет заселить очередного клиента, он выделяет ему номер (пусть будет последний 10), и внутри этого номера уже одну из 4-х кроватей. Как результат получим организацию кэша "10 sets + 4 ways". Если-бы в каждом номере была только одна кровать, то при появлении новых клиентов в том-же номере, админу пришлось-бы выселять старых гостей (привет Direct-Mapped). Чтобы двое не боролись часто за одну кровать, номера в отеле и делают многоместными N-Way. При такой схеме, ёмкость вычисляется по формуле:
CacheSize = Sets * Way * 64, где 64 - это размер одной линейки кэша.Конечно в реальной ситуации наборов Set гораздо больше. Например на скринах утилиты выше L3 имеет:
Sets=8192, и в каждом Ways=12. В конструкции контроллера имеются N-компараторов работающих параллельно, благодаря которым на поиск линейки уходит всего пару тактов ЦП. Реализуется такая скорость за счёт разделения физ.адреса ОЗУ на три части:1. Младшие 6-бит "Offset" всегда стабильны, и определяют смещение нужного байта внутри 64-байтной строки.
2. По средним битам "Index", контроллер вычисляет порядковый номер набора Set, поэтому его разрядность напрямую зависит от общего числа Sets. Например 9-битный индекс способен адресовать 2^9=512 наборов, а 13-битный 2^13=8192.
3. Оставшиеся биты физ.адреса представляют собой "TAG" - именно здесь вступает в игру разрядность физ.адреса, который возвращает инструкция
cpuid. У каждой 64-байтной линейки свой тег, и для них в конструкции предусмотрена специальная память, что даёт возможность сопоставлять хранящиеся в кэш данные, с данными из ОЗУ. Когда по индексу найден набор Set, теги адресуют уже конкретную строку внутри набора. Таким образом, чем больше ёмкость кэша, тем меньше для неё требуется теговой памяти, и наоборот.Загрузка кэша данными реализуется на основе "стратегии кэширования", а выгрузка данных на основе "политики замещения". Кэш всегда забит под завязку и новые данные попадают в него только путём замещения старых. Самый простой - это алгоритм произвольного выбора "Random". Понятно, что это неэффективно, а потому сейчас практикуется алго на основе частотного анализа обращений к данным LRU (Least Recently Used), когда замещаются те данные, к которым дольше всего не обращались. Этот алго требует наличия счётчиков в каждой строке кэша, который обновляется при попадании Cache-Hit.
Помимо выделенного чёрным на рис.выше счётчика (обычно несколько бит на набор), в число служебных входят так-же бит валидности и бит модифицированных данных. Обратно в память ОЗУ сбрасываются только те кэш-линейки, у которых взведёны оба бита(V:М). Когда процессор ищет свободные слоты в кэш, он просто проверяет биты(V) у всех строк - если он взведён в единицу, значит строка используется, иначе её можно перезаписать новыми данными.
Когда мы запускаем какой-нибудь прикладной софт, префетчеры в ЦП подтягивают его код и данные в соответствующие кеши L1 Inst/Data. При этом у всех загруженных линеек автоматически взводятся биты(V). Но когда мы закрываем софт, контроллеру нужно освободить все отработанные линейки, чтобы они не занимали понапрасну кэш, для чего достаточно просто сбросить в нуль их биты Valid. Аналогичным образом удаляются и файлы с диска NTFS, что позволяет восстанавливать их обратно всего одним флагом
0xE5 "Delete Mask".2. Программная модель
Пробежавшись по макушкам аппаратной реализации, теперь рассмотрим софт-составляющую. Истина гласит, что без программного обеспечения компьютеры глупые, и абсолютно стерильные. В контекстсте кэша это особо актуально, т.к. поведение контроллера во многом определяют доступные нам на чтение/запись модельно специфичные регистры MSR. В эти регистры биос загоняет дефолтные значения, но как только управление получит ОС, она может подправить их под себя.
2.1. Режимы работы - WriteBack, WriteThrough, Uncacheable
Кэш-контроллер рассчитан на работу в нескольких режимах, что в конечном итоге задаёт механизмы кэширования памяти. Термины ниже определяют алгоритм поведения ЦП при чтении и, главное при записи модифицированных данных из кэша обратно в ОЗУ:
1. Write-Back (WB) - операции r/w кэшируются, а запись выполняется исключительно в кэш, и сбрасывается в ОЗУ позднее. Сброс инициируется, когда необходимо освободить линейки при полностью забитом кэше, или когда кто-то обратился по адресу линейки к ОЗУ (поддержка когерентности). Этот тип управления кэшем обеспечивает наивысшую производительность, т.к. снижает нагрузку на шину, за счёт исключения избыточных записей в память.
2. Write-Through (WT) - операции r/w кэшируются, а запись проходит по всем кэшам более низких уровней L2,3, и одновременно в память ОЗУ. Это обеспечивает согласованность (когерентность) данных. Допускается спекулятивное чтение и объединение операций записи (write combining). Устаревший режим, и на практике почти не используется.
3. Write Combining (WC) - операции r/w не кэшируются, однако допускается спекулятивное чтение, когда ЦП сам предугадывает нужные данные. Модифицированные линейки копятся в специальном WC-буфере, и сливаются в память пачками минуя кэш. Этот тип управления кэшем идеально подходит для кадровых буферов, где порядок записи не имеет значения.
4. Write-Protected (WP) - при промахе кэш заполняется из ОЗУ, однако запись осуществляется сразу в память, и приводит к аннулированию (т.е. сбросу бита V) соответствующих строк кэша во всех ядрах ЦП. Этот режим доступен только через регистры MTRR.
5. Uncacheable (UC) - кэш полностью игнорируется! Любое чтение и запись уходят напрямую из/в физ.память в программном порядке. Режим полезен для доступа к памяти MMIO (memory-mapped i/o). При использовании с обычной ОЗУ он существенно снижает производительность, т.к. подразумевает чтение и запись в память буквально каждого чиха.
6. Uncached (UC-) - как и предыдущий за исключением того, что здесь доступно спекулятивное чтение. Этот режим может быть выбран только через таблицу PAT.
Представьте, что ЦП выполняет код и видит, что скоро понадобятся данные по адресу X. Процессор может прочитать их по собственной инициативе без явного запроса со стороны программы - это и называется спекулятивным чтением. Проблема в том, что для обычной ОЗУ это безопасно - чтение ничего не меняет. Но для пространства MMIO (регистры устройств) чтение может иметь побочный эффект, например сбросить флаг статуса прерываний, что автоматически запустит процедуру обработки ISR (Interrupt Service Routine). Поэтому для доступа к MMIO используется исключительно чистый режим UC, который запрещает спекуляцию.
2.2. Регистры MTRR-Fixed, и MTRR-Variable
MTRR подразумевает "Memory Type Range Registers", т.е. регистры типа регионов памяти, а потому они задают атрибуты крупным областям, о чём прямым текстом сообщает "Range". К сожалению из прикладного уровня у нас нет доступа к регистрам MSR, зато прочитать их может утилита RWE, которую я и буду использовать далее в примерах.
Определение итогового механизма кэширования происходит на основе строгой иерархии приоритетов. Диапазоны памяти ОЗУ и соответствующие им типы памяти определяются тремя группами MSR:
1. MTRRCAP(0xFE) - указывает на наличие регистров MTRR в процессоре. Самый младший байт является счётчиком парных регистров "Variable", и у меня их 10 штук
0x0A. Маска во втором байте имеет значение 01101b, и это поддержка регистров процессором - здесь полный набор, кроме анклавов Intel SGX.2. MTRR_DEF_TYPE(0x2FF) - задает свойства кэшей по умолчанию для областей физ.памяти, не охваченных остальными регистрами MTRR. Так-же в этом регистре указывается, активны или нет регистры Fix и Variable, хотя в предыдущем CAP было только их наличие. Как видим оба бита взведены, а значит всё ок. В таблице я сразу перечислил константы для типов кэшей, где 2 и 3 отправлены в резерв и не используются. В моём случае кэшем по умолчанию является UC, о чём свидетельствует значение младшего байта.
3. MTRR_FIX4/16/64К - 11 регистров описывают диапазоны фиксированных адресов в пределах первого МБ физ.памяти. Если адрес лежит в пределах от
0x00000 до 0xFFFFF, регистры Fixed имеют самый высокий приоритет. Все регистры 8-байтные (64-бит), и каждый разделён на восемь 1-байтных полей, в которых кодируется тип кэша для 8-ми возможных (под)диапазонов. Типы кэшей определяют константы в таблице выше:• MTRR_FIX64K_00000 - охватывает блок памяти размером 512 КБ, от нуля до
0x7FFFF. Этот диапазон разделён на 8 (под)диапазонов по 64 КБ. На скрине ниже утилиты RWE видно, что все они кэшируются как Write-Back = 0x06.
• MTRR_FIX16K_80000 + FIX16K_A0000 - отображают 2 блока памяти по 128 КБ, от
0x80000 до 0xBFFFF. Диапазон разделён на 16 участков по 16 КБ, причём на моей машине тип кэша для второго 128 КБайтного блока, не определён, т.к. значение регистра MSR:0x259 равно нуль.
• MTRR_FIX4K_C0000...FIX4K_F8000 - отображают 8 блоков по 32 КБ, от
0хC0000 до 0хFFFFF. Разделён на 64 (под)диапазона по 4 КБ. В данном случае во-всех регистрах лежит 0х05, а этот код привязан к типу кэша Write-Protected.4. MTRR_PHYBASE - 10 пар регистров Base+Mask для произвольных "Variable" диапазонов выше первого МБ. Каждая пара определяет 1 диапазон так, что в 10-ти парах можно охватить всё физическое пространство ОЗУ. Здесь опять всплывает разрядность физ.адреса от листа
cpuid.eax=0x80000008. В документации она указывается как переменная MAXPHYADDR, а в моём случае это 36-бит (см.окно утилиты в начале темы).Адрес базы выравнивается на 4КБ фрейм физ.памяти, а потому младшие 12-бит игнорируются, и в них можно закодировать тип кэша, а для маски - бит валидности регистра. База блока указывается явно, а маска хранит инверсный размер блока в байтах. Таким образом, алго вычисления диапазона будет выглядеть так:
1. Прочитать регистр базы, запомнить 1-байтное поле с типом кэша, и сбросить его в нуль. Это будет база блока.
2. Прочитать регистр маски, проверить его на валидность, и сбросить бит(11) в нуль.
3. Инструкцией NOT проинверсировать все биты маски, после чего обрезать старшую часть по разрядности физ.адреса.
4. Прибавить к получившейся маске единицу - это и будет размером блока относительно базы.
Теперь попробуем получить регионы памяти и тип их кэшей из скрина утилиты RWE ниже. Напомню, что физ.адрес у меня 36-битный, т.к. это принципиально.
Код:
PhysBase0_Reg : 0x0000`0000`0000`0006 = тип кэша WB, база нуль
PhysMask0_Reg : 0x0000`000C`0000`0800 = Valid
Инверсия маски : 0xFFFF`FFF3`FFFF`FFFF +1
Оставить 36 бит: 0x0000`0004`0000`0000 = размер 16 ГБ, и это вся моя ОЗУ
PhysBase1_Reg : 0x0000`0004`0000`0006 = тип кэша WB, база 0x400000000
PhysMask1_Reg : 0x0000`000F`E000`0800 = Valid
Инверсия маски : 0xFFFF FFF0 1FFF FFFF +1
Оставить 36 бит: 0x0000`0000`2000`0000 = размер 512 МБ
PhysBase2_Reg : 0x0000`0000`DB80`0000 = тип кэша UC, база 0xDB800000
PhysMask2_Reg : 0x0000`000F`FF80`0800 = Valid
Инверсия маски : 0xFFFF`FFF0`007F`FFFF +1
Оставить 36 бит: 0x0000`0000`0080`0000 = размер 8 MБ
2.3. Записи PTE и таблица РАТ
MTRR и PAT - это два механизма, которые вместе определяют, как ЦП обращается к памяти (кэширует, объединяет записи, или обходит кэш). MTRR это глобальные настройки для всей системы, которые настраивает биос. Механизм работает с физ.адресами и имеет большую гранулярность, в то время как PAT более гибкий и оперирует на уровне мелких 4КБ страниц вирт.памяти. PAT подразумевает "Page Attribute Table".
На счёт таблицы, конечно, громко сказано, поскольку вся таблица - это один 64-битный регистр
IA32_PAT(0x277). Центром вселенной здесь являются 8 переменных PA7-PA0 (page attributes), в которые и записываются восемь 1-байтных значений из регистра PAT. В самом-же регистре биос указывает уже знакомые нам из предыдущей таблицы коды типов кэширования, к которым добавился ещё один UC- со значением 0x07 (без кэша, но спекулятивное чтение).Когда после телодвижений биоса регистр РАТ готов к использованию, позже ОС может изменить его. Так в доках сказано, что РАТ в дефолте равен
0x00070406`00070406, хотя у меня Win изменила уже переменную РА1 с 04 на 01, что означает было WT, а стало WC. Как результат, жонглируя битами РАТ, PCD и PWT в записях РТЕ, я не смогу теперь назначить сквозной тип кэша Write-Through ни для какой из вирт.страниц, как-бы мне этого не хотелось. Точно так-же напрочь отсутствует сейчас и тип WP=0x05.Важно понять, что если мы изменим регистр РАТ, то штатная комбинация битов 7-4-3 в записях РТЕ будет кодировать уже другой тип кэша, согласно значениям в переменных РА0-7 (см.столбец "код" выше).
Обратите внимание, что младшие 32-бита регистра РАТ дублируются в старшие. Это сделано специально для совместимости со старыми ЦП, у которых не было аппаратной поддержки атрибутов страниц. У них бит(7) отправлен в резерв, а потому двумя битами можно выбрать лишь 4 стандартных типа кэширования. Но когда бит(7) взведён, появляется возможность закодировать уже все 6 вариантов. Например регистры РАТ гипервизоров имеют значения
0x05040606`00070106, т.е. в наличии у них WP+WT.Теория хорошо, а что имеем в реале? Чекнуть состояние кэшей для страниц нашего приложения можно в отладчике WinDbg. Для этого запрашиваем адрес процесса в памяти ядра, далее переключаем на него контекст, и наконец выводим свойства записи РТЕ страницы кода или данных.
Код:
0: kd> !process 0 0 cpucache.exe <------- мой софт cpuid
PROCESS fffffa800c96d8a0
Session: 1 Cid: 0ea4 Peb: 7fffffdf000 ParentCid: 06d8
DirBase: 24aaec000 ObjectTable: fffff8a005c844e0 HandleCount: 69.
Image : CpuCache.EXE
0: kd> .process /P fffffa800c96d8a0 <------- переключаем на него контекст отладчика
Implicit process is now fffffa80`0c96d8a0
0: kd> !cmkd.ptelist -v 0x00402000 <------- запрашиваем страницу секции-кода
ptelist : VA=0000000000402000
PXE Idx=000 Va=FFFFF6FB7DBED000 Contents=02F0000186856867 Hard Pfn=00186856 Attr=---DA--UWEV
PPE Idx=000 Va=FFFFF6FB7DA00000 Contents=030000021F8D7867 Hard Pfn=0021F8D7 Attr=---DA--UWEV
PDE Idx=002 Va=FFFFF6FB40000010 Contents=03100003600D8867 Hard Pfn=003600D8 Attr=---DA--UWEV
PTE Idx=002 Va=FFFFF68000002010 Contents=4A70000276CCF105 Hard Pfn=00276CCF Attr=-G-----UREV
0: kd> dt _mmpte_hardware FFFFF68000002010 <------- свойства записи РТЕ
nt!_MMPTE_HARDWARE
+0x000 Valid : 0y1 Pos 0, 1 Bit
+0x000 Write : 0y0 Pos 1, 1 Bit
+0x000 Owner : 0y1 Pos 2, 1 Bit
+0x000 WriteThrough : 0y0 Pos 3, 1 Bit <------- PWT = 0
+0x000 CacheDisable : 0y0 Pos 4, 1 Bit <------- PCD = 0
+0x000 Accessed : 0y0 Pos 5, 1 Bit
+0x000 Dirty : 0y0 Pos 6, 1 Bit
+0x000 PageAttribTable : 0y0 Pos 7, 1 Bit <------- PAT = 0
+0x000 Global : 0y1 Pos 8, 1 Bit
+0x000 Unused : 0y000 Pos 9, 3 Bits
+0x000 PageFrameNumber : 0x276ccf Pos 12, 36 Bits
+0x000 Reserved : 0y0000 Pos 48, 4 Bits
+0x000 SoftwareWsIndex : 0x4a7 Pos 52, 11 Bits
+0x000 NoExecute : 0y0 Pos 63, 1 Bits
0: kd>
Угу.. значит все 3 бита сброшены, а согласно таблицы выше это переменная РА-0, с типом кэша 0x06 = WriteBack. Как уже упоминалось, этот способ кэширования является самым производительным, и для прикладного софта система задействует именно его.
3. Заключение
Такой вот получился длинный опус. Зато теперь мы знаем, что кэш в процессоре - это "вещь в себе", поскольку управляется он снаружи регистрами FIXED, MTRR и РАТ. Кэш-контроллер динамически меняет режим своей работы по текущему адресу, который глубоко в закромах несёт с собой и механизм кэширования.
Если адрес попадает в диапазон обоих регистров MTRR и РАТ, то MTRR перебивает РАТ сверху, т.к. он оперирует фреймами физической памяти, а не виртуальной как РАТ. Аналогично и с коллизией FIXED->MTRR, где выигрывает именно FIXED.
В скрепку положил исходник на ассемблере fasm своей утилиты CPUID, и готовый исполняемый файл для тестов. Кому не лень, просьба проверить его на своих проциках. Интересует реакция на наличие гипер-трейдинга НТ (поскольку на обоих моих машинах его нет), а так-же если вдруг у кого обнаружится кэш L4, и физ.адрес более 39-бит. Всем удачи, пока!
Скачать архив
Ссылки по теме:
Логическая организация кэш-памяти процессора
Cache Memory in Computer Organization - GeeksforGeeks
AI-выжимка
по разделам
Тезисы статьи скоро
Статья читается полностью. Тезисы со ссылками на разделы появятся позже.
Содержание
Codeby Academy
Практика и мастерство
От основ до продвинутого — программы для практиков Codeby.
Перейти к курсу →
Комментарии
0