При прочтении Таненбаума возникли сложности с пониманием организации памяти в ОС. Какая модель используется в современных ОС? Сегментная, страничная, сегментно-страничная? В частности привело смущение сказанное про компиляторы, что для них использование сегментной организации памяти гораздо удобнее, но компилятор же запускается в ОС. Это и запутало. Как я понимаю как работать с памятью реализовано в ОС, но разве программа может изменить этот способ?
jfs
52.2k11 золотых знаков108 серебряных знаков310 бронзовых знаков
задан 8 окт 2016 в 23:45
3
- В современных ОС используется либо страничная, либо
сегментно-страничная организация памяти.
Сегментная не выгодна с точки зрения рациональности использования физической памяти (возможна фрагментация).
За всю работу по переводу логического адреса в физический ответственна аппаратура ( MMU ), а за формирование правил и таблиц по которым будет осуществлен перевод — ОС.
- То есть, компилятор (и любая другая программа) не может перейти в другой режим адресования.
С точки зрения прикладной программы, она (программа) находится в линейном адресном пространстве размером около 2Гб (для 32 битной windows), так как, 2 ГБ виртуального адресного пространства занимает код ОС, драйверов и DLL.
Хочу заметить, что виртуальный адрес не имеет ничего общего с физическим.
Очень утрированно процесс управления памятью показан на картинке ниже:
ответ дан 15 окт 2016 в 15:09
RomanRoman
812 бронзовых знака
2
4.2.1.
Простое
непрерывное
распределение
памяти
Это самое простое решение, согласно которому вся память условно может быть
разделена на три участка:
• область, занимаемая операционной
системой;
• область, в которой размещается
исполняемая задача;
• свободная область памяти.
Изначально
являясь самой первой схемой, она
продолжает и сегодня быть доста-
точно
распространенной в элементарных системах. При
простом непрерывном распре-
делении предполагается,
что ОС не поддерживает мультипрограммирование,
поэтому не
возникает проблемы
распределения памяти между несколькими
задачами. Программные
модули, необходимые
для всех программ, располагаются в
области самой ОС, а вся ос-
тавшаяся память
может быть предоставлена задаче. Эта
область памяти является непре-
рывной, что облегчает работу системы
программирования.
Для того, чтобы
выделить наибольший объем памяти для
задач, ОС строится таким
образом, что постоянно в оперативной памяти располагается только самая нужная ее
часть.
Эту часть стали называть ядром
операционной
системы.
Остальные модули ОС
являются
диск-резидентными, то есть загружаются
в оперативную память только по не-
обходимости, и после своего выполнения
вновь выгружаются.
Такая схема распределения памяти влечет за собой два вида потерь вычисли-
тельных
ресурсов
[2]: потеря процессорного времени, потому
что центральный процес-
сор простаивает,
пока задача ожидает завершения операций
ввода/вывода, и потеря са-
мой оперативной памяти, потому что далеко не каждая программа использует всю
память, а режим работы
в этом случае однопрограммный. Однако
это очень недорогая
реализация, которая
позволяет отказаться от многих
дополнительных функций операци-
онной системы. В частности, от такой
сложной проблемы, как защита памяти.
Классическим примером
для данного
случая
является
распределение
памяти
в
ОС
DOS.
Как известно, MS-DOS — это однопрограммная
ОС. В ней, конечно, можно ор-
ганизовать запуск резидентных или
TSR-задач, но в целом она предназначена для вы-
полнения только одного вычислительного
процесса.
В IBM
PC изначально использовался
16-разрядный микропроцессор i8086/88, ко-
торый за счет
введения сегментного способа адресации
позволял адресовать память объ-
емом до 1 Мбайт.
В последующих ПК (IBM PC AT, AT386 и др.)
поддерживалась со-
вместимость с первыми, поэтому при работе с
MS-DOS прежде всего рассматривают
первый мегабайт.
Таким образом, вся память в соответствии
с архитектурой IBM PC ус-
ловно может быть разбита на три части
[ОС].
В самых младших адресах памяти
(первые 1024 ячейки) размещается таблица
векторов прерываний. Это связано с аппаратной реализацией процессора
i8086/88, на
котором был
реализован ПК. В последующих процессорах
(начиная с i80286) адрес таб-
94
лицы прерываний определяется через
содержимое соответствующего регистра,
но для
обеспечения полной совместимости с
первым процессором при включении или
аппарат-
ном сбросе в этот
регистр заносятся нули. Следует отметить,
что в случае использования
современных
микропроцессоров i80x86 векторы прерываний
можно разместить и в дру-
гой области.
Вторая часть памяти
отводится для размещения программных
модулей самой
MS-DOS и
программ пользователя. Эта область
памяти называется Conventional
Memory
(основная, стандартная память).
В младших адресах основной памяти размещается то,
что можно назвать
ядром ОС — системные переменные, основные
программные модули,
блоки
данных для буферизации операций
ввода/вывода. Для управления устройствами,
драйверы которых
не входят в базовую подсистему
ввода/вывода, загружаются так на-
зываемые
загружаемые
(или инсталлируемые)
драйверы, перечень которых определя-
ется в специальном
конфигурационном файле CONFIG.SYS. В случае
использования ПК
с объемом ОП более
1 Мбайта и наличия в памяти драйвера
HIMEM.SYS возможно раз-
мещение данных за
пределами первого мегабайта. Эта область
памяти получила назва-
ние HMA
(high memory area- область высокой памяти)
Наконец,
третья
часть
адресного
пространства
отведена для постоянных запо-
минающих устройств
и функционирования некоторых устройств
ввода/вывода. Эта об-
ласть памяти получила
название UMA
(upper memory areas — область верхней памяти).
Для того чтобы предоставлять больше памяти программам пользователя, в
MS-
DOS применено
то же решение, что и во многих других простейших
ОС — командный
процессор COMMAND.СОМ
сделан состоящим из двух частей.
Первая часть является
резидентной, она
размещается в области ядра. Вторая часть
— транзитивная; она разме-
щается в области
старших адресов раздела памяти, выделяемой
для программ пользова-
теля. Если
программа пользователя перекрывает
собой область, в которой была распо-
ложена транзитивная часть командного процессора, то последний при необходимости
восстанавливает ее в памяти после выполнения программы, так как далее управление
возвращается резидентной части
COMMAND.СОМ.
4.2.2.
Распределение
памяти
с
перекрытием (оверлейные
структуры)
Если есть
необходимость создать программу,
логическое (и виртуальное) адресное
пространство которой должно быть больше, чем свободная область памяти, или даже
больше, чем весь
возможный объем оперативной памяти, то
используется распределение
с перекрытием.
Этот способ распределения предполагает,
что вся программа может быть
разбита
на части — сегменты. Каждая оверлейная
программа имеет одну главную
часть
(main) и
несколько
сегментов
(segment), причем в памяти машины одновременно
могут
находиться только
ее главная часть и один или несколько
не перекрывающихся сегмен-
тов.
Пока в оперативной памяти
располагаются выполняющиеся сегменты,
остальные
находятся во внешней памяти.
После того как текущий
(выполняющийся) сегмент за-
вершит свое
выполнение, возможны два варианта. Либо
он сам (если данный сегмент не
нужно сохранить
во внешней памяти в его текущем состоянии)
обращается к ОС с ука-
занием, какой
сегмент должен быть загружен в память
следующим. Либо он возвращает
управление главному сегменту задачи
(в модуль main), и уже тот обращается к ОС с
указанием, какой
сегмент сохранить (если это нужно), а
какой сегмент загрузить в опе-
ративную память, и вновь отдает управление
одному из сегментов, располагающихся
в
памяти. Простейшие
схемы сегментирования предполагают,
что в памяти в каждый кон-
кретный момент времени
может располагаться только один сегмент
(вместе с модулем
main).
Более сложные схемы, используемые в больших вычислительных системах,
по-
зволяют располагать
сразу по несколько сегментов. В некоторых
вычислительных ком-
95
плексах могли существовать отдельно
сегменты кода и сегменты данных. Сегменты
ко-
да, как правило,
не претерпевают изменений в процессе
своего исполнения, поэтому при
загрузке нового сегмента кода
на место отработавшего последний
можно не сохранять
во внешней памяти, в отличие от сегментов
данных, которые необходимо сохранять
в
любом случае.
Первоначально программисты сами должны были включать в тексты своих про-
грамм
соответствующие обращения (вызовы)
к ОС и тщательно планировать, какие сег-
менты могут
находиться в оперативной памяти
одновременно, чтобы их адресные про-
странства не пересекались. В более современных системах программирования вызовы
стали вставляться компиляторами в код
программы автоматически, если в том
возникает
необходимость.
4.2.3.
Распределение
памяти
разделами
Для организации мультипрограммного режима необходимо обеспечить одновре-
менное расположение в оперативной памяти
нескольких задач (целиком или их частя-
ми). Самая простая
схема распределения памяти между
несколькими задачами предпо-
лагает, что память, незанятая ядром ОС, может быть разбита на несколько
непрерывных
частей
(зон, разделов). Разделы характеризуются именем,
типом, грани-
цами (как правило,
указываются начало раздела и его длина).
Разбиение памяти на не-
сколько
непрерывных
разделов
может быть фиксированным
(статическим),
либо ди-
намическим.Рассмотрим
вышесказанное более подробно.
Разбиение всего
объема оперативной памяти на несколько
разделов может осуще-
ствляться
единовременно
(то есть в процессе генерации варианта
ОС, который потом и
эксплуатируется) или по
мере необходимости. Пример разбиения памяти
на разделы с
фиксированными границами приведен на
рис.4.2.
Рис.4.2.
Распределение
памяти
разделами
с
фиксированными
границами
В каждом разделе
на данный момент времени может
располагаться по одной про-
грамме (задаче). В этом случае по отношению
к каждому разделу можно применить все
96
те методы разработки программ, которые
используются для однопрограммных
систем.
Возможно использование
оверлейных структур, что позволяет
создавать более сложные
программы и в то же время поддерживать
идеологию мультипрограммирования.
Первые
многозадачные ОС, а в более поздний период
— недорогие вычислительные системы
строились по
подобной схеме, так как она является
несложной и обеспечивает возмож-
ность параллельного выполнения программ. Иногда в одном разделе размещалось по
несколько небольших программ, которые постоянно в нем и находились. Такие про-
граммы назывались
ОЗУ-резидентными.
При небольшом
объеме памяти и, следовательно, небольшом
количестве разделов
увеличить количество
параллельно выполняемых приложений
(особенно когда эти при-
ложения интерактивны и
во время своей работы фактически не
используют процессор-
ное
время, а в основном ожидают операций
ввода/вывода) можно за счет идеологии
сво-
пинга
(swapping). При
свопинге задача может быть целиком выгружена на магнитный
диск (перемещена
во внешнюю память), а на ее место
загружается либо более привиле-
гированная, либо просто готовая к
выполнению другая задача, находившаяся
на диске в
приостановленном состоянии.
Основным
недостатком такого
способа распределения памяти
является наличие
достаточно большого
объема неиспользуемой памяти, которая
может быть в каждом из
разделов. Поскольку
разделов несколько, то и неиспользуемых
областей получается не-
сколько,
поэтому такие потери стали называть фрагментацией
памяти.
В отдельных
разделах потери
памяти могли быть достаточно значительными,
но использовать фраг-
менты свободной памяти при таком способе
распределения практически невозможно.
Чтобы избавиться
от фрагментации, можно размещать в
оперативной памяти зада-
чи
плотно (динамически),
одну за другой, выделяя ровно столько
памяти, сколько зада-
ча требует.
Одной
из первых ОС, реализовавшей такой способ
распределения памяти, была ОС
MVT (multiprogramming with a
variable number of tasks) — мультипрограммирование
с пе-
ременным числом задач.
Эта ОС была одной из самых распространенных
при эксплуа-
тировании
больших ЭВМ класса IBM 360/370. В этой системе
специальный
планиров-
щик
(диспетчер памяти) ведет список адресов свободной оперативной памяти. При
появлении новой задачи диспетчер памяти
просматривает этот список и выделяет
для
задачи раздел,
объем которого либо равен необходимому, либо
чуть больше, если па-
мять
выделяется не ячейками, а некими
дискретными единицами. При этом модифици-
руется список
свободной памяти. При освобождении
раздела диспетчер памяти пытается
объединить
освобождающийся раздел с одним из
свободных участков, если таковой яв-
ляется смежным.
При этом список свободных участков может быть упорядочен либо по адресам,
либо по объему.
Выделение памяти под новый раздел может
осуществляться одним из
трех методов [2]:
• первый подходящий участок;
• самый подходящий участок;
• самый неподходящий участок.
В
первом
случае
список свободных областей упорядочивается
по адресам (напри-
мер, по их
возрастанию). Диспетчер памяти
просматривает этот список и выделяет
зада-
че раздел в той
области, которая первой подойдет по
объему. В этом случае, если подоб-
ный фрагмент имеется, то в среднем необходимо просмотреть половину списка. При
освобождении
раздела также необходимо просмотреть
половину списка. Таким образом,
«первый
подходящий»
метод приводит
к
тому,
что память
для небольших
задач
преимущественно будет
выделяться
в
области младших
адресов
и,
следовательно,
это
будет увеличивать вероятность
того,
что в
области старших адресов будут
образовываться
фрагменты
достаточно
большого
объема.
97
Метод «самый
подходящий»
предполагает, что список свободных
областей упо-
рядочен по
возрастанию объема этих фрагментов. В
этом случае при просмотре списка
для нового раздела
будет использован фрагмент свободной
памяти, объем которой наи-
более точно соответствует требуемому. Требуемый раздел будет определяться по-
прежнему в результате
просмотра в среднем половины списка. В
результате оставшийся
фрагмент оказывается
настолько малым, что в нем уже вряд ли
удастся разместить ка-
кой-либо еще раздел
и при этом этот фрагмент попадет в самое
начало списка. Поэтому
в целом такую дисциплину
нельзя
назвать
эффективной.
Как
ни странно, самым
эффективным
методом
является последний, по которому
для нового раздела выделяется
«самый неподходящий» фрагмент свободной памяти.
Для этой дисциплины
список свободных областей упорядочивается
по убыванию объе-
ма свободного
фрагмента. Очевидно, что если есть такой
фрагмент памяти, то он сразу
же и будет найден,
и поскольку этот фрагмент является
самым большим, то, скорее все-
го,
после выделения из него раздела памяти
для задачи оставшаяся область памяти
еще
сможет быть использована в дальнейшем.
Очевидно, что при
любом из вышеуказанных методов, из-за
того, что задачи появ-
ляются и завершаются
в произвольные моменты времени и при
этом они имеют разные
объемы, в памяти
всегда будет наблюдаться сильная
фрагментация. При этом возможны
ситуации, когда
из-за сильной фрагментации памяти
диспетчер задач не сможет образо-
вать новый раздел,
хотя суммарный объем свободных областей
будет больше, чем необ-
ходимо для задачи. В
этой ситуации необходимо организовать так
называемое уплот-
нение памяти (дефрагментацию). Для уплотнения все вычисления
приостанавливаются, и диспетчер памяти
корректирует свои списки, перемещая
разделы
в начало
памяти (или, наоборот, в область старших адресов).
Недостатком этого ре-
шения является потеря времени на дефрагментацию и, что самое главное, невоз-
можность при этом выполнять сами
вычислительные процессы.
Следует отметить, что данный способ распределения памяти применялся доста-
точно длительное
время, так как в нем для задач выделяется
непрерывное адресное про-
странство, что существенно упрощает
создание систем программирования и их
работу.
В современных ОС на
смену различным непрерывным способам распределения
памяти пришли разрывные
способы.
Идея заключается в предложении размещать
задачу
не в одной непрерывной
области памяти, а в нескольких областях.
Это требует для своей
реализации
соответствующей аппаратной поддержки
— нужно иметь относительную ад-
ресацию. Если
указывать адрес начала текущего фрагмента
программы и величину сме-
щения относительно этого
начального адреса, то можно указать необходимую
нам пе-
ременную или команду. Таким образом, виртуальный адрес можно представить
состоящим из двух
полей.
Первое поле будет указывать часть программы
(с которой
сейчас
осуществляется работа) для определения
местоположения этой части в памяти, а
второе поле
виртуального адреса позволит найти
нужную нам ячейку относительно най-
денного адреса. Программист может либо самостоятельно разбивать программу на
фрагменты, либо
автоматизировать эту задачу и возложить
ее на систему программиро-
вания. Рассмотрим наиболее распространенные разрывные способы распределения па-
мяти.
4.2.4.
Сегментное
распределение
памяти
Для реализации этого способа программу необходимо разбивать на части и уже
каждой части в отдельности выделять физическую память. Естественным механизмом
разбиения программы на части является разбиение ее на логические элементы
— сег-
менты.
В принципе каждый программный модуль
может быть воспринят как отдельный
сегмент, и тогда
вся программа будет представлять собой
множество сегментов. Каждый
98
сегмент размещается в памяти как до
определенной степени самостоятельная
единица.
Логически обращение к элементам программы
в этом случае будет представляться как
указание имени сегмента и смещения относительно
начала этого сегмента. Физически
имя (или порядковый
номер) сегмента будет соответствовать
некоторому адресу, с кото-
рого этот сегмент начинается при его размещении в памяти, и
смещение должно при-
бавляться к этому базовому адресу.
Преобразование имени сегмента
в его порядковый номер осуществляет
система
программирования,
а операционная
система
размещает сегменты в физической памя-
ти и
для каждого сегмента получает информацию
о его начале. Таким образом, вирту-
альный
адрес
для
данного
способа
состоит
из
двух
полей
—
номер
сегмента
и
смеще-
ние относительно
начала
сегмента.
Каждый сегмент, размещаемый в памяти,
имеет
соответствующую информационную структуру, часто называемую дескриптором сег-
мента.
Именно операционная система строит для
каждого исполняемого процесса соот-
ветствующую таблицу
дескрипторов сегментов и при размещении
каждого из сегментов
в оперативной
или внешней памяти в дескрипторе
отмечает его текущее местоположе-
ние. Если сегмент
задачи в данный момент находится в
оперативной памяти, то об этом
делается
пометка в дескрипторе. Как правило, для
этого используется «бит
присутст-
вия»
(present bit). В
этом
случае
в
поле
«адрес»
диспетчер
памяти
записывает
адрес
физической
памяти,
с которого сегмент начинается, а в
поле
«длина
сегмента»
(limit)
указывается
количество
адресуемых
ячеек
памяти.
Это поле используется не только
для того, чтобы
размещать сегменты без наложения один
на другой, но и для того, чтобы
проконтролировать,
не обращается ли код исполняющейся задачи
за пределы текущего
сегмента. В случае
превышения длины сегмента вследствие
ошибок программирования
мы можем говорить
о нарушении адресации и с помощью
введения специальных аппа-
ратных
средств генерировать сигналы прерывания,
которые позволят обнаруживать по-
добные
ошибки. Если бит
присутствия
в дескрипторе указывает, что сейчас
этот сег-
мент находится не
в оперативной, а во внешней памяти
(например, на жестком диске), то
названные поля
адреса и длины используются для указания
адреса сегмента в координа-
тах внешней памяти.
Помимо информации о местоположении сегмента, в дескрипторе сегмента, как
правило, содержатся
данные о его типе (сегмент кода или
сегмент данных), правах дос-
тупа к этому сегменту (можно или нельзя
его модифицировать, предоставлять
другой
задаче), отметка об обращениях к данному
сегменту (информация о том, как часто
этот
сегмент используется),
на основании которых можно принять
решение о предоставлении
места, занимаемого текущим сегментом,
другому сегменту.
При
передаче управления следующей задаче ОС
должна занести в соответствую-
щий
регистр адрес таблицы дескрипторов
сегментов для этой задачи. Сама таблица
де-
скрипторов
сегментов,
в свою очередь, также представляет собой
сегмент данных, ко-
торый обрабатывается диспетчером памяти
операционной системы.
При таком подходе появляется
возможность размещать в оперативной памяти
не
все сегменты задачи,
а только те, с которыми в настоящий
момент происходит работа.
Если требуемого
сегмента в оперативной памяти нет, то
возникает прерывание и управ-
ление передается
через диспетчер памяти программе
загрузки сегмента. Пока происхо-
дит поиск сегмента
во внешней памяти и загрузка его в
оперативную, диспетчер памяти
определяет
подходящее для сегмента место. Возможно,
что свободного места нет, и то-
гда принимается решение о выгрузке какого-нибудь сегмента и его перемещение во
внешнюю память. Если при этом еще остается
время, то процессор передается другой
готовой к выполнению
задаче. После загрузки необходимого сегмента
процессор вновь
передается задаче, вызвавшей прерывание из-за отсутствия сегмента. Всякий раз при
считывании сегмента
в оперативную память в таблице дескрипторов
сегментов необхо-
димо установить адрес начала сегмента
и признак присутствия сегмента.
99
При
поиске
свободного
места
обычно используются правила
«первого подходя-
щего» и
«самого неподходящего».
Если свободного фрагмента в памяти достаточного
объема нет, но тем не менее сумма свободных фрагментов превышает требования по
памяти для нового сегмента, может быть
применена процедура дефрагментации
памяти.
В идеальном случае размер сегмента
должен быть достаточно малым, чтобы его
можно было разместить в случайно
освобождающихся фрагментах оперативной
памяти,
но и достаточно большим, чтобы содержать
логически законченную часть программы
с
тем, чтобы минимизировать межсегментные
обращения.
Для
решения
проблемы
замещения
(определения того сегмента, который
должен
быть либо перемещен во внешнюю память,
либо просто замещен новым) используются
следующие правила [2]:
• FIFO
(first in — first out, что означает: «первый
пришедший первым и выбывает»);
• LRU
(least recently used, что означает «последний из
недавно использованных» или,
иначе говоря, «дольше всего неиспользуемый»);
• LFU
(least frequently used, что означает: «используемый
реже всех остальных»);
• random
(случайный) выбор сегмента.
Первая и последняя дисциплины являются
самыми простыми в реализации, но они
не учитывают, насколько часто используется тот
или иной сегмент и, следовательно,
диспетчер памяти может выгрузить или
расформировать тот сегмент, к которому
в са-
мом ближайшем будущем будет обращение.
Алгоритм FIFO ассоциирует с каждым
сегментом время, когда он был помещен
в
память. Для замещения выбирается наиболее
старый сегмент. Учет времени необязате-
лен, когда все сегменты в
памяти связаны в FIFO-очередь и каждый
помещаемый в па-
мять сегмент добавляется в хвост этой
очереди. Алгоритм учитывает только
время на-
хождения сегмента в памяти, но не
учитывает фактическое использование сегментов.
Например, первые загруженные сегменты
программы могут содержать переменные,
ис-
пользуемые на протяжении работы всей
программы. Это приводит к немедленному
воз-
вращению к только что замещенному
сегменту.
Для реализации дисциплин LRU и LFU необходимо,
чтобы процессор имел допол-
нительные аппаратные средства. В принципе
достаточно, чтобы при обращении к деск-
риптору сегмента для получения физического
адреса, с которого сегмент начинает
рас-
полагаться в памяти, соответствующий
бит обращения менял свое значение
(скажем, с
нулевого, которое установила ОС, в
единичное). Тогда диспетчер памяти может
время
от времени просматривать таблицы
дескрипторов исполняющихся задач и
собирать для
соответствующей обработки статистическую
информацию об обращениях к сегментам.
В результате можно составить
список, упорядоченный либо по длительности
не исполь-
зования (для дисциплины LRU), либо по
частоте использования (для дисциплины
LFU).
Важнейшей
проблемой,
которая возникает при организации
мультипрограммного
режима, является защита памяти. Для того чтобы выполняющиеся приложения не
смогли испортить саму ОС и другие
вычислительные процессы, необходимо,
чтобы дос-
туп к таблицам сегментов с целью их
модификации был обеспечен только для
кода са-
мой ОС. Для этого код ОС должен выполняться
в некотором привилегированном режи-
ме, из которого можно осуществлять манипуляции
с дескрипторами сегментов, тогда
как выход за пределы сегмента в обычной
прикладной программе должен вызывать
пре-
рывание по защите памяти. Каждая
прикладная задача должна иметь возможность
об-
ращаться только к своим собственным
сегментам.
Остановимся на основных недостатках
сегментного способа распределения
памя-
ти. Во-первых, по сравнению
с непрерывными способами, произошло
увеличение
вре-
мени
на
загрузку
программы
и
ее
сегментов.
Для получения доступа к искомой ячейке
памяти необходимо сначала найти и
прочитать дескриптор сегмента, а уже
потом, ис-
пользуя данные из него, вычислить и
конечный физический адрес сегмента.
Для того
100
чтобы уменьшить эти потери,
используется кэширование,
т.е расположение дескрипто-
ров выполняемых задач в кэш-памяти
микропроцессора.
Вторым недостатком является наличие фрагментации памяти, хотя и сущест-
венно меньшей, чем при непрерывном
распределении.
Кроме
этого, имеются потери
памяти
и
процессорного
времени
на
размещение
и
обработку
дескрипторных
таблиц,
так как на каждую задачу необходимо
иметь свою
таблицу дескрипторов сегментов, а при определении физических адресов необходимо
выполнять операции сложения. Уменьшение этих недостатков реализовано в странич-
ном способе распределения памяти,
который будет рассмотрен далее.
Примером использования сегментного способа организации виртуальной памяти
является
операционная система для ПК OS/2 ver.1,
созданная для процессора i80286. В
этой ОС в полной
мере использованы аппаратные
средства микропроцессора, который
специально проектировался для поддержки
сегментного способа распределения
памяти.
OS/2 v.1 поддерживала распределение памяти,
при котором выделялись сегменты
программы и сегменты
данных. Система позволяла работать как
с именованными, так и
неименованными
сегментами. Имена разделяемых сегментов
данных имели ту же фор-
му, что и имена
файлов. Процессы получали доступ к
именованным разделяемым сег-
ментам, используя
их имена в специальных системных вызовах.
OS/2 v.1 допускала раз-
деление программных сегментов приложений и подсистем, а также глобальных
сегментов данных
подсистем. Сегменты, которые активно
не использовались, могли вы-
гружаться на
жесткий диск. Система восстанавливала
их, когда в этом возникала необ-
ходимость. Вообще,
вся концепция системы OS/2 была построена
на понятии разделения
памяти:
процессы почти всегда разделяют
сегменты с другими процессами. В этом со-
стояло ее существенное
отличие от систем типа UNIX, которые обычно
разделяют толь-
ко реентерабельные программные модули
между процессами.
4.2.5.
Страничное
распределение
памяти
В этом способе
все фрагменты (части) задачи имеют
одинаковый размер и длину,
кратную степени
двойки. Вследствие этого, операции
сложения можно заменить опера-
циями слияния, что существенно уменьшает вычислительную нагрузку. Одинаковые
части
задачи называют страницами и
говорят, что память разбивается на
физические
страницы, а программа
— на виртуальные страницы. Часть
виртуальных страниц задачи
может размещаться
в оперативной памяти, а часть — во внешней.
Обычно место во внеш-
ней памяти, в
качестве которой в абсолютном большинстве
случаев выступают жесткие
диски, называют файлом
подкачки
или страничным
файлом
(paging file). Иногда этот
файл
называют swap-файлом,
тем самым подчеркивая, что записи этого
файла — страни-
цы, которые замещают
друг друга в оперативной памяти. В
некоторых ОС выгруженные
страницы располагаются
не в файле, а в специальном разделе
дискового пространства. В
UNIX-системах для этих целей выделяется специальный раздел, но кроме него могут
быть использованы и файлы, выполняющие те же функции, если
объема выделяемого
раздела недостаточно.
Разбиение всей оперативной памяти на страницы одинаковой величины, причем
величина
каждой
страницы выбирается
кратной
степени
двойки,
приводит к тому,
что вместо одномерного адресного пространства памяти можно говорить о дву-
мерном.
Первая координата адресного пространства
— это номер страницы, а вторая ко-
ордината — номер
ячейки внутри выбранной страницы
(его называют индексом). Таким
образом,
физический адрес определяется парой
(Рр, i), а виртуальный адрес — парой (РV,
i), где
РV
— это номер виртуальной страницы, Рр —
это номер физической страницы и i —
это
индекс ячейки внутри страницы. Количество
бит,
отводимое
под
индекс,
опреде-
ляет
размер
страницы,
а
количество
бит,
отводимое
под
номер
виртуальной
стра-
101
ницы, —
объем
возможной
виртуальной
памяти,
которой
может
пользоваться
про-
грамма [2]. Отображение, осуществляемое системой во время исполнения, сводится к
отображению
РVв
Рр и приписыванию к полученному значению
битов адреса, задавае-
мых величиной
i. При этом нет необходимости ограничивать
число виртуальных стра-
ниц числом физических, то есть не поместившиеся страницы можно размещать во
внешней памяти,
которая в данном случае служит расширением
оперативной. Для ото-
бражения виртуального адресного
пространства задачи на физическую
память, как и в
случае
с сегментным способом организации,
каждой задаче необходимо иметь таблицу
страниц для трансляции адресных пространств. Для описания каждой страницы дис-
петчер
памяти ОС заводит соответствующий
дескриптор,
который отличается от деск-
риптора сегмента
прежде всего тем, что в нем нет необходимости
иметь поле длины, т.к.
все страницы имеют одинаковый размер.
По номеру виртуальной страницы в таблице
дескрипторов страниц текущей задачи находится соответствующий элемент
(дескрип-
тор). Если бит
присутствия имеет единичное значение,
значит, данная страница сейчас
размещена в
оперативной, а не во внешней памяти и в
дескрипторе записан номер физи-
ческой страницы,
отведенной под данную виртуальную. Если
же бит присутствия равен
нулю, то в дескрипторе
записан адрес виртуальной страницы,
расположенной в данный
момент
во внешней памяти. Таким достаточно
сложным механизмом и осуществляет-
ся
трансляция
виртуального
адресного
пространства
на
физическую
память.
Защита страничной памяти,
как и в случае с сегментным механизмом,
основана
на контроле уровня доступа к каждой странице. Как правило, возможны следующие
уровни
доступа: только
чтение;
чтение
и
запись;
только
выполнение.
В этом случае
каждая страница
снабжается соответствующим кодом уровня
доступа. При трансформа-
ции логического
адреса в физический сравнивается
значение кода разрешенного уровня
доступа с фактически требуемым. При их
несовпадении работа программы прерывается.
При обращении к
виртуальной странице, не оказавшейся
в данный момент в опера-
тивной памяти,
возникает прерывание и управление
передается диспетчеру памяти, ко-
торый должен найти свободное место. Обычно предоставляется первая же свободная
страница. Если
свободной физической страницы нет,
то диспетчер памяти по одной из
вышеупомянутых
дисциплин замещения (LRU, LFU, FIFO, random)
определит страницу,
подлежащую
расформированию или сохранению
во внешней памяти. На ее место он и
разместит новую
виртуальную страницу, к которой было
обращение из задачи. Для ис-
пользования
дисциплин LRU и LFU в процессоре должны
быть реализованы соответст-
вующие аппаратные средства.
Если объем
физической памяти небольшой и даже
часто требуемые страницы не
удается разместить в оперативной памяти, возникает так называемая
«пробуксовка».
Другими словами,
пробуксовка
—
это ситуация, при которой загрузка нужной нам
стра-
ницы вызывает
перемещение во внешнюю память той
страницы, с которой мы тоже ак-
тивно
работаем. Чтобы не допускать этого,
можно увеличить
объем
оперативной па-
мяти, уменьшить количество параллельно выполняемых задач, либо попробовать
использовать более
эффективные
дисциплины
замещения.
В абсолютном
большинстве современных ОС используется
дисциплина замещения
страниц LRU как
самая эффективная. Так, именно эта
дисциплина используется в OS/2 и
Linux. Но в ОС от
компании Microsoft, например Windows NT,
разработчики, желая сде-
лать систему максимально независимой от аппаратных возможностей процессора, по-
шли на отказ от
этой дисциплины и применили правило
FIFO. А для того, чтобы хоть
как-нибудь
сгладить ее неэффективность, была
введена «буферизация»
тех страниц, ко-
торые должны быть записаны в файл подкачки на диск или просто расформированы.
Принцип буферизации прост. Прежде чем
замещаемая страница действительно будет
перемещена
во внешнюю память или просто расформирована,
она помечается как кан-
дидат на выгрузку.
Если в следующий раз произойдет обращение к странице, находя-
102
щейся в таком «буфере», то страница
никуда не выгружается и уходит в конец
списка
FIFO. В противном
случае страница действительно выгружается,
а на ее место в «буфе-
ре» попадает следующий
«кандидат». Величина такого «буфера» не
может быть боль-
шой, поэтому эффективность страничной реализации памяти в
Windows NT намного
ниже, чем у других
современных ОС и явление пробуксовки
проявляется при достаточ-
но
большом объеме оперативной памяти. В
системе Windows NT файл с выгруженными
виртуальными страницами
носит название PageFile.sys. Следует отметить,
что в совре-
менных версиях
Windows NT Microsoft все же реализовала дисциплину
замещения LRU.
Более подробно
распределение памяти в ОС семейства
Windows будет рассмотрено да-
лее.
Как и в случае с
сегментным способом организации
виртуальной памяти, странич-
ный способ приводит
к тому, что без специальных аппаратных
средств он будет сущест-
венно
замедлять работу вычислительной системы.
Поэтому обычно используется кэши-
рование страничных дескрипторов. Наиболее эффективным способом кэширования
является использование ассоциативного кэша. Именно такой ассоциативный кэш и
создан в
32-разрядных микропроцессорах
i80x86, начиная с i80386, который стал под-
держивать страничный
способ распределения памяти. В этих
микропроцессорах имеется
кэш на 32 страничных
дескриптора. Поскольку размер
страницы в них равен 4 Кбайт,
возможно быстрое обращение к 128 Кбайт
памяти.
Рассмотрев страничный способ организации памяти можно сделать следующие
выводы. Основным достоинством страничного способа, по
сравнению с рассмотрен-
ными
ранее, является минимально
возможная
фрагментация.
Так как на каждую за-
дачу может
приходиться по одной незаполненной
странице становится очевидным, что
память можно использовать достаточно
эффективно.
Недостатками способа является, во-первых, наличие существенных дополни-
тельных
накладных
расходов
для страничной трансляция виртуальной
памяти. В этом
случае таблицы страниц нужно тоже размещать в памяти.
Кроме этого, эти таблицы
нужно обрабатывать; именно с ними
работает диспетчер памяти.
Второй существенный недостаток страничной адресации заключается в том, что
программы разбиваются на
страницы случайно,
без учета логических взаимосвязей,
имеющихся в коде.
Это приводит к усложнению организации
разделения программных
модулей между
выполняющимися процессами. Для исправления
этого недостатка, пред-
ложен сегментно-страничный способ,
рассмотренный далее.
Несмотря на
вышеперечисленные недостатки все
современные ОС для ПК исполь-
зуют именно этот способ распределения
памяти.
4.2.6.
Сегментно-страничное
распределение
памяти
В данном способе
распределения памяти, как и в сегментном,
программа разбива-
ется
на логически законченные части —
сегменты и виртуальный адрес содержит
указа-
ние на
номер соответствующего сегмента. Вторая
составляющая виртуального адреса —
смещение относительно
начала сегмента состоит из двух полей:
виртуальной страницы
и индекса. Другими
словами, получается, что виртуальный
адрес теперь состоит из трех
компонентов: сегмент, страница, индекс.
Очевидно, что этот
способ организации виртуальной памяти
вносит еще большую
задержку доступа
к памяти. Необходимо сначала вычислить
адрес дескриптора сегмента
и прочитать его,
затем вычислить адрес элемента таблицы
страниц этого сегмента и из-
влечь из памяти
необходимый элемент, и уже только после
этого можно к номеру физи-
ческой страницы
приписать номер ячейки в странице
(индекс). Задержка доступа к ис-
комой
ячейке получается по крайней мере в
три раза больше, чем при простой прямой
103
адресации. Чтобы избежать этого вводится кэширование, причем кэш, как правило,
строится по ассоциативному принципу.
Главным достоинством сегментно-страничного способа является возмож-
ность
размещать
сегменты
в
памяти
целиком.
Сегменты разбиты на страницы, все
страницы конкретного сегмента обязательно загружаются в память. Это позволяет
уменьшить обращения
к отсутствующим страницам. Страницы
исполняемого сегмента
при этом, находятся
в памяти в случайных местах, так как
диспетчер памяти манипули-
рует страницами,
а не сегментами. Таким образом, наличие
сегментов облегчает реали-
зацию разделения программных модулей между параллельными процессами. В этом
случае
возможна и динамическая компоновка
задачи. Выделение же памяти страницами
позволяет минимизировать фрагментацию.
Главный недостаток этого способа
— еще большая потребность в вычисли-
тельных ресурсах. Сегментно-страничное распределение памяти достаточно сложно
реализовать,
используется оно редко, обычно в дорогих
и мощных вычислительных сис-
темах. Возможность реализации сегментно-страничного способа организации памяти
заложена и в семействе
32 разрядных микропроцессоров
i80x86, но вследствие слабой
аппаратной поддержки, трудностей при создании систем программирования и ОС, он
практически не
используется
в
ПК.
Алгоритмы распределения памяти – это важная часть операционных систем, включая Windows. Они отвечают за эффективное использование оперативной памяти компьютера. Распределение памяти осуществляется для выполнения различных задач, таких как запуск приложений, хранение данных и многие другие.
Операционная система Windows применяет несколько алгоритмов распределения памяти для обеспечения эффективной работы. Один из них это алгоритм динамического разделения памяти. Он основан на принципе выделения памяти по мере необходимости. Когда приложение запускается, операционная система выделяет необходимое количество памяти. При завершении работы приложения, эта память освобождается и становится доступной для других приложений.
Другой алгоритм, используемый в операционной системе Windows, это алгоритм виртуальной памяти. Виртуальная память представляет собой механизм, позволяющий операционной системе эффективно управлять памятью. Она позволяет приложениям использовать память, которой на самом деле нет в физической памяти компьютера, но которую можно «виртуализировать» из доступного дискового пространства. Это позволяет увеличить доступную память для приложений и обеспечить их более быструю работу.
Алгоритмы распределения памяти в Windows играют ключевую роль в обеспечении стабильной и эффективной работы операционной системы. Они позволяют оптимально использовать оперативную память компьютера и улучшить производительность приложений. Знание этих алгоритмов позволяет программистам и системным администраторам более эффективно работать с памятью в Windows и решать возникающие проблемы связанные с распределением ресурсов.
Содержание
- Распределение памяти в операционной системе Windows: понятие и роль алгоритмов
- Функции алгоритмов распределения памяти в операционной системе Windows
- Типы алгоритмов распределения памяти в операционной системе Windows
- Основные принципы работы алгоритмов распределения памяти в операционной системе Windows
- Ошибки и проблемы при работе алгоритмов распределения памяти в операционной системе Windows
Распределение памяти в операционной системе Windows: понятие и роль алгоритмов
В основе распределения памяти лежат алгоритмы, которые определяют, каким образом память будет выделена и используется процессами и программами. Алгоритмы решают такие вопросы, как управление памятью, выделение и освобождение блоков памяти, фрагментация и компактация памяти.
Один из наиболее распространенных алгоритмов распределения памяти в операционной системе Windows — это алгоритм выделения памяти с битовой картой. Для его работы создается структура данных — битовая карта, в которой каждому блоку памяти соответствует один бит. Такая карта позволяет отслеживать свободные и занятые блоки памяти и эффективно выделять свободные участки под новые процессы или программы.
Еще одним важным алгоритмом является алгоритм выделения памяти средствами списка свободных блоков. Он основан на использовании связного списка, в котором каждый элемент соответствует свободному блоку памяти. При поступлении запроса на выделение памяти, операционная система проходит по списку и ищет первый подходящий свободный блок.
Распределение памяти в операционной системе Windows играет решающую роль в эффективном использовании ресурсов компьютера. Правильные алгоритмы позволяют минимизировать фрагментацию памяти, управлять выделением и освобождением памяти и обеспечивать стабильную работу системы.
Функции алгоритмов распределения памяти в операционной системе Windows
Алгоритмы распределения памяти в операционной системе Windows выполняют важную роль в управлении ресурсами компьютера. Они позволяют оптимально использовать доступную память и обрабатывать запросы на выделение и освобождение памяти.
В Windows существует несколько основных алгоритмов распределения памяти:
- Алгоритм First Fit (первое соответствие) — данный алгоритм ищет первый блок памяти, который может удовлетворить запрос на выделение памяти. Он прост и быстр, но может приводить к фрагментации памяти.
- Алгоритм Best Fit (наилучшее соответствие) — данный алгоритм ищет наиболее подходящий блок памяти для запроса. Он позволяет минимизировать фрагментацию, но может быть более медленным, чем First Fit.
- Алгоритм Next Fit (следующее соответствие) — данный алгоритм продолжает поиск с последнего найденного блока памяти. Он компромисс между First Fit и Best Fit, и обычно дает неплохие результаты.
- Алгоритм Worst Fit (наихудшее соответствие) — данный алгоритм ищет наибольший свободный блок памяти для запроса. Он может быть полезен при работе с большими блоками памяти, но может приводить к большей фрагментации.
Каждый из алгоритмов имеет свои достоинства и недостатки, и выбор конкретного алгоритма зависит от конкретных требований и характеристик программы или системы, где будет использоваться.
В операционной системе Windows алгоритмы распределения памяти реализованы в ядре системы и доступны через соответствующие системные вызовы. Разработчики могут использовать эти функции для выделения и освобождения памяти в своих приложениях.
Типы алгоритмов распределения памяти в операционной системе Windows
Операционная система Windows использует различные алгоритмы для эффективного управления памятью, обеспечивая оптимальное распределение памяти между процессами. Вот несколько основных типов алгоритмов распределения памяти:
- Фиксированный размер блоков (Fixed-size allocation): В этом типе алгоритма память разделена на равные блоки определенного размера. Когда процесс запрашивает память, операционная система предоставляет ему один или несколько свободных блоков нужного размера. Этот тип алгоритма прост и эффективен, но может приводить к фрагментации памяти.
- Разделение памяти на страницы (Paging): В этом типе алгоритма память физически разделена на равные страницы фиксированного размера. Когда процесс запрашивает память, операционная система выделяет ему одну или несколько страниц. Этот тип алгоритма позволяет использовать виртуальную память и управлять ее обменом с диском.
- Сегментация (Segmentation): В этом типе алгоритма память разделена на логические сегменты, каждый из которых может иметь разные размеры. Когда процесс запрашивает память, операционная система выделяет ему один или несколько сегментов. Этот тип алгоритма позволяет эффективно использовать память, но может приводить к фрагментации.
- Сегментация с пагинацией (Segmentation with paging): Этот тип алгоритма комбинирует преимущества сегментации и пагинации. Память разделена на сегменты, а каждый сегмент физически разделен на страницы. Когда процесс запрашивает память, операционная система выделяет ему одну или несколько страниц внутри нужного сегмента.
Каждый из этих типов алгоритмов имеет свои особенности, преимущества и ограничения. Операционная система Windows выбирает наиболее подходящий тип алгоритма в зависимости от требований процесса, состояния системы и других факторов.
Управление памятью — важный аспект работы операционных систем, и выбор эффективного алгоритма распределения памяти играет критическую роль в обеспечении производительности и стабильности системы.
Основные принципы работы алгоритмов распределения памяти в операционной системе Windows
Алгоритмы распределения памяти в операционной системе Windows играют важную роль в эффективной управлении ресурсами компьютера. Они отвечают за то, как операционная система выделяет и освобождает память для различных процессов и задач.
Основная задача алгоритмов распределения памяти — оптимизировать использование доступной системе памяти и предоставить каждому процессу необходимый объем ресурса для выполнения задач. Для этого операционная система Windows использует несколько алгоритмов распределения памяти, таких как алгоритмы выполняющегося среднего, алгоритм отслеживания свободной памяти и алгоритм сдвига стека.
Алгоритм выполняющегося среднего (working-set algorithm) основан на концепции активности процессов. Он отслеживает активные и неактивные страницы памяти и освобождает неактивные страницы, чтобы освободить место для новых данных. Этот алгоритм позволяет операционной системе динамически регулировать объем выделенной памяти для каждого процесса в зависимости от его активности.
Алгоритм отслеживания свободной памяти (free memory tracking algorithm) направлен на поддержание достаточного объема свободной памяти для выполнения операций в системе. Он следит за состоянием доступной памяти и если ее объем уменьшается до критического значения, алгоритм предпринимает меры для освобождения памяти, например, путем перемещения некритических данных в виртуальную память или освобождения неиспользуемых ресурсов.
Алгоритм сдвига стека (stack shifting algorithm) используется для эффективного управления стековой памятью. Он освобождает память, занятую стеками, когда они больше не нужны, и выделяет новую память для растущих стеков. Этот алгоритм помогает операционной системе эффективно использовать память, выделяемую для выполнения программ и уменьшить вероятность переполнения стеков.
Алгоритм | Принцип |
---|---|
Алгоритм выполняющегося среднего | Отслеживает активные и неактивные страницы памяти для эффективного выделения и освобождения памяти |
Алгоритм отслеживания свободной памяти | Поддерживает достаточный объем свободной памяти в системе для выполнения операций |
Алгоритм сдвига стека | Управляет стековой памятью, освобождая и выделяя память для стеков |
Эффективное распределение памяти в операционной системе Windows позволяет работать множеству процессов и задачам, находящихся в памяти, без необходимости добавления нового физического оборудования. Алгоритмы распределения памяти играют важную роль в обеспечении стабильной и высокой производительности операционной системы.
Ошибки и проблемы при работе алгоритмов распределения памяти в операционной системе Windows
Работа с алгоритмами распределения памяти в операционной системе Windows может столкнуться с различными проблемами и ошибками, которые могут привести к снижению производительности или даже к аварийному завершению работы программ. Некоторые из наиболее распространенных проблем включают:
1. Утечка памяти. Это состояние, при котором программа неправильно освобождает память после ее использования, что приводит к постепенному истощению доступной оперативной памяти. Утечка памяти может привести к замедлению работы системы и даже к ее сбою.
2. Фрагментация памяти. При работе с динамической памятью в Windows происходит ее фрагментация, то есть разделение на небольшие блоки, которые могут быть разбросаны по всей доступной памяти. Фрагментация памяти может привести к увеличению времени доступа к данным и увеличению объема используемой памяти.
3. Неправильное выделение памяти. При использовании алгоритмов распределения памяти могут возникать ошибки в выделении необходимого объема памяти для выполнения конкретной задачи. Это может приводить к появлению ошибок и непредсказуемому поведению программ.
4. Синхронизация доступа к памяти. В многопоточных приложениях возникает необходимость синхронизации доступа к общей памяти. Неправильная синхронизация может привести к гонкам данных и ошибкам в работе программы.
5. Ограничение доступной памяти. В операционной системе Windows существуют ограничения на доступную память для каждого процесса. При достижении этих ограничений могут возникать ошибки и сбои в работе программ.
Все эти проблемы и ошибки требуют тщательного исследования и корректировки алгоритмов распределения памяти в операционной системе Windows. Разработчики программ должны уделять внимание созданию эффективных и надежных алгоритмов, а также следить за правильным использованием и освобождением памяти при выполнении задач.
Практическое
занятие №3
Тема: Управление
памятью
Цель работы: Приобрести практические навыки использования системных программ для
получения информации о распределении памяти в вычислительной памяти.
Ход работы
1. Ознакомиться с краткими
теоретическими сведениями.
2. Выполнить задания.
3. Ответить на контрольные
вопросы.
4. Оформить отчет.
Теоретическая часть
Память считается не менее важным и
интересным ресурсом вычислительной системы, чем процессорное время. А поскольку
существует несколько видов памяти, каждый из них может рассматриваться как
самостоятельный ресурс, характеризующийся определенными способами разделения.
Оперативная память может делиться
и одновременно (то есть в памяти одновременно может располагаться несколько
задач или, по крайней мере, текущих фрагментов, участвующих в вычислениях), и
попеременно (в разные моменты оперативная память может предоставляться для
разных вычислительных процессов). В каждый конкретный момент времени процессор
при выполнении вычислений обращается к очень ограниченному числу ячеек
оперативной памяти. С этой точки зрения желательно память выделять для возможно
большего числа параллельно исполняемых задач. С другой стороны, как правило,
чем больше оперативной памяти может быть выделено для конкретного текущего вычислительного
процесса, тем лучше будут условия его выполнения, поэтому проблема эффективного
разделения оперативной памяти между параллельно выполняемыми вычислительными
процессами является одной из самых актуальных.
Внешняя память тоже является
ресурсом, который часто необходим для выполнения вычислений. Когда говорят о
внешней памяти (например, памяти на магнитных дисках), то собственно память и
доступ к ней считаются разными видами ресурса. Каждый из этих ресурсов может
предоставляться независимо от другого. Но для полноценной работы с внешней
памятью необходимо иметь оба этих ресурса. Собственно, внешняя память может
разделяться и одновременно, а вот доступ к ней всегда разделяется попеременно.
Информацию о параметрах разных
видов памяти в ОС MS Windows можно получить с помощью
Диспетчера задач.
Диспетчер задач позволяет
просматривать общее использование памяти на вкладке Быстродействие, где
отображается информация в трех разделах:
1) в
разделе Выделение памяти содержатся три статистических параметра
виртуальной памяти:
а) Всего — это общий объем
виртуальной памяти, используемой как приложениями, так и ОС;
б) Предел — объем
доступной виртуальной памяти;
в) Пик — наибольший объем
памяти, использованный в течение сессии с момента последней загрузки;
2) в
разделе Физическая память содержатся параметры, несущие информацию о
текущем состоянии физической памяти машины, которая не имеет отношения к файлу
подкачки:
а) параметр Всего — это
объем памяти, обнаруженный ОС на компьютере;
б) Доступно — отражает
память, доступную для использования процессами. Эта величина не включает в себя
память, доступную приложениям за счет файла подкачки. Каждое приложение требует
определенный объем физической памяти и не может использовать только ресурсы
файла подкачки;
в) системный кэш – объем физической
памяти, доступный кэш-памяти системы и оставленный ОС после удовлетворения
своих потребностей;
3) в
разделе Память ядра — отображается информация о потребностях компонентов
ОС, обладающих наивысшим приоритетом. Параметры этого раздела отображают
потребности ключевых служб ОС:
а) Всего — объем
виртуальной памяти, необходимый ОС;
б) Выгружаемая —
информацию об общем объеме памяти, использованной системой за счет файла
подкачки;
в) Невыгружаемая — объем
физической памяти, потребляемой ОС.
С помощью Диспетчера задач можно
узнать объемы памяти, используемые процессами. Для этого перейти на вкладку Процессы,
которая показывает список исполняемых процессов и занимаемую ими память, в том
числе физическую память, пиковое, максимально использование памяти и
виртуальную память. Информация в Диспетчере задач не является полной, а именно:
— в
окне Диспетчера задач представлены процессы, зарегистрированные в Windows, не включены драйверы устройств, некоторые
системные службы;
— требования
к памяти отражают текущее состояние процесса (объемы памяти, занимаемые
приложениями в текущий момент);
— поскольку
не выводятся временные характеристики, то нет возможности отследить ее
изменения.
Утилита TaskList доставляет более обширную информацию по сравнению с Диспетчером задач.
Запускается утилита из окна командной строки.
Операционные системы семейства Windows
в Служебных программах содержат программу Сведения о
системе, с помощью которой можно получить сведения об основных
характеристиках организации памяти в компьютере:
— полный
объем установленной в компьютере физической памяти;
— общий
объем виртуальной памяти и доступной (свободной) в данный момент времени
виртуальной памяти;
— размещение
и объем файла подкачки.
Практические задания
Задание 1. Щелкните на кнопке Ресурсы аппаратуры, а затем на кнопке Память,
и получите сведения об использовании физической памяти аппаратными компонентами
компьютера.
Задание 2. Изменение размера файла подкачки.
Файл подкачки — это область жесткого диска, используемая Windows для хранения данных оперативной памяти. Он создает иллюзию, что система
располагает большим объемом оперативной памяти, чем это есть на самом деле. По
умолчанию файл подкачки удаляется системой после каждого сеанса работы и
создается в процессе загрузки ОС. Размер файла подкачки постоянно меняется по
мере выполнения приложений и контролируется ОС.
Для самостоятельной установки
размера файла подкачки нужно выполнить следующую последовательность действий:
а) щелкнуть правой кнопкой мыши
по значку Мой компьютер и выбрать в контекстном меню строку Свойства;
б) перейти на вкладку Дополнительно
и нажать кнопку Параметры в рамке Быстродействие;
в) в появившемся окне Параметры
быстродействия нажать кнопку Изменить.
Предварительно следует выбрать
принцип распределения времени процессора: для оптимизации работы программ
(если это пользовательский компьютер), или служб, работающих в фоновом
режиме (если это сервер). Кроме того, следует задать режим
использования памяти: для пользовательского компьютера — оптимизировать
работу программ, для сервера — системного кэша.
Основное правило — при небольшом
объеме оперативной памяти файл подкачки должен быть достаточно большим. При
большом объеме оперативной памяти (512 Мбайт) файл подкачки можно уменьшить.
Можно установить Исходный размер файла подкачки, равный размеру
физической памяти, а Максимальный размер не более двух размеров
физической памяти.
После этого нажмите кнопку Задать
и убедитесь, что новое значение файла подкачки установлено.
Щелкните на кнопке ОК.
Выйдет сообщение, что данное изменение требует перезагрузки компьютера.
Нажмите ОК.
Задание 3. Используя командную строку, получите отчеты о распределении памяти в
системе с помощью команд
wmic os get FreePhysicalMemory
wmic os get FreeSpaceInPagingMemory
wmic
os
get
FreeVirtualMemory
wmic os get MaxProcessMemorySize
wmic os get SizeStoredInPagingFiles
wmic os get TotalSwapSpaceSize
wmic os get TotalVirtualMemorySize
wmic os get TotalVisibleMemorySize
Просмотрите и проанализируйте
отчеты о распределении памяти всеми указанными командами. Запишите, какую
информацию выводит каждая из команд.
Содержание
отчета
Тема, цель, оборудование, порядок
выполнения заданий, ответы на контрольные вопросы, вывод.
Контрольные вопросы
1. Зачем нужна оперативная память компьютеру?
2. Что такое виртуальная память, ее назначение.
3. Какие алгоритмы распределения памяти использует современная ОС Windows, а какие ОС Linux?
Подсистема работы с оперативной памятью в Linux — достаточно многогранная конструкция. Чтобы разобраться в её деталях нужно целенаправленно погрузиться в тему, с обязательным чтением исходников ядра, но это нужно не каждому. Для разработки и эксплуатации серверного программного обеспечения важно иметь хотябы базовое предстваление о том, как она работает, но меня не перестает удивлять насколько небольшая доля людей им обладает. В этом посте я постараюсь кратко пробежаться по основным вещам, без понимания которых на мой взгляд очень легко натворить глупостей.
Какая бывает память?
Физическая и виртуальная
Начнем издалека. В спецификации любого компьютера и в частности сервера непременно числится надпись «N гигабайт оперативной памяти» — именно столько в его распоряжении находится физической памяти.
Задача распределения доступных ресурсов между исполняемым программным обеспечением, в том числе и физической памяти, лежит на плечах операционной системы, в нашем случае Linux. Для обеспечения иллюзии полной независимости, она предоставляет каждой из программ свое независимоевиртуальное адресное пространство и низкоуровневый интерфейс работы с ним. Это избавляет их от необходимости знать друг о друге, размере доступной физической памяти и текущей её занятости. Адреса в виртуальном пространстве процессов называют логическими.
Для отслеживания соответствия между физической и виртуальной памятью ядро Linux использует иерархический набор структур данных в своей служебной области физической памяти (только оно работает с ней напрямую), а также специализированные аппаратные контуры, которые в совокупности называют MMU.
Следить за каждым байтом памяти в отдельности было бы накладно, по-этому ядро оперирует достаточно большими блоками памяти — страницами, типовой размер которых составляет 4 килобайта.
Также стоит упомянуть, что на аппаратном уровне как правило есть поддержка дополнительного уровня абстракции в виде «сегментов» оперативной памяти, с помощью которых можно разделять программы на части. В отличии от других операционных систем, в Linux она практически не используется — логический адрес всегда совпадает с линейным (адресом внутри сегмента, которые сконфигурированы фиксированным образом).
Файловая и анонимная
У приложений существует много способов выделить себе память для тех или иных нужд. Высокоуровневые языки программирования и библиотеки часто прячут от разработчиков какой из них в реальности использовался и другие детали (хотя их всегда можно «раскусить» с помощью strace
). Если углубляться в особенности каждого доступного варианта, эта статья быстро бы превратилась в книгу. Вместо этого предлагаю разделить их на две, на мой взгляд, крайне важные группы по тому, какую память они выделяют:
- Файловой памяти однозначно соответствует какой-либо файл или его часть в файловой системе. Первым делом в ней как правило находится исполняемый код самой программы. Для прикладных задач можно запросить отображение файла в виртуальное адресное пространство процесса с помощью системного вызова
mmap
— после чего с ним можно работать как с любой другой областью памяти без явного чтения/записи, что будет при этом происходить с данными в файловой системе и что будут видеть другие процессы «отобразившие» этот же файл зависит от настроек. - Любую другую выделенную память называют анонимной, так как ей не соответствует никакой файл, которые как известно именованы. Сюда попадают как переменные на стеке, так и области, выделенные с помощью функций вроде
malloc
(к слову, за сценой для выделения больших блоков памяти они обычно тоже используютmmap
с особым набором настроек, а для всего остального —brk/sbrk
или выдают ранее освобожденную память).
На первый взгляд отличия не выглядят чем-то особенным, но тот факт, что области файловой памяти именованы, позволяет операционной системе экономить физическую память, порой очень значительно, сопоставляя виртуальные адреса нескольких процессов, работающих с одним и тем же файлом, одной физической странице в памяти. Это работает прозрачно, начиная от кода запущенных нескольких копий приложений, заканчивая специально сконструированными под эту оптимизацию систем.
Вытесняемая и нет
Суммарный объем используемой виртуальной памяти всех программ запросто может превышать объем доступной физической памяти. При этом в каждый конкретный момент времени приложениями может использоваться лишь небольшое подмножество хранимых по виртуальным адресам данных. Это означает, что операционная система может откладывать не используемые в данный момент данные из оперативной памяти на жесткий диск («вытесняя»» их из памяти), а затем при попытке к этим данным обратиться — скопировать обратно в физическую оперативную память. Этот механизм официально называется major page fault, но под просто page fault как правило подразумевают тоже её, так как minor page fault мало кого заботит (отличие в том, что в случае minor ядру удается найти запрашиваемые данные уже загруженными в память с какой-то другой целью и обращения к диску в итоге не происходит).
На время восстановления запрашиваемых приложением данных его выполнение прерывается и управление передается ядру для выполнения соответствующей процедуры. Время, которое потребуется, чтобы приложение смогло продолжить свою работу, напрямую зависит от типа используемого жесткого диска:
- Прочитать 4Кб данных с обычного серверного жесткого диска 7200rpm занимает порядка 10 мс, при хорошем стечении обстоятельств чуть меньше.
- Если вытесненных страниц оказывается много, запросто могут набегать заметные доли секунды (как условным пользователям, так и на внутренних приборах, в зависимости от задачи).
- Особенно опасны циклические pagefaults, когда есть две или более регулярно используемые области памяти, которые вместе не помещаются в физическую память, по-этому бесконечно вытесняют друг друга туда-обратно.
- При этом диск вынужден делать честный seek, что само по себе тоже может быть не кстати. Например, если с этим же диском работает какая-либо база данных.
- Если используется SSD, то ситуация несколько более радужная — из-за отсутствия механического движения аналогичная операция занимает примерно на порядок меньше, около 1 мс или её доли, в зависимости от типа и конкретной модели диска. Но годы идут, а SSD так и остаются нишевым компромиссным продуктом по цене-объему.
- А теперь для сравнения: если бы страница уже была в памяти, то при обращении к ней счет шел бы на сотни наносекунд. Это почти на 4 порядка быстрее, чем pagefault, даже на SSD.
Стоит отметить, что с точки зрения приложения всё это прозрачно и является внешним воздействием, то есть может происходить в самый не подходящий, с точки зрения решаемой им задачи, момент.
Думаю понятно, что приложения, которым важна высокая производительность и стабильное время отклика, должны избегать pagefault’ов всеми доступными методами, к ним и перейдем.
Методы управления подсистемой памяти
swap
С файловой памятью всё просто: если данные в ней не менялись, то для её вытеснения делать особо ничего не нужно — просто перетираешь, а затем всегда можно восстановить из файловой системы.
С анонимной памятью такой трюк не работает: ей не соответствует никакой файл, по-этому чтобы данные не пропали безвозвратно, их нужно положить куда-то ещё. Для этого можно использовать так называемый «swap» раздел или файл. Можно, но на практике не нужно. Если swap выключен, то анонимная память становится невытесняемой, что делает время обращения к ней предсказуемым.
Может показаться минусом выключенного swap, что, например, если у приложения утекает память, то оно будет гарантированно зря держать физическую память (утекшая не сможет быть вытеснена). Но на подобные вещи скорее стоит смотреть с той точки зрения, что это наоборот поможет раньше обнаружить и устранить ошибку.
mlock
По-умолчанию вся файловая память является вытесняемой, но ядро Linux предоставляет возможность запрещать её вытеснение с точностью не только до файлов, но и до страниц внутри файла.
Для этого используется системный вызов mlock
на области виртуальной памяти, полученной с помощью mmap
. Если спускаться до уровня системных вызовов не хочется, рекомендую посмотреть в сторону консольной утилиты vmtouch
, которая делает ровно то же самое, но снаружи относительно приложения.
Несколько примеров, когда это может быть целесообразно:
- У приложения большой исполняемый файл с большим количеством ветвлений, некоторые из которых срабатывают редко, но регулярно. Такого стоит избегать и по другим причинам, но если иначе никак, то чтобы не ждать лишнего на этих редких ветках кода — можно запретить им вытесняться.
- Индексы в базах данных часто физически представляют собой именно файл, с которым работают через
mmap
, аmlock
нужен чтобы минимизировать задержки и число операций ввода-вывода на и без того нагруженном диске(-ах). - Приложение использует какой-то статический словарь, например с соответствием подсетей IP-адресов и стран, к которым они относятся. Вдвойне актуально, если на одном сервере запущено несколько процессов, работающих с этим словарем.
OOM killer
Перестаравшись с невытесняемой памятью не трудно загнать операционную систему в ситуацию, когда физическая память кончилась, а вытеснять ничего нельзя. Безысходной она выглядит лишь на первый взгляд: вместо вытеснения память можно освободить.
Происходит это достаточно радикальными методами: послуживший названием данного раздела механизм выбирает по определенному алгоритму процесс, которым наиболее целесообразно в текущий момент пожертвовать — с остановкой процесса освобождается использовавшаяся им память, которую можно перераспределить между выжившими. Основной критерий для выбора: текущее потребление физической памяти и других ресурсов, плюс есть возможность вмешаться и вручную пометить процессы как более или менее ценные, а также вовсе исключить из рассмотрения. Если отключить OOM killer полностью, то системе в случае полного дефицита ничего не останется, как перезагрузиться.
cgroups
По-умолчанию все пользовательские процессы наравне претендуют на почти всю физически доступную память в рамках одного сервера. Это поведение редко является приемлемым. Даже если сервер условно-однозадачный, например только отдает статические файлы по HTTP с помощью nginx, всегда есть какие-то служебные процессы вроде syslog или какой-то временной команды, запущенной человеком. Если же на сервере одновременно работает несколько production процессов, например, популярный вариант — подсадить к веб-серверу memcached, крайне желательно, чтобы они не могли начать «воевать» друг с другом за память в случае её дефицита.
Для изоляции важных процессов в современных ядрах существует механизм cgroups, c его помощью можно разделить процессы на логические группы и статически сконфигурировать для каждой из групп сколько физической памяти может быть ей выделено. После чего для каждой группы создается своя почти независимая подсистема памяти, со своим отслеживанием вытеснения, OOM killer и прочими радостями.
Механизм cgroups намного обширнее, чем просто контроль за потреблением памяти, с его помощью можно распределять вычислительные ресурсы, «прибивать» группы к ядрам процессора, ограничивать ввод-вывод и многое другое. Сами группы могут быть организованы в иерархию и вообще на основе cgroups работают многие системы «легкой» виртуализации и нынче модные Docker-контейнеры.
Но на мой взгляд именно контроль за потреблением памяти — самый необходимый минимум, который определенно стоит настроить, остальное уже по желанию/необходимости.
NUMA
В многопроцессорных системах не вся память одинакова. Если на материнской плате предусмотрено N
процессоров (например, 2 или 4), то как правило все слоты для оперативной памяти физически разделены на N
групп так, что каждая из них располагается ближе к соответствующему ей процессору — такую схему называют NUMA.
Таким образом, каждый процессор может обращаться к определенной 1/N
части физической памяти быстрее (примерно раза в полтора), чем к оставшимся (N-1)/N
.
Ядро Linux самостоятельно умеет это всё определять и по-умолчанию достаточно разумным образом учитывать при планировании выполнения процессоров и выделении им памяти. Посмотреть как это все выглядит и подкорректировать можно с помощью утилиты numactl
и ряда доступных системных вызовов, в частности get_mempolicy
/set_mempolicy
.
Операции с памятью
Есть несколько тем, с которыми в реальности сталкиваются лишь C/C++ разработчики низкоуровневых систем, и не мне им про это рассказывать. Но даже если напрямую с этим не сталкиваться на мой взгляд полезно в общих чертах знать, какие бывают нюансы:
- Операции, работающие с памятью:
- В большинстве своем не атомарны (то есть другой поток может их «увидеть» на полпути), без явной синхронизации атомарность возможна только для блоков памяти не больше указателя (т.е. как правило 64 бита) и то при определенных условиях.
- В реальности происходят далеко не всегда в том порядке, в котором они написаны в исходном коде программы: процессоры и компиляторы на правах оптимизации могут менять их порядок, как считают нужным. В случае многопоточных программ эти оптимизации часто могут приводить к нарушению логики их работы. Для предотвращения подобных ошибок разработчики могут использовать специальные инструменты, в частности барьеры памяти — инструкции, которые запрещают переносить операции с памятью между частями программы до неё и после.
- Новые процессы создаются с помощью системного вызова
fork
, который порождает копию текущего процесса (чтобы запустить другую программу в новом процессе существует отдельное семейство системных вызовов —exec
), у которого виртуальное пространство практически полностью идентично родительскому, что не потребляет дополнительной физической памяти до тех пор, пока тот или другой не начнут его изменять. Этот механизм называетсяcopy on write
и на нем можно играть для создания большого числа однотипных независимых процессов (например, обрабатывающих какие-то запросы), с минимумом дополнительных расходов физической памяти — в некоторых случаях так жить удобнее, чем с многопоточным приложением. - Между процессором и оперативной памятью находится несколько уровней кешей, обращение к которым ещё на порядки быстрее, чем к оперативной памяти. К самому быстрому — доли наносекунд, к самому медленному единицы наносекунд. На особенностях их работы можно делать микро оптимизации, но из высокоуровневых языков программирования до них толком не добраться.
Итого
Подсистему памяти в Linux нельзя бросать на произвол судьбы. Как минимум, стоит следить за следующими показателями и вывести на приборы (как суммарно, так и по процессам или их группам):
- Скорость возникновения major page faults;
- Срабатывания OOM killer;
- Текущий объем использования физической памяти (это число обычно называют RSS, не путать с одноименным форматом для публикации текстового контента).
В штатном режиме все три показателя должны быть стабильны (а первые два — близки к нулю). Всплески или плавный рост стоит рассматривать как аномалию, в причинах которой стоит разобраться. Какими методами — надеюсь я показал достаточно направлений, куда можно по-копать.
- Статья написана с ориентиром на современные Debian-like дистрибутивы Linux и физическое оборудование с двумя процеcсорами Intel Xeon. Общие принципы ортогональны этому и справедливы даже для других операционных систем, но вот детали могут сильно разниться даже в зависимости от сборки ядра или конфигурации.
- У большинства упомянутых выше системных вызовов, функций и команд есть
man
, к которому рекомендую обращаться за подробностями об их использовании и работе. Если под рукой нет linux-машины, где можно набратьman foo
— они обычно легко ищутся с таким же запросом. - Если есть желание углубиться в какую-либо из затронутых вскользь тем — пишите об этом в комментариях, любая из них может стать заголовком отдельной статьи.
P.S.
На последок ещё раз повторю цифры, которые настоятельно рекомендую запомнить:
- 0.0001 мс (100 нс) — обращение к оперативной памяти
- 0.1-1 мс (0.1-1 млн. нс) — обращение к SSD при major pagefault, на 3-4 порядка дороже
- 5-10 мс (5-10 млн. нс) — обращение к традиционному жесткому диску при pagefault, ещё на порядок дороже
// мс — миллисекунды, нс — наносекунды.