MMU (Memory Management Unit)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 12:45, 9 ноября 2016.

Блок управления памятью или устройство управления памятью (англ. memory management unit, MMU) — компонент аппаратного обеспечения компьютера, отвечающий за управление доступом к памяти, запрашиваемым центральным процессором. Его функции заключаются в трансляции адресов виртуальной памяти в адреса физической памяти (то есть управление виртуальной памятью), защите памяти, управлении кеш-памятью, арбитражем шины и, в более простых компьютерных архитектурах (особенно 8-битных), переключением блоков памяти. Иногда также упоминается как блок управления страничной памятью (англ. Paged memory management unit, PMMU). В настоящее время, чаще всего, упоминается в связи с организацией т. н. виртуальной памяти и, следовательно, критически важен для многих современных многозадачных операционных систем, включая все современные Windows NT и многие из UNIX‐подобных. Специальная редакция ядра Linux, μClinux, может работать без MMU. Блок управления памятью в настоящее время очень часто включается в состав центрального процессора или чипсета компьютера.

Свойства MMU

Основной функцией MMU является аппаратная поддержка виртуальной памяти. Виртуальная память — это воображаемая память, выделяемая операционной системой для размещения пользовательской программы, ее рабочих полей и информационных массивов. У систем с виртуальной памятью четыре основных свойства:

  • Пользовательские процессы изолированы друг от друга и, умирая, не тянут за собой всю систему;
  • Пользовательские процессы изолированы от физической памяти, то есть знать не знают, сколько у вас на самом деле оперативки и по каким адресам она находится;
  • Операционная система гораздо сложнее, чем в системах без виртуальной памяти;
  • Никогда нельзя знать заранее, сколько времени займет выполнение следующей команды процессора.[1]

Принцип работы MMU

Схема работы MMU

