Нехватка оперативной памяти
Одной из проблем ОС при работе с большим количеством процессов является нехватка физической оперативной памяти. В качестве диагностики этого процесса можно провести подсчёт запущенных процессов, умножив их на средний размер процесса (4 Мб для WindowsXP, 10 Мб для Windows 7, 16 для Windows 8/10). Это довольно грубая оценка. Расчет оперативной памяти для персонального компьютера.
Добавление 16Мб позволило увеличить этот показатель до 83%. Добавление ещё 16Мб до 93%. При этом мы практически достигнем максимума, поскольку 20% - это очень хороший показатель. Дальнейшее увеличение памяти не даст ощутимого прироста производительности.
Проблемы организации виртуальной памяти Чтобы обеспечить эффективность виртуальной памяти система должна быть очень «разумной». Если из памяти выгрузить блок, который тут же потребуется вновь, операционная система будет заниматься постоянным перемещением одних и тех же блоков в основную память и на диск. Задаче устранения этого нежелательного эффекта посвящен ряд исследовательских работ, выполнившихся в 70-х годах, приведших к появлению различных сложных, но эффективных алгоритмов. По сути, они сводятся к попыткам определить на основании последних событий в системе, какие блоки памяти потребуются в ближайшем будущем.
Хочу поговорить об устройстве управления памятью (MemoryManagementUnit, MMU). Как вы, разумеется, знаете, основной функцией MMU является аппаратная поддержка виртуальной памяти. Словарь по кибернетике под редакцией академика Глушкова говорит нам, что виртуальная память — это воображаемая память, выделяемая операционной системой для размещения пользовательской программы, ее рабочих полей и информационных массивов. У систем с виртуальной памятью четыре основных свойства:
1. Пользовательские процессы изолированы друг от друга и, умирая, не тянут за собой всю систему 2. Пользовательские процессы изолированы от физической памяти, то есть знать не знают, сколько у вас на самом деле оперативки и по каким адресам она находится. 3. Операционная система гораздо сложнее, чем в системах без виртуальной памяти 4. Никогда нельзя знать заранее, сколько времени займет выполнение следующей команды процессора
К сожалению, по какой-то причине все вышеперечисленные товарищи недостаточно почтительно относятся к MMU, а их знакомство с виртуальной памятью обычно начинается и заканчивается изучением страничной организации памяти и буфера ассоциативной трансляции (TranslationLookasideBuffer, TLB). Самое интересное при этом остается за кадром. Не хочу повторять Википедию, поэтому если вы забыли даже про то, что такое страничная память, то самое время перейти по ссылке. Про TLB будет пара строк ниже. Устройство MMU
Если программист хочет думать, что у него всегда четыре гигабайта памяти (разумеется, речь идет о 32-битном процессоре), а его программа — единственное, что отвлекает процессор ото сна, то нам потребуется виртуальная память. Чтобы добавить поддержку виртуальной памяти, достаточно между процессором и оперативной памятью разместить MMU, которое будет транслировать виртуальные адреса (адреса, используемые в программе) в физические (адреса, попадающие на вход микросхем памяти):
Вот что происходит внутри MMU:
1. Процессор подает на вход MMU виртуальный адрес 2. Если MMU выключено или если виртуальный адрес попал в нетранслируемую область, то физический адрес просто приравнивается к виртуальному 3. Если MMU включено и виртуальный адрес попал в транслируемую область, производится трансляция адреса, то есть замена номера виртуальной страницы на номер соответствующей ей физической страницы (смещение внутри страницы одинаковое):
· Если запись с нужным номером виртуальной страницы есть в TLB, то номер физической страницы берется из нее же · Если нужной записи в TLB нет, то приходится искать ее в таблицах страниц, которые операционная система размещает в нетранслируемой области ОЗУ (чтобы не было промаха TLB при обработке предыдущего промаха). Поиск может быть реализован как аппаратно, так и программно — через обработчик исключения, называемого страничной ошибкой (pagefault). Найденная запись добавляется в TLB, после чего команда, вызвавшая промах TLB, выполняется снова.
Лирическое отступление об MPU
Рассмотрим работу TLB на простом примере. Допустим, у нас есть два процесса А и Б. Каждый из них существует в своем собственном адресном пространстве и ему доступны все адреса от нуля до 0xFFFFFFFF. Адресное пространство каждого процесса разбито на страницы по 256 байт (это число я взял с потолка — обычно размер страницы не меньше одного килобайта), т.е. адрес первой страницы каждого процесса равен нулю, второй — 0x100, третьей — 0x200 и так далее вплоть до последней страницы по адресу 0xFFFFFF00. Разумеется, процесс не обязательно должен занимать все доступное ему пространство. В нашем случае Процесс А занимает всего две страницы, а Процесс Б — три. Причем одна из страниц общая для обоих процессов. Также у нас есть 1536 байт физической памяти, разбитой на шесть страниц по 256 байт (размер страниц виртуальной и физической памяти всегда одинаков), причем память эта отображается в физическое адресное пространство процессора с адреса 0x40000000 (ну вот так ее припаяли к процессору). В нашем случае первая страница Процесса А расположена в физической памяти с адреса 0x40000500. Не будем вдаваться в подробности того, как эта страница туда попадает — достаточно знать, что ее загружает операционная система. Она же добавляет запись в таблицу страниц (но не в TLB), связывая эту физическую страницу ссоответствующей ей виртуальной, и передает управление процессу. Первая же команда, выполненная Процессом А, вызовет промах TLB, в результате обработки которого в TLB будет добавлена новая запись. После этого операционная система может приостановить Процесс А и запустить Процесс Б. Его первую страницу она загрузит по физическому адресу 0x40000000. Однако, в отличие от Процесса А, первая команда Процесса Б уже не вызовет промах TLB, так как запись для нулевого виртуального адреса в TLB уже есть. В результате Процесс Б начнет выполнять код Процесса А! Что интересно, именно так и работал широко известный в узких кругах процессор ARM9. Самый простой способ решить эту проблему — инвалидировать TLB при переключении контекста, то есть отмечать все записи в TLB как недействительные. Это не самая хорошая идея, так как:
· В TLB на этот момент занято всего две записи из восьми, то есть новая запись поместилась бы туда без проблем · Удаляя из TLB записи, принадлежащие другим процессам, мы вынуждаем эти процессы генерировать повторные страничные ошибки, когда операционная система снова запустит их. Если в TLB не восемь записей, а тысяча, то производительность системы может значительно упасть.
Третий способ — это развитие второго. Вместо того, чтобы делить четыре гигабайта виртуального адресного пространства процессора между несколькими процессами, почему бы просто не увеличить его? Скажем, в 256 раз? А чтобы обеспечить изоляцию процессов, сделать так, чтобы каждому процессу по-прежнему было доступно ровно четыре гигабайта оперативной памяти? Теперь, когда процессор подает на вход MMU виртуальный адрес, поиск в TLB производится по комбинации VPN и ASID, поэтому первая команда Процесса Б вызовет страничную ошибку даже без предшествующей инвалидации TLB. В современных процессорах ASID чаще всего или восьмибитный, или 16-битный. Например, во всех процессорах ARM с MMU, начиная с ARM11, ASID восьмибитный, а в архитектуре ARMv8 добавлена поддержка 16-битного ASID. Кстати, если процессор поддерживает виртуализацию, то помимо ASID у него может быть еще и VSID (VirtualaddressSpaceIDentificator), который еще больше расширяет виртуальное адресное пространство процессора и содержит номер запущенной на нем виртуальной машины. Даже после добавления ASID могут возникнуть ситуации, когда нужно будет инвалидировать одну или несколько записей или даже весь TLB:
1. Если физическая страница выгружена из оперативной памяти на диск — потому что обратно в память эта страница может быть загружена по совсем другому адресу, то есть виртуальный адрес не изменится, а физический изменится 2. Если операционная система изменила PID процесса — потому что и ASID станет другим 3. Если операционная система завершила процесс
На этом можно было бы завершать рассказ об MMU, если бы не один нюанс. Дело в том, что между процессором и оперативной памятью помимо MMU находится еще и кэш-память. Те, кто забыл, что такое кэш-память и как она работает, могут освежить знания тут и там. Для примера возьмем двухканальный (2-way) кэш размером один килобайт с размером строки кэша (или линии кэша — как вам угодно) в 64 байта. Сейчас не важно, кэш ли это команд, кэш данных или объединенный кэш. Поскольку размер страницы памяти у нас 256 байт, то в каждой странице помещается четыре строки кэша. Поскольку кэш находится между процессором и MMU, то очевидно, что и для индексации, и для сравнения тэгов используются исключительно виртуальные адреса (физические адреса появляются только на выходе MMU). По-английскитакойкэшназывается Virtually Indexed, Virtually Tagged cache (VIVT).
1. Предположим, что сначала выполняется Процесс А. В процессе выполнения в кэш-память одна за другой загружаются строки с командами или данными для этого процесса (Строки 0-4). 2. Операционная система останавливает Процесс А и передает управление Процессу Б. 3. По идее, в этот момент процессор должен загрузить в кэш первую строку из первой страницы Процесса Б и начать выполнять оттуда команды. На самом деле, процессор подает на вход кэша виртуальный адрес 0x0, после чего кэш отвечает, что ничего загружать не надо, ибо нужная строка уже закэширована. 4. Процесс Б начинает весело выполнять код Процесса А.
Подождите, скажете вы, мы же только что решили эту проблему, добавив ASID в TLB? А кэш-то у нас стоит до MMU — когда промаха кэша нет, то мы в MMU и не смотрим, и какой там ASID — знать не знаем. Это первая проблема c VIVT-кэшем — так называемые омонимы (homonyms), когда один и тот же виртуальный адрес может отображаться на разные физические адреса (в нашем случае виртуальный адрес 0x00000000 отображается на физический адрес 0x40000500 для Процесса А и 0x40000000 для Процесса Б). Вариантов решения проблемы омонимов множество:
1. «Флашить» кэш (cacheflush, т.е. записывать измененное содержимое кэша обратно в память) и инвалидировать кэш (то есть отмечать строки как недействительные) при переключении контекста. Если у нас раздельный кэш команд и данных, то кэш команд достаточно просто инвалидировать (то есть отметить все строки как пустые), потому что кэш команд доступен процессору только для чтения и флашить его нет смысла 2. Добавить ASID в тэг кэша 3. Обращаться в MMU каждый раз, когда мы обращаемся в кэш, а не только тогда, когда случился промах — тогда можем воспользоваться уже имеющейся логикой сравнения PID с ASID. Прощайте, энергосбережение и быстродействие!
Синонимы в VIVT-кэше
1. Сначала выполняется Процесс А — в кэш одна за другой загружаются Строки А0 — А3 и Общая строка 0 (помните, что у процессов А и Б одна страница общая) 2. Потом операционная система, не флаша кэш, переключает контекст 3. Начинает выполняться Процесс Б. В кэш загружаются строки Б0 — Б7. 4. Наконец, Процесс Б обращается к Общей строке 0. Эта строка уже загружена в кэш Процессом А, но процессор про это не знает, так как она фигурирует в кэше под другим виртуальным адресом (напомню, что в VIVT-кэше физических адресов нет) 5. Возникает промах кэша. Виртуальный адрес 0x00000200 транслируется в физический адрес 0x40000200, и Общая строка 0 повторно загружается в кэш. Ее расположение определяется виртуальным адресом — а адресу 0x00000200 соответствует Индекс 0 (биты 8-6) и Тэг 0x1 (биты 31-9). 6. Поскольку оба канала в наборе с индексом 0 уже заняты, приходится выкинуть одну из уже загруженных строк (А0 или Б0). Используя алгоритм LRU (LeastRecentlyUsed), кэш выкидывает Строку А0 и на ее место добавляет Общую строку 0.
В результате один и тот же кусок физической памяти находится в кэше в двух разных местах. Теперь если оба процесса изменят свою копию, а потом захотят сохранить ее в память, то один из них ждет сюрприз.
1. Самый простой способ, как мы уже выяснили — флашить кэш при каждом переключении контекста (кстати, для борьбы с синонимами, в отличие от омонимов, инвалидировать кэш не нужно). Однако, во-первых, это дорого, а во-вторых, это не поможет, если процесс захочет иметь в своем адресном пространстве несколько копий одной и той же физической страницы 2. Можно выявлять синонимы аппаратно:
· Либо при каждом промахе пробегать по всему кэшу, выполняя трансляцию адреса для каждого тэга и сравнивая полученные физические адреса с тем, который получен при трансляции адреса, вызвавшего промах (и позавидуют живые мертвым!) · Либо добавить в процессор новый блок, который будет выполнять обратную трансляцию — преобразование физического адреса в виртуальный. После этого при каждом промахе выполнять трансляцию вызвавшего его адреса, после чего при помощи обратной трансляции преобразовывать этот физический адрес в виртуальный и сравнивать его со всеми тэгами в кэше. Ей-богу, лучше бы вам просто флашить кэш! 3. Третий способ — избегать синонимов программно. Например, не использовать общие страницы. Или снова начать линковать программы в общее адресное пространство.
Популярное: Личность ребенка как объект и субъект в образовательной технологии: В настоящее время в России идет становление новой системы образования, ориентированного на вхождение... Модели организации как закрытой, открытой, частично открытой системы: Закрытая система имеет жесткие фиксированные границы, ее действия относительно независимы... Почему двоичная система счисления так распространена?: Каждая цифра должна быть как-то представлена на физическом носителе... ©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (420)
|
Почему 1285321 студент выбрали МегаОбучалку... Система поиска информации Мобильная версия сайта Удобная навигация Нет шокирующей рекламы |