| Сергей Холодилов ( @ 2009-03-05 10:51:00 |
| Entry tags: | программинг |
Виртуальная память - 0
Скорее всего, все знают, как устроена виртуальная память в Intel x86 ...
Не. Это вряд ли.
Скорее всего, многие видели картинки, более-менее изображающие устройство виртуальной памяти в Intel x86. Как выясняется, есть и другие забавные и поучительные варианты того же самого.
Это должен был быть один большой пост. Но он получается слишком большой и я его уже пишу слишком долго. Поэтому пусть будет много маленьких.
[Ликбез] Виртуальная память: что это и зачем
Вдруг кто-то не знал или забыл.Итак: процессор, память, многозадачная операционная система, много задач (ака процессов). У каждого процесса есть какой-то исполняемый код, какие-то данные, чтобы всё это работало оно должно находиться в памяти. И тут возникает несколько проблем:
- Процессы не должны мешать друг другу. В частности один процесс не должен иметь возможность случайно поменять данные/код другого процесса, иначе из-за ошибок в одном процессе будут падать все остальные (если такая возможность нужна, пусть попросит явно, это отдельный разговор).
- Памяти мало. Всем сразу не хватает.
- Памяти мало. А она ещё и фрагментируется. Допустим, загрузили процесс в память, за ним загрузили следующий, потом первый выгрузили. Осталась дырка. Эту память можно использовать только если будет загружаться процесс, по размеру не превышающий первый.
- Вообще, "размер процесса" понятие очень условное. Память выделяется из кучи, памяти нужно то мало, то много, дать процессу один кусок навсегда -- очень не гибко
- Памяти мало. Часто разные процессы используют один и тот же код. Разделяемые библиотеки, или просто одна и та же программа выполняется. Хорошо бы что бы при этом в памяти была только одна копия кода.
Для всего этого придумали сегментную виртуальную память, но она не удобным образом решала не все проблемы. Поэтому ещё немного подумали и придумали страничную виртуальную память. Это было настолько давно, что даже старики уже помнят только последнюю. И когда говорят "виртуальная память" имеется ввиду именно она. Но я начну свой рассказ с начала.
Сегментная виртуальная память
Сегментная виртуальная память требует сегментной адресации.Простите, но что это такое я описывать не буду. Длинно, нужно рисовать картинки, не очень важно, да и по ходу разберётесь.
Т.е. процесс использует свои сегменты и в чужие не лезет. Это не сложно, это проверяется на уровне сегментных регистров: при попытке использовать сегмент, который нельзя, ОС получает тревожный сигнал (ака исключение), а процесс получает по башке (например, segmentation fault). Этим отличается защищённый режим от реального.
Термины реальный и защищённый режим вообще-то относятся к x86. В данном случае я применяю их нестрого, отношу к "защищённому режиму" те возможности, которые необходимы для реализации многозадачной ОС и которых не было в реальном режиме x86.
Это решение первой проблемы, но это ещё не виртуальная память.
Тут важно отметить, что процесс адресует память относительно начала сегмента, а где это начало он, по хорошему, понятия не имеет. А если имеет, то это ошибка проектирования ОС: не должен. Это ещё одно отличие защищённого режима от реального. А виртуальная память начинается с того, что ОС может временно выгрузить какой-то сегмент процесса из памяти, а когда этот сегмент опять понадобится процессу -- снова загрузить, возможно -- в другое место физической памяти (это термин; да, вот такое странное представление о физике).
Ну, допустим, как-то так:
- Есть редко работающий процесс. Например, он отзывается на какие-то внешние события, типа прихода данных из сети.
- ОС частично выгружает его из памяти. Содержимое сегментов данных этого процесса сохраняются на диске в специальном файле, сами сегменты помечаются как выгруженные (в таблице сегментов), занимаемая ими память -- как свободная (ОС должна где-то помнить, что у неё занято, а что свободно).
- И вот доходит дело до этого процесса. Он пытается обратиться к своему сегменту данных... Но тот помечен как выгруженный. ОС получает тревожный сигнал, загружает сегмент в память (возможно, выгрузив для этого что-то не нужное), помечает сегмент как нормальный, прописывает ему новый базовый адрес и просит процесс повторить попытку. Как в жизни.
Кто понимает, почему x86 не может так работать -- респект и уважуха :) Ну да, там всё будет немного сложнее, но это уже будет глубокий оффтопик.
Таким образом, выгружая и загружая, можно выполнять больше процессов, чем у нас памяти. Более того, разные процессы могут ссылаться на один и тот же сегмент кода, значит можно и сэкономить. Сложно с:
- динамическим выделением памяти (в принципе решаемо, но тяжело)
- фрагментацией физической памяти (хотя сегменты и можно перемещать, это довольно затратно)
Но главное -- это очень грубый инструмент, с низкой разрешающей способностью. Можно выгрузить/загрузить только целый сегмент, это много.
Страничная виртуальная память
Несложно заметить, что сегментная виртуальная память работает только за счёт того, что процесс адресует физическую память не напрямую, а немного косвенно. И, влезая в промежуточный уровень, ОС может незаметно для процесса химичить с памятью. Увеличиваем степень косвенности -- увеличиваем химические возможности ОС. По этому пути и пошли.Итак, процессу предоставляется виртуальное адресное пространство (ВАП), каждому своё. Оно большое. По нонешним прогрессивным временам адресуемая область от 0 до 264-1, недавно стандартом было от 0 до 232-1. Это много, особенно если учесть что процессов не один, а физической памяти, скажем, 256 Мб, т.е. даже на одного не хватает.
Но оно на то и большое, чтобы быть с запасом. Считается, что большая его часть никогда не понадобится большей части процессов. Т.е. в этом огромном ВАП есть небольшие островки используемой памяти. Процесс может запросить себе дополнительный островок, или отпустить какой-то из используемых... Речь не про ограничения, расчёт именно на то, что их на самом деле не нужно слишком много.
Дальше, ВАП разбито на небольшие блоки одинакового размера. К примеру, по 4096 байт. И на такие же блоки разбита физическая память. Блоки называются страницами.
Соответствие между используемыми островками ВАП и физической памятью происходит постранично. Т.е. ОС знает, что такой-то странице из ВАП такого-то процесса соответствует такая-то страница физической памяти. Есть какая-то таблица или что-то в этом роде. Неиспользуемым частям ВАП не соответствует ничего.
Это и есть дополнительный уровень косвенности.
Точно так же как и с сегментами, ОС может выгружать страницы, снова загружать, перемещать их в физической памяти, совмещать страницы разных процессов в одной физической. Только теперь это легче и эффективнее, т.к.:
- все страницы одинакового размера
- меньше дискретность
- В пределах ВАП элементарно регулируется объём используемой памяти, с точностью до страницы можно помечать память как используемую или как неиспользуемую процессом.
Что ещё интересного нужно отметить в этом красивом решении:
- Адресное пространство других процессов оказывается за пределами языка. Оно просто не адресуемо, нет таких адресов, которые ссылаются на другой процесс. Чтобы как-то читать/писать в память других процессов нужно специально думать.
- При попытке обратиться к неиспользуемой памяти, процесс тоже получит от ОС по башке.
To be continue... Следующая серия про x86, а дальше начнётся что-нибудь более интересное