Виртуальная память
Виртуа́льная па́мять (англ. virtual memory) — это техника управления памятью, при которой каждой выполняющейся программе предоставляется собственное логическое адресное пространство, а отображение этого пространства на физическую оперативную память и на вторичное хранилище производится совместно аппаратурой процессора и операционной системой[1][2]. Прикладная программа обращается к данным по виртуальным адресам, не зная ни их физического размещения, ни того, какие участки её собственных данных в данный момент действительно находятся в ОЗУ; благодаря этому возможна одновременная работа множества процессов и адресация объёмов, превосходящих установленную оперативную память[3].
Общие сведения
| Виртуальная память | |
|---|---|
| англ. virtual memory | |
| Область использования | Операционные системы, архитектура ЭВМ, системное программирование |
| Дата появления | 1956 (теоретическая концепция); декабрь 1962 (первая промышленная реализация — Atlas) |
| Место появления | Берлинский технический университет; Манчестерский университет |
| Автор понятия | Фриц-Рудольф Гюнтш |
Определение и назначение
В современной литературе виртуальная память определяется как абстрактный уровень между логической моделью памяти, видимой программе, и реальным размещением данных в физических устройствах хранения[1]. Перевод адресов выполняет блок управления памятью (memory management unit, MMU), а политика размещения, замещения и защиты страниц определяется операционной системой[2][3].
Применение виртуальной памяти решает несколько связанных задач:
- изоляция процессов — каждое приложение получает собственное непересекающееся адресное пространство, что предотвращает случайное или преднамеренное вмешательство в чужие данные и обеспечивает основу защиты памяти[1][4];
- облегчение программирования — автоматическое перемещение данных между ОЗУ и диском избавляет разработчика от ручной разбивки программы на блоки, использовавшейся в технике оверлеев[5];
- эффективное использование и защита памяти — физическая память распределяется по требованию, неактивные страницы вытесняются на вторичное хранилище, а права доступа (чтение, запись, исполнение) проверяются аппаратно для каждой страницы[3][6].
Общие принципы работы
Адрес, который программа записывает в регистр или указатель, называется виртуальным; ячейка ОЗУ, к которой в итоге происходит обращение, идентифицируется физическим адресом. Преобразование одного в другое осуществляет MMU, рассматривая виртуальный адрес как составной: старшие биты задают номер страницы (или сегмента), младшие — смещение внутри неё[3][7]. Для каждого процесса операционная система ведёт собственное отображение, поэтому одинаковые виртуальные адреса разных процессов могут указывать на разные физические страницы либо, наоборот, разделять общую страницу[4].
Роль операционной системы
ОС создаёт и обновляет таблицы страниц, распределяет физические фреймы между процессами, обрабатывает отказы страниц (page fault), реализует подкачку страниц и поддерживает права доступа[8][6]. При обращении к отсутствующей в ОЗУ странице аппаратура передаёт управление обработчику исключения; ядро выбирает кандидата на вытеснение в соответствии с алгоритмом замещения (LRU, второго шанса, рабочего множества) и загружает требуемую страницу из файла подкачки или из исполняемого образа[9].
Аппаратная поддержка
В подавляющем большинстве современных универсальных процессоров MMU и кэш переводов адресов — буфер ассоциативной трансляции (TLB) — интегрированы непосредственно в ядро процессора[10][7]. В то же время на микроконтроллерах и в значительной части жёстких систем реального времени виртуальная память либо отсутствует, либо применяется в усечённом виде: важнее предсказуемость задержек, чем удобство абстракции[11].
Влияние виртуальной памяти на производительность определяется в первую очередь стоимостью двух событий. Промах TLB требует обхода многоуровневой таблицы страниц и обычно стоит десятки тактов процессора; страничный отказ с обращением к диску — на несколько порядков дороже и может занимать миллионы тактов, поскольку включает физический ввод-вывод[12][13].
История
В 1940-е и начале 1950-х годов программисту, столкнувшемуся с нехваткой основной памяти, приходилось вручную разбивать программу на сегменты и организовывать их поочерёдную загрузку — технику, известную как оверлеи. Подбор оптимальной схемы оверлеев требовал глубокого знания машины и был хорошо известен как источник трудно воспроизводимых ошибок[5].
Первые концепции виртуальной памяти
Первое известное формализованное описание виртуальной памяти содержится в докторской диссертации немецкого физика Фрица-Рудольфа Гюнтша, представленной в Берлинский технический университет в мае 1956 года. Гюнтш описал машину с шестью блоками памяти на магнитных сердечниках по 100 слов и логическим адресным пространством в 1000 блоков; аппаратура должна была автоматически переносить блоки между основной памятью и магнитными барабанами, так чтобы программисту это движение оставалось невидимым[14].
Практические реализации
Первая работающая система со страничной виртуальной памятью была построена в Манчестерском университете в рамках проекта Atlas. Архитектура, названная авторами «одноуровневым хранилищем» (англ. one-level store), объединяла 16 384 слова основной памяти на сердечниках с 98 304 словами барабанной памяти; работающие прототипы появились в 1959 году, а официальная сдача первой машины состоялась в декабре 1962 года[15][16]. В 1961 году Burroughs Corporation выпустила B5000 — первый коммерческий компьютер с виртуальной памятью, в котором вместо страниц использовались сегменты переменного размера, описываемые аппаратными дескрипторами[17]. В 1965 году собственная реализация виртуальной памяти появилась в советской машине БЭСМ-6, однако 15-разрядное физическое адресное пространство процесса в ней не расширялось, что сдерживало развитие прикладного программирования[18].
Споры о том, способно ли автоматическое управление памятью конкурировать с тщательно настроенными оверлеями, были закрыты в 1969 году экспериментами группы Дэвида Сейра в IBM: измерения показали, что виртуальная память не уступает ручному управлению практически по всем интересным метрикам, а зачастую и превосходит его[19]. На рубеже 1970-х виртуальная память была положена в основу системы разделения времени IBM VM/370[20].
Распространение в мини-компьютерах и архитектуре x86
Первым мини-компьютером, в котором виртуальная память была доступна как коммерческая опция, считается Nord-1 норвежской компании Norsk Data (модуль виртуальной памяти предложен к 1969 году)[21]. В 1978 году DEC представила 32-разрядный суперминикомпьютер VAX-11/780 и операционную систему VAX/VMS с развитой страничной системой виртуальной памяти и алгоритмом замещения, опирающимся на рабочее множество[22].
В семействе x86 аппаратная виртуальная память впервые появилась в 80286 (1982) с введением защищённого режима; механизм опирался исключительно на сегментацию и плохо масштабировался для крупных сегментов[23]. Процессор 80386 (1985) добавил поверх сегментации страничный механизм с 4-килобайтной страницей и двухуровневой таблицей страниц; со временем именно страничный механизм стал основным средством защиты, а сегментация в 64-разрядном режиме перестала использоваться по своему первоначальному назначению[10][23].
Реализация виртуальной памяти
В подавляющем большинстве современных ОС виртуальная память организуется страницами фиксированного размера — обычно 4 КБ, с возможностью использовать большие страницы (2 МБ, 1 ГБ) для приложений с интенсивным трафиком к памяти[3][10]. Фиксированный размер страницы упрощает аппаратную реализацию преобразования адресов и исключает внешнюю фрагментацию, однако порождает внутреннюю фрагментацию: выделение даже одного байта на практике приводит к занятию целой страницы[3].
Схема преобразования адреса в общем виде такова: виртуальный адрес разбивается на номер виртуальной страницы (VPN) и смещение; VPN служит ключом к таблице страниц, которая возвращает номер физического фрейма (PFN); конкатенация PFN и смещения даёт физический адрес[3]. Чтобы не обращаться к таблице страниц при каждом доступе к памяти, процессор кэширует последние переводы в TLB; при промахе выполняется обход таблицы страниц (page walk), реализуемый либо аппаратно (как в x86), либо программно через обработчик ОС (как, например, в классических MIPS)[24][13]. Если требуемой страницы нет в ОЗУ, MMU генерирует исключение page fault, и ядро выполняет подкачку[9].
Если рабочее множество процессов в сумме превышает доступное ОЗУ, система может попасть в режим пробуксовки (англ. thrashing): практически всё время процессора уходит на обработку страничных отказов и собственно полезная работа не выполняется. Классическим средством борьбы с пробуксовкой служит модель рабочего множества, предложенная Питером Деннингом и используемая до сих пор в виде различных её вариаций[25][26].
Сегментная и странично-сегментная организация
При сегментной организации виртуальное пространство делится на сегменты переменного размера, отражающие логическую структуру программы — отдельный сегмент для кода, для стека, для динамических данных и т. д. Виртуальный адрес имеет вид пары ⟨g, s⟩, где g — номер сегмента, s — смещение внутри него; физический адрес получается сложением базы сегмента, прочитанной из таблицы сегментов, со смещением[27]. Для каждого сегмента независимо задаются права доступа, что естественным образом согласуется с логикой языка программирования, однако переменный размер сегментов приводит к внешней фрагментации физической памяти[27]. Страничная схема, напротив, устраняет внешнюю фрагментацию ценой внутренней.
На современных процессорах x86-64 сегментация в качестве механизма защиты практически не используется: в 64-разрядном режиме большинство сегментных регистров имеют нулевую базу и неограниченный предел, а защита и трансляция полностью лежат на страничном механизме[10][23]. В Microsoft Windows управление памятью построено не на сегментах, а на так называемых объектах-секциях (section objects) и страницах; секции описывают области, которые могут быть отображены в адресные пространства одного или нескольких процессов[28].
Существуют и странично-сегментные схемы, сочетающие логическое деление программы на сегменты с физической постраничной подкачкой внутри каждого сегмента. Такая модель была реализована в системе Multics на компьютере GE 645 и оказала большое влияние на проектирование последующих операционных систем, в том числе семейства Unix[29].
Виртуальная память в современных ОС
В настольных и серверных ОС виртуальная память — основной механизм управления памятью. В Linux каждый процесс получает независимое адресное пространство, страницы могут отображаться на файлы через системный вызов mmap(2), а вытесненные страницы записываются в раздел или файл подкачки; разделяемая память реализована через общие отображения и интерфейсы POSIX shared memory[30][8]. Microsoft Windows использует файл подкачки pagefile.sys и аналогичный механизм отображения файлов через секции[31][4]. В macOS на ядре XNU с версии 10.9 (Mavericks) применяется сжатие памяти (англ. memory compression): редко используемые страницы сначала сжимаются в ОЗУ и лишь при сильной нехватке памяти отправляются в swap-файлы[32][33].
Мобильные операционные системы существенно ограничивают подкачку на постоянное хранилище ради энергопотребления и износа флеш-памяти. В iOS страничный своп на диск не используется, а нехватка памяти обрабатывается подсистемой Jetsam, принудительно завершающей процессы при превышении пороговых значений нагрузки[32][34]. В Android аналогичную функцию выполняет демон lmkd (Low Memory Killer Daemon): он отслеживает давление на память и завершает наименее приоритетные процессы; одновременно ядро поддерживает сжатие памяти в ZRAM[35][36].
В микроконтроллерах и жёстких системах реального времени виртуальная память применяется ограниченно или не применяется вовсе: непредсказуемая задержка обработки страничного отказа несовместима с гарантированными сроками отклика, а сам механизм MMU увеличивает стоимость и энергопотребление кристалла[11].
Преимущества и ограничения
К преимуществам виртуальной памяти относят: упрощение программирования за счёт прозрачной подкачки данных; аппаратно-программную защиту памяти и изоляцию процессов; возможность одновременно выполнять программы, чьи суммарные потребности в памяти превышают физический объём ОЗУ; использование оперативной памяти как кэша для вторичного хранилища, в том числе при отображении файлов в память через mmap[1][30][6].
К ограничениям относят: накладные расходы на трансляцию адресов и обслуживание TLB; значительные задержки и риск пробуксовки при недостатке физической памяти; усложнение аппаратуры (MMU, многоуровневые таблицы страниц, IOMMU); плохую совместимость с требованиями жёсткого реального времени из-за непредсказуемости задержек, вызванных страничными отказами[13][25][11].
Галерея
Примечания
Литература
- Arpaci-Dusseau R. H., Arpaci-Dusseau A. C. Operating Systems: Three Easy Pieces — University of Wisconsin–Madison (свободно доступная онлайн-книга).
- Gorman M. Understanding the Linux Virtual Memory Manager — The Linux Kernel Archives.
- Denning P. J. Before Memory Was Virtual — Denning Institute, 1997.
- Denning P. J. Virtual memory // ACM Computing Surveys. — 1970. — Vol. 2, No. 3.
- Levy H. M., Lipman P. H. Virtual Memory Management in the VAX/VMS Operating System // IEEE Computer. — 1982.
- Bensoussan A., Clingen C. T., Daley R. C. The Multics Virtual Memory: Concepts and Design // CACM. — 1972.
Ссылки
- Milestones:Atlas Computer and the Invention of Virtual Memory, 1957–1962 — Engineering and Technology History Wiki (IEEE) (англ.)
- The Atlas Milestone — Communications of the ACM (англ.)
- Concepts overview — официальная документация ядра Linux (англ.)
- mmap(2) — Linux manual page (англ.)
- Virtual Address Spaces — Microsoft Learn (англ.)
- About the Virtual Memory System — Apple Developer (англ.)
- Low memory killer daemon — Android Open Source Project (англ.)