ЭКЗАМЕНАЦИОННЫЙ БИЛЕТ № 15. 1. Страничная и сегментная адресация
1. Страничная и сегментная адресация
Сегментная адресация памяти — схема логической адресации памяти компьютера в архитектуре x86. Линейный адрес конкретной ячейки памяти, который в некоторых режимах работы процессора будет совпадать с физическим адресом, делится на две части: сегмент и смещение. Сегментомназывается условно выделенная область адресного пространства определённого размера, а смещением — адрес ячейки памяти относительно начала сегмента. Базой сегмента называется линейный адрес (адрес относительно всего объёма памяти), который указывает на начало сегмента в адресном пространстве. В результате получается сегментный (логический) адрес, который соответствует линейному адресу база сегмента+смещение и который выставляется процессором на шину адреса.
Селектором называется число (в x86 — 16-битное), однозначно определяющее сегмент. Селектор загружается в сегментные регистры.
В реальном и защищённом режимах x86-процессора функционирование сегментной адресации отличается.
В реальном режиме процессора всё адресное пространство делится на одинаковые сегменты размером от 16 байт до 65536 байт. Начало каждого последующего сегмента (так называемая База сегмента) смещена относительно базы предыдущего на минимальный размер сегмента, то есть на 16 байт(т. н. параграф). Таким образом, сегменты могут частично перекрывать друг друга. (Например, байт 17 сегмента 2 — это также и байт сегмента 3, и байт сегмента 1.)
Селектор 16-разрядный и задаёт номер сегмента. Учитывая, что сегменты следуют друг за другом с постоянным интервалом в 24=16 байт, очень легко выяснить линейный адрес сегмента, умножая его на 16.
В защищённом режиме процессора адресное пространство задачи делится на сегменты различных размеров с различными базами. Для определения базы и размера сегментов служат дескрипторы сегментов, хранящиеся в дескрипторных таблицах (GDT и LDT).
Здесь сегменты № 3 и № 11 указывают на одну и ту же область и являются псевдонимами (алиасными от англ. Alias). Сегмент № 7 охватывает сегменты № 1, № 2, № 3 и № 11. Сегмент № 5 указывает на GDT, позволяя её изменять (это никак не относится к GDT — её настоящий дескриптор хранится в регистре GDTR (показан жёлтым)). Адресация через локальную таблицу дескрипторов (LDT) происходит аналогично.
Селектор также 16-разрядный, но делится на три части: RPL (биты 0-1), TI (бит 2) и номер дескриптора ([биты 3-15).
· RPL — см.: Сегментная защита памяти;
· TI определяет дескрипторную таблицу (GDT или LDT при 0 или 1 соответственно), из которой выбирается дескриптор;
· номер дескриптора — порядковый номер в дескрипторной таблице. Так как размер дескриптора равен восьми байтам, а номер дескриптора начинается с третьего бита, то можно адресовать дескриптор (если надо), просто обнулив RPL и TI.
2. Учет пользователей объектов ядра. Защита.
3. Кучи. Управление памятью кучи.
Кучи
Win32 поддерживает области памяти в виде куч (heaps). Процесс может
содержать несколько куч, и из них разработчик выделяет память.
Часто достаточно одной кучи, но по приведенным ниже причинам
используется и много куч. Если достаточно одной кучи, просто используйте
функции библиотеки С для управления памятью (malloc, free, calloc,
realloc).
Кучи являются объектами Win32, поэтому они имеют дескрипторы.
Дескриптор кучи необходимо знать, когда выделяется память. Каждый процесс имеет собственную кучу, принятую по умолчанию, к которой обращается функция malloc, а следующая функция возвращает ее дескриптор.
HANDLE GetProcessHeap (VOID)
Возвращаемое значение: дескриптор кучи процесса; NULL в случае неудачи.
Заметим, что в данном случае для указания на ошибку возвращается
значение NULL, а не INVALID_HANDLE_VALUE, как в функции CreateFile.
Программа также может создавать особые кучи. Это относится к тем
случаям, когда необходимы отдельные кучи для резервирования отдельных
структур данных. Ниже описаны преимущества отдельных куч.
• Справедливость распределения. Ни один поток не может получить
больше памяти, чем зарезервировано для его кучи. В частности, утечка памяти, вызванная программой, не учитывающей свободные и больше не используемые элементы данных, повлияет только на поток процесса.
• Многопоточное быстродействие. Благодаря предоставлению каждому
потоку отдельной кучи соревнование между потоками сокращается, что может существенно повысить быстродействие.
• Эффективность резервирования. Резервирование элементов данных
фиксированного размера в маленькой куче значительно более эффективно, чем резервирование множества элементов разных размеров в одной большой куче.
Также снижается фрагментация памяти. Кроме того, выделение каждому
потоку отдельной кучи упрощает синхронизацию, что дает дополнительный
выигрыш.
• Эффективность освобождения памяти. Вся куча и все структуры данных,
размещенные в ней, могут быть освобождены одним вызовом функции. При
этом также освобождается и вся выделенная, но потерянная память в куче.
• Эффективная локализация ссылок. Расположение структуры данных в
маленькой куче гарантирует, что ее элементы будут сосредоточены в
сравнительно небольшом количестве страниц, что потенциально сокращает
страничные ошибки при обработке структуры.
Ценность этих преимуществ изменяется в зависимости от приложения, и
многие программисты будут пользоваться только кучей процесса и
библиотекой С. В любом случае, следующие две функции создают и
уничтожают кучи.
Начальный размер кучи, который может быть нулевым и всегда округляется
до числа, кратного размеру страницы, определяет объем физической памяти (в файле подкачки), первоначально отведенный куче. Когда программа выходит за границы начального размера, автоматически выделяются дополнительные страницы вплоть до максимального предела. Так как файл подкачки является ограниченным ресурсом, отложенное выделение удобно в случаях, когда размер кучи заранее не известен. Ненулевое значение переменной dwMaximumSize определяет предел для динамического повышения объема кучи. Куча процесса также будет динамически расти.
HANDLE HeapCreate ( DWORD flOptions,
SIZE_T dwInitialSize, SIZE_T dwMaximumSize);
Возвращаемое значение: дескриптор кучи или NULL в случае ошибки.
Два поля размера имеют тип SIZE _ T , а не DWORD . Тип SIZE _ T определен таким образом, что может быть 32-разрядным или 64-разрядным беззнаковым целым, в зависимости от флагов компилятора (_WIN32 или _WIN64). Тип SIZE _ T был введен для обеспечения возможности перехода к WIN64. Переменная flOptions является комбинацией двух флагов.
• HEAP_GENERATE_EXCEPTIONS— при этом значении неудачные
попытки выделения памяти вызывают исключения, которые будут обработаны структурным обработчиком исключений (Structured Exception Handler — SEH). Функция HeapCreate сама по себе не вызывает исключений; если этот флаг установлен, исключение вызывают при неудаче такие функции, как HeapAlloc.
• HEAP_NO_SERIALIZE — установка этого флага в некоторых случаях
позволяет получить небольшое повышение быстродействия. Следует сказать также несколько слов о dwMaximumSize.
• Если значение dwMaximumSize. не равно нулю, виртуальное адресное
пространство выделяется и в том случае, когда весь указанный объем выделить невозможно. Это максимальный размер кучи, которая называется невозрастающей. Данная опция ограничивает размер кучи, возможно, для достижения справедливости распределения ресурсов, о которой упоминалось ранее.
• С другой стороны, если значение dwMaximumSize. — нуль, то куча
является возрастающей за пределы начального размера. Эта граница
определяется доступным виртуальным адресным пространством, часть
которого может быть предоставлена другим кучам, и пространством файла
подкачки.
Отметим, что кучи не имеют атрибутов безопасности, так как они
недоступны извне процесса.
Для уничтожения всей кучи используйте функцию HeapDestroy. Это
другое исключение из общего правила о том, что функция CloseHandle
применяется для удаления всех ненужных дескрипторов.
BOOL HeapDestroy (HANDLE hHeap);
Переменная hHeap должна указывать на кучу, созданную функцией
HeapCreate. Будьте осторожны, не уничтожьте кучу процесса (полученную
функцией GetProcessHeap). Уничтожение кучи освобождает пространство
виртуальной памяти и физическую память в файле подкачки. Разумеется,
грамотно спроектированные программы должны освобождать кучи, которые
больше не используются.
Уничтожение кучи — это также быстрый способ освободить структуру
данных без необходимости уничтожать каждый элемент отдельно, хотя
экземпляры объектов С++ таким образом не будут уничтожены, поскольку их
деструкторы не вызываются. Уничтожение кучи имеет ряд преимуществ.
1. Нет необходимости писать код для поэлементного обхода структуры.
2. Нет необходимости освобождать каждый отдельный элемент.
3. Система не тратит время на поддержку кучи с того момента, когда все
структуры данных освобождаются одним системным вызовом.