Принцип работы современных MMU основан на разделении виртуального адресного пространства (одномерного массива адресов, используемых центральным процессором) на участки одинакового, как правило несколько килобайт, хотя, возможно, и существенно большего, размера равного степени 2, называемые страницами. Младшие n бит адреса (смещение внутри страницы) остаются неизменными. Старшие биты адреса представляют собой номер (виртуальной) страницы. MMU обычно преобразует номера виртуальных страниц в номера физических страниц используя буфер ассоциативной трансляции (англ. Translation Lookaside Buffer, TLB). Если преобразование при помощи TLB невозможно, включается более медленный механизм преобразования, основанный на специфическом аппаратном обеспечении или на программных системных структурах. Данные в этих структурах как правило, называются элементами таблицы страниц (англ. page table entries (PTE)), а сами структуры — таблицами страниц (англ. page table (PT)). Конкатенация номера физической страницы со смещением внутри страницы даёт физический адрес.Элементы PTE или TLB могут также содержать дополнительную информацию: бит признака записи в страницу (англ. dirty bit), время последнего доступа к странице (англ. accessed bit, для реализации алгоритма замещения страниц наиболее давно использованный (англ. least recently used, LRU), какие процессы (пользовательские (англ. user mode) или системные (англ. supervisor mode)) могут читать или записывать данные в страницу, необходимо ли кэшировать страницу.[2]

Устройство MMU

Процессор без поддержки виртуальной памяти

Все адреса, используемые в программе для такого процессора — реальные, «физические», т.е. программист, линкуя программу, должен знать, по каким адресам находится оперативная память. Если у вас припаяно 640 кБ оперативки, отображаемой в адреса 0x02300000-0x0239FFFF, то все адреса в вашей программе должны попадать в эту область.

Если программист хочет думать, что у него всегда четыре гигабайта памяти (разумеется, речь идет о 32-битном процессоре), а его программа — единственное, что отвлекает процессор ото сна, то нам потребуется виртуальная память. Чтобы добавить поддержку виртуальной памяти, достаточно между процессором и оперативной памятью разместить MMU, которое будет транслировать виртуальные адреса (адреса, используемые в программе) в физические (адреса, попадающие на вход микросхем памяти.

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

Процесс

  • Процессор подает на вход MMU виртуальный адрес
  • Если MMU выключено или если виртуальный адрес попал в нетранслируемую область, то физический адрес просто приравнивается к виртуальному
  • Если MMU включено и виртуальный адрес попал в транслируемую область, производится трансляция адреса, то есть замена номера виртуальной страницы на номер соответствующей ей физической страницы (смещение внутри страницы одинаковое):
  • Если запись с нужным номером виртуальной страницы есть в TLB, то номер физической страницы берется из нее же
  • Если нужной записи в TLB нет, то приходится искать ее в таблицах страниц, которые операционная система размещает в нетранслируемой области ОЗУ (чтобы не было промаха TLB при обработке предыдущего промаха). Поиск может быть реализован как аппаратно, так и программно — через обработчик исключения, называемого страничной ошибкой (page fault). Найденная запись добавляется в TLB, после чего команда, вызвавшая промах TLB, выполняется снова. [3]
Процессы внутри MMU

Работа TLB

Tlb123.png

Рассмотрим работу TLB на простом примере. Допустим, у нас есть два процесса А и Б. Каждый из них существует в своем собственном адресном пространстве и ему доступны все адреса от нуля до 0xFFFFFFFF. Адресное пространство каждого процесса разбито на страницы по 256 байт (это число я взял с потолка — обычно размер страницы не меньше одного килобайта), т.е. адрес первой страницы каждого процесса равен нулю, второй — 0x100, третьей — 0x200 и так далее вплоть до последней страницы по адресу 0xFFFFFF00. Разумеется, процесс не обязательно должен занимать все доступное ему пространство. Процесс А занимает всего две страницы, а Процесс Б — три. Причем одна из страниц общая для обоих процессов.

Также есть 1536 байт физической памяти, разбитой на шесть страниц по 256 байт (размер страниц виртуальной и физической памяти всегда одинаков), причем память эта отображается в физическое адресное пространство процессора с адреса 0x40000000 (ну вот так ее припаяли к процессору).

первая страница Процесса А расположена в физической памяти с адреса 0x40000500. Не будем вдаваться в подробности того, как эта страница туда попадает — достаточно знать, что ее загружает операционная система. Она же добавляет запись в таблицу страниц (но не в TLB), связывая эту физическую страницу с соответствующей ей виртуальной, и передает управление процессу. Первая же команда, выполненная Процессом А, вызовет промах TLB, в результате обработки которого в TLB будет добавлена новая запись. Когда Процессу А потребуется доступ ко второй его странице, операционная система загрузит ее в какое-нибудь свободное место в физической памяти (пусть это будет 0x40000200). Случится еще один промах TLB, и нужная запись снова будет добавлена в TLB. Если места в TLB нет — будет перезаписана одна из более ранних записей.

После этого операционная система может приостановить Процесс А и запустить Процесс Б. Его первую страницу она загрузит по физическому адресу 0x40000000. Однако, в отличие от Процесса А, первая команда Процесса Б уже не вызовет промах TLB, так как запись для нулевого виртуального адреса в TLB уже есть. В результате Процесс Б начнет выполнять код Процесса А! Что интересно, именно так и работал широко известный в узких кругах процессор ARM9.

Самый простой способ решить эту проблему — инвалидировать TLB при переключении контекста, то есть отмечать все записи в TLB как недействительные. Это не самая хорошая идея, так как:

  • В TLB на этот момент занято всего две записи из восьми, то есть новая запись поместилась бы туда без проблем
  • Удаляя из TLB записи, принадлежащие другим процессам, мы вынуждаем эти процессы генерировать повторные страничные ошибки, когда операционная система снова запустит их. Если в TLB не восемь записей, а тысяча, то производительность системы может значительно упасть.

Способ посложнее — линковать все программы так, чтобы они использовали разные части виртуального адресного пространства процессора. Например, Процесс А может занимать младшую часть (0x0-0x7FFFFFFF), а Процесс Б — старшую (0x80000000-0xFFFFFFFF). Очевидно, что в этом случае ни о какой изоляции процессов друг от друга речи уже не идет, однако этот способ действительно иногда применяют во встроенных системах. Для систем общего назначения по понятным причинам он не подходит.

5bd8618b339b041a839070c281d624d3.png

Третий способ — это развитие второго. Вместо того, чтобы делить четыре гигабайта виртуального адресного пространства процессора между несколькими процессами, почему бы просто не увеличить его? Скажем, в 256 раз? А чтобы обеспечить изоляцию процессов, сделать так, чтобы каждому процессу по-прежнему было доступно ровно четыре гигабайта оперативной памяти? Сделать это очень просто. Виртуальный адрес расширили до 40 бит, при этом старшие восемь бит уникальны для каждого процесса и записаны в специальном регистре — идентификаторе процесса (PID). При переключении контекста операционная система перезаписывает PID новым значением (сам процесс свой PID поменять не может). Если для нашего Процесса А PID равен единице, а для Процесса Б — двойке, то одинаковые с точки зрения процессов виртуальные адреса, например 0x00000100, с точки зрения процессора оказываются разными — 0x0100000100 и 0x0200000100 соответственно. Очевидно, что в нашем TLB теперь должны находиться не 24-битные, а 32-битные номера виртуальных страниц. Для удобства восприятия старшие восемь бит хранятся в отдельном поле — идентификаторе адресного пространства (Address Space IDentifier — ASID).

Теперь, когда процессор подает на вход MMU виртуальный адрес, поиск в TLB производится по комбинации VPN и ASID, поэтому первая команда Процесса Б вызовет страничную ошибку даже без предшествующей инвалидации TLB.

В современных процессорах ASID чаще всего или восьмибитный, или 16-битный. Например, во всех процессорах ARM с MMU, начиная с ARM11, ASID восьмибитный, а в архитектуре ARMv8 добавлена поддержка 16-битного ASID.

Кстати, если процессор поддерживает виртуализацию, то помимо ASID у него может быть еще и VSID (Virtual address Space IDentificator), который еще больше расширяет виртуальное адресное пространство процессора и содержит номер запущенной на нем виртуальной машины.

Даже после добавления ASID могут возникнуть ситуации, когда нужно будет инвалидировать одну или несколько записей или даже весь TLB:

  1. Если физическая страница выгружена из оперативной памяти на диск — потому что обратно в память эта страница может быть загружена по совсем другому адресу, то есть виртуальный адрес не изменится, а физический изменится
  2. Если операционная система изменила PID процесса — потому что и ASID станет другим
  3. Если операционная система завершила процесс

Примечания

  1. Что такое MMU? [Электронный ресурс] : — Режим доступа : http://studopedia.ru/15_73378_chto-takoe-MMU.html
  2. Блок управления памятью [Электронный ресурс] : — Режим доступа: https://ru.wikipedia.org/wiki/Блок_управления_памятью
  3. MMU в картинках [Электронный ресурс] : — Режим доступа: https://habrahabr.ru/post/211150/