Сегментная адресация памяти в 32-х разрядных системах
Доброго времени суток, друзья! Сегодня будет еще одно небольшое включение в операционные системы и системное программирование, но больше с теоретической стороны. Причиной написания этой заметки послужил дефицит и разрозненность статей в интернете, которые описывали бы способы адресации оперативной памяти. Можно найти в отдельности принцип работы сегментной модели, кучу статей про страничную память, и, если очень сильно постараться, то и про сегментно-страничную что-нибудь отыщется. А она в свою очередь сейчас является основной в подавляющем большинстве операционных систем.
Поэтому я пришел к выводу, что нужно собраться и написать три заметки про организацию памяти. Встречайте первую из них: о сегментной адресации памяти.
Под словом «память» я буду подразумевать, конечно же, оперативную память компьютера. О жестком диске речь будет идти только в момент упоминания подкачки.
Деление памяти на сегменты
Сегментная модель является логичным продолжением так называемой «неразрывной», в которой у каждого процесса была информация о начале его участка в общей физической памяти и смещение внутри этого участка для каждого байта данных, все. В старой модели один процесс мог спокойно залезть в память другого, перетереть там все данные, а потом сбегать до системных адресов и бахнуть компьютер. С целью остановить беспредел были придуманы сегменты.
Сегмент представляет из себя участок физической памяти определенного размера(не фиксированного, это важно!). Соответственно вся память делится на сегменты, и любой процесс для того, чтобы обратиться к участку памяти должен знать два числа: номер сегмента и смещение от начала сегмента. Вместе они образуют логический(его еще называют сегментный) адрес, который затем будет преобразован уже в настоящий физический адрес в оперативной памяти.
Селектор сегмента
Первое из двух чисел в логическом адресе называется селектором, он 16-ти разрядный, среди которых старшие 13 бит являются индексом(порядковым номером) сегмента в таблице дескрипторов. Остальные три бита делятся следующим образом: 1 бит называется TI и указывает на тип дескрипторной таблицы(0 — глобальная, 1 — локальная), еще два бита отданы под RPL — запрашиваемый уровень привилегий.
При обращении к нужному дескриптору RPL будет сравниваться с DPL этого сегмента. Как вы уже могли догадаться, DPL(Descriptor privilege level) это уровень привилегий дескриптора.
Кстати, о доступе. Благодаря делению памяти на сегменты появилась возможность разграничивать права доступа и пресекать подлые попытки переписать чужую память, это стало настоящим прорывом. Всего существует 4 уровня привилегий: 0-2 соответствуют супервизору, при этом на нулевом уровне доступны самые привилегированные действия, 3 — уровень пользователя(пользовательских программ).
Глобальная таблица дескрипторов — GDT
Чуть более подробно об этой таблице. Все сегменты хранятся в определенной структуре, называемой GDT — Global Descriptor Table. Из названия видно, что она представляет из себя простую таблицу. Хранится в оперативной памяти всегда, подгружается при запуске операционной системы, на ее начало указывает специальный регистр GDTR. Кстати, это один из немногих регистров процессора, который хранит в себе физический адрес. И он же хранит размер таблицы, таким образом мы и операционная система всегда можем иметь доступ к нашим сегментам.
Из размерности поля индекса в селекторе можно посчитать максимальное возможное количество дескрипторов в таблице — 2 13 = 8192 дескриптора.
Размер дескриптора равен 8 байтам или 32 битам. Вот как выглядит такая запись, называемая дескриптором.
Базовый адрес есть ни что иное как физический адрес сегмента в памяти. Об остальных битах подробно расписано на википедии , я не стану повторяться.
Локальная таблица дескрипторов
Кроме GDT еще существует LDT — Local Descriptor Table — которая отвечает за сегменты каждого отдельного процесса. Ссылки на все LDT содержатся в GDT, но в любой локальной таблице не может быть ссылок на другую локальную. А во всем остальном локальные таблицы не отличаются от глобальной.
Трансляция адресов в сегментной модели памяти
И последнее, поговорим конкретнее о преобразовании логического адреса в физический. У нас есть такая штука, как селектор:смещение, где смещение это просто линейный адрес внутри самой программы(например адрес переменной). И еще у нас есть базовый адрес сегмента, полученный из глобальной таблицы дескрипторов по индексу в селекторе.
- Первым делом сравниваются RPL селектора и DPL сегмента, если запрашиваемый уровень привилегий меньше или равен уровню доступа сегмента, то доступ разрешен.
- Теперь мы должны взять базовый адрес сегмента и прибавить к нему смещение из логического адреса.
- Проверить, что полученный физический адрес не выходит за границы сегмента. Для этого существует поле limit в дескрипторе.
- Если все условия соблюдены, мы получаем доступ к ячейке памяи по физическому адресу(база сегмента + смещение).
Заключение
С появлением дескрипторов отпала сама главная головная боль программистов и ужас системных администраторов — появились права доступа к памяти. Но остались еще подводные камни, достаточно затратно следить за сегментами из-за того, что у них произвольный размер. Побороться с этим вызвалась так называемая «страничная» память. Но о ней уже в следующем выпуске, спасибо за внимание!
Assembler
Перед изучением команд и регистров процессора 8086, очень важно понять, как он получает доступ к оперативной памяти, чтобы записывать в нее значения и читать их от туда.
Почему именно процессор 8086? Просто потому, что режим совместимости с командами этого процессора есть во всех старших моделях. И начинать изучать язык ассемблера проще с этого процессора.
Процессор 8086, мог работать только в одном режиме адресации памяти. Все следующие модели, начиная с процессора 80286, сохранили режим совместимости с 8086. Этот режим получил название реального режима ( Real Address Mode ), или R-режима.
Итак, ближе к делу.
Наименьшим адресуемым блоком памяти является байт (8 бит) . Каждый байт памяти имеет уникальное местоположение, называемое физически м адресом , по которому в него может записываться и читаться информация. Очевидно, что для того, чтобы получить доступ к ячейке памяти процессору надо знать ее физический адрес. Для доступа к памяти процессор имеет адресную шину, на которой выставляет адрес ячейки памяти к которой ему необходим доступ. Грубо говоря, адресная шина – это «ноги» (pins) процессора на которых он выставляет адрес ячейки в двоичной системе счисления. Например, чтобы адресовать память размером в четыре байта (у каждого байта свой адрес), процессору, было бы, достаточно адресной шины в два бита (две «ноги). Так как, с помощью двух бит можно было бы адресовать 4 ячейки памяти: адрес 00b – 1-ая ячейка (байт), адрес 01b – 2-ая ячейка (байт), адрес 10b – 3-я ячейка (байт), 11 – 4-ая ячейка (байт). Таким образом, очевидно, что чем большую разрядность имеет адресная шина процессора, с тем большим объемом памяти он может работать.
Процессор 8086 имел 20 битную адресную шину. Что позволяло адресовать 1048576 байт (2 20 ) памяти или округленно 1 Мбайт. Проблема состояла в том, что процессор 8086 имел 16 битную архитектуру. То есть все его регистры были 16 битными. А с помощью 16 бит можно адресовать только 65536 (2 16 ) байт памяти или округленно 64 Кбайт. Тогда каким же образом процессор 8086 адресовал 1 Мбайт памяти?
Решением стала сегментная адресация памяти. С помощью этого метода физический адрес конкретного байта памяти может логически определятся двумя 16-разрядными значениями. Для того, чтобы с помощью 16-разрядных регистров можно было обращаться в любую точку 20-разрядного адресного пространства, введён двухкомпонентный логический адрес из двух 16-разрядных компонент:
Где Segment – адрес сегмента, а Offset – смещение от начала этого сегмента.
Но постойте! Два 16-разрядных регистра дают 32 разряда. Как же из этого получается 20 битный адрес? Давайте разбираться где тут собака порылась.
Для определения начала сегментов памяти процессор 8086 использует четыре 16-битных сегментных регистра (CS, DS, SS, ES). Смещение внутри сегмента выбирается из регистров-указателей SP, BP, SI, DI или регистра IP (указателя команд — Instructions Pointer). Для получения 20-битного физического адреса, процессор размещает на адресной шине значение сегментного регистра и сдвигает его влево на четыре бита, заполняя младшие четыре бита адресной шины нулями (умножение на десятичное 16 или шестнадцатеричное 10 ), затем к этому значению прибавляется смещение и адрес сформирован.
Исходя из этого получается что границы сегментов (16-битное значение + 4 нулевых бита ) располагаются через каждые 16 байт физических адресов. 4 битами можно адресовать 16 (байт) ячеек памяти, каждая из которых как мы помним содержит один байт . Каждый из этих 16-байтовых фрагментов называется параграфом . 16-разрядные сегментные регистры могут адресовать 65536 (2 16 ) параграфов (границ сегментов). А параграф, как уже говорилось, это 16 байт. 65536(параграфов) умножаем 16(байт) получаем 1048576 байт или округленно 1 Мбайт. Хотя и тут не все гладко :). Здесь порылась вторая собака. Откапывать ее будет чуть позже.
Ниже приведен вывод регистров и сегмента кода в программе debug.exe, чтобы можно было все это наглядно увидеть.
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=13DF ES=13DF SS=13DF CS=13DF IP=0100 NV UP EI PL NZ NA PO NC
13DF:0100 0000 ADD [BX+SI],AL DS:0000=CD
-d cs:100
13DF:0100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 .
13DF:0110 00 00 00 00 00 00 00 00-00 00 00 00 34 00 CE 13 . 4.
Например, в сегментном регистре (CS -выделен ярким желтым цветом) хранится значение 13DFh, при умножении его на 10h получаем 13DF0h. Стоит обратить внимание, что младшая шестнадцатеричная цифра в адресе каждого сегмента всегда равна 0. То есть адрес любого сегмента всегда кратен 16 десятичному (10h). Поскольку последняя цифра в адресе сегмента всегда равна 0, то ее можно не хранить. В действительности 8086 вместо умножения на 16 использовал содержимое регистра так, как если бы оно имело четыре дополнительных нулевых бита (см. картинку).
Максимальный размер сегмента определяется теми же 16 битами регистра, в котором хранится смещение. Следовательно, максимальный размер сегмента может быть 65536 байт (2 16 ). Минимальный – 16 байт (размер параграфа). Таким образом, сегменты – это виртуальные умозрительные части с максимальным объемом 64 Кбайт каждая.
Теперь будем откапывать вторую собаку. Возьмем максимальное значение, которое может адресовать сегментный регистр FFFF, применим к нему сдвиг влево на 4 бита, получим FFFF0h (1048560d). Теперь прибавим к этому числу максимальное значение которое может хранится в регистре смещения – FFFF. Таким образом, FFFF0+FFFF= 10FFEF (1114095d). И что это такое? Мы же явно вышли за пределы 1048576 байт памяти. 20 битная адресная шина позволяет максимально адресовать 1048576 байт памяти с адресами от 00000h до FFFFFh. При адресации же памяти свыше 100000h и до 10FFEFh происходил «заворот» — старший единичный бит адреса игнорировался и доступ шёл к 64 килобайтам в начальных адресах (0000h…FFEFh).
Вместе со второй собакой мы откопали и большую свинью, которую, сами того не ведая, подложили разработчики процессора 8086 и сегментной адресации памяти.
Факты о «свинье»:
* Нет никаких препятствий для обращения к физически не существующей памяти.
* При обращении к несуществующей памяти результат непредсказуем (все зависит от разработчика материнской платы и другого аппаратного обеспечения компьютера).
* Программа может обращаться к любому сегменту как для считывания, так и для записи данных и команд.
И еще немного правды о сегментах:
* Сегменты физически не выделены в памяти. Сегменты — это логические окна, через которые программы просматривают области памяти удобными, в 64 Кбайт порциями.
* Размеры сегментов могут изменятся от 16 байт до 64 Кбайт (65536 байт).
* Сегменты не обязательно в памяти располагаются один за другим. Хотя такое бывает достаточно часто.
* Сегменты могут перекрываться один другим; поэтому один и тот же физический байт памяти может иметь различные логические адреса , определяемые разными, но при этом эквивалентными парами сегмент-смещение. Например, пары логических адресов 0000:0010 и 0001:0000 указывают на один и тот же физический адрес ячейки памяти — 0010h.
* Назначением базовых адресов сегментов занимается операционная система, а внутри каждого сегмента адреса формируются программой.
* сегментная организация обеспечивает создание позиционно – независимых или динамически перемещаемых в памяти программ.
3.1.2. Сегментирование памяти
Говоря об адресации, нельзя обойти вопрос о сегментировании памяти, применяемой в некоторых процессорах, например в процессорах IBM PC-совместимых персональных компьютеров.
В процессоре Intel 8086 сегментирование памяти организовано следующим образом.
Вся память системы представляется не в виде непрерывного пространства, а в виде нескольких кусков — сегментов заданного размера (по 64 Кбайта), положение которых в пространстве памяти можно изменять программным путем.
Для хранения кодов адресов памяти используются не отдельные регистры, а пары регистров:
сегментный регистр определяет адрес начала сегмента (то есть положение сегмента в памяти);
регистр указателя (регистр смещения) определяет положение рабочего адреса внутри сегмента.
При этом физический 20-разрядный адрес памяти, выставляемый на внешнюю шину адреса, образуется так, как показано на рис. 3.5, то есть путем сложения смещения и адреса сегмента со сдвигом на 4 бита. Положение этого адреса в памяти показано на рис. 3.6.
Сегмент может начинаться только на 16-байтной границе памяти (так как адрес начала сегмента, по сути, имеет четыре младших нулевых разряда, как видно из рис. 3.5), то есть с адреса, кратного 16. Эти допустимые границы сегментов называются границами параграфов.
Отметим, что введение сегментирования, прежде всего, связано с тем, что внутренние регистры процессора 16-разрядные, а физический адрес памяти 20-разрядный (16-разрядный адрес позволяет использовать память только в 64 Кбайт, что явно недостаточно). В появившемся в то же время процессоре MC68000 фирмы Motorola внутренние регистры 32-разрядные, поэтому там проблемы сегментирования памяти не возникает.
Рис. 3.5. Формирование физического адреса памяти из адреса сегмента и смещения.
Рис. 3.6. Физический адрес в сегменте (все коды — шестнадцатеричные).
Применяются и более сложные методы сегментирования памяти. Например, в процессоре Intel 80286 в так называемом защищенном режиме адрес памяти вычисляется в соответствии с рис. 3.7.
В сегментном регистре в данном случае хранится не базовый (начальный) адрес сегментов, а коды селекторов, определяющие адреса в памяти, по которым хранятся дескрипторы (то есть описатели) сегментов. Область памяти с дескрипторами называется таблицей дескрипторов. Каждый дескриптор сегмента содержит базовый адрес сегмента, размер сегмента (от 1 до 64 Кбайт) и его атрибуты. Базовый адрес сегмента имеет разрядность 24 бит, что обеспечивает адресацию 16 Мбайт физической памяти.
Рис. 3.7. Адресация памяти в защищенном режиме процессора Intel 80286.
Таким образом, на сумматор, вычисляющий физический адрес памяти, подается не содержимое сегментного регистра, как в предыдущем случае, а базовый адрес сегмента из таблицы дескрипторов.
Еще более сложный метод адресации памяти с сегментированием использован в процессоре Intel 80386 и в более поздних моделях процессоров фирмы Intel. Этот метод иллюстрируется рис. 3.8.
Адрес памяти (физический адрес) вычисляется в три этапа. Сначала вычисляется так называемый эффективный адрес (32-разрядный) путем суммирования трех компонентов: базы, индекса и смещения (Base, Index, Displacement), причем возможно умножение индекса на масштаб (Scale). Эти компоненты имеют следующий смысл:
Рис. 3.8. Формирование физического адреса памяти процессора 80386 в защищенном режиме.
смещение — это 8-, 16- или 32-разрядное число, включенное в команду.
база — это содержимое базового регистра процессора. Обычно оно используется для указания на начало некоторого массива.
индекс — это содержимое индексного регистра процессора. Обычно оно используется для выбора одного из элементов массива.
масштаб — это множитель (он может быть равен 1, 2, 4 или 8), указанный в коде команды, на который перед суммированием с другими компонентами умножается индекс. Он используется для указания размера элемента массива.
Затем специальный блок сегментации вычисляет 32-разрядный линейный адрес, который представляет собой сумму базового адреса сегмента из сегментного регистра с эффективным адресом. Наконец, физический 32-битный адрес памяти образуется путем преобразования линейного адреса блоком страничной переадресации, который осуществляет перевод линейного адреса в физический страницами по 4 Кбайта.
В любом случае сегментирование позволяет выделить в памяти один или несколько сегментов для данных и один или несколько сегментов для программ. Переход от одного сегмента к другому сводится всего лишь к изменению содержимого сегментного регистра. Иногда это бывает очень удобно. Но для программиста работать с сегментированной памятью обычно сложнее, чем с непрерывной, несегментированной памятью, так как приходится следить за границами сегментов, за их описанием, переключением и т.д.
Организация памяти
За последнюю неделю дважды объяснял людям как организована работа с памятью в х86, с целью чтобы не объяснять в третий раз написал эту статью.
И так, чтобы понять организацию памяти от вас потребуется знания некоторых базовых понятий, таких как регистры, стек и тд. Я по ходу попробую объяснить и это на пальцах, но очень кратко потому что это не тема для этой статьи. Итак начнем.
Как известно программист, когда пишет программы работает не с физическим адресом, а только с логическим. И то если он программирует на ассемблере. В том же Си ячейки памяти от программиста уже скрыты указателями, для его же удобства, но если грубо говорить указатель это другое представление логического адреса памяти, а в Java и указателей нет, совсем плохой язык. Однако грамотному программисту не помешают знания о том как организована память хотя бы на общем уровне. Меня вообще очень огорчают программисты, которые не знают как работает машина, обычно это программисты Java и прочие php-парни, с квалификацией ниже плинтуса.
Так ладно, хватит о печальном, переходим к делу.
Рассмотрим адресное пространство программного режима 32 битного процессора (для 64 бит все по аналогии)
Адресное пространство этого режима будет состоять из 2^32 ячеек памяти пронумерованных от 0 и до 2^32-1.
Программист работает с этой памятью, если ему нужно определить переменную, он просто говорит ячейка памяти с адресом таким-то будет содержать такой-то тип данных, при этом сам програмист может и не знать какой номер у этой ячейки он просто напишет что-то вроде:
int data = 10;
компьютер поймет это так: нужно взять какую-то ячейку с номером стопицот и поместить в нее цело число 10. При том про адрес ячейки 18894 вы и не узнаете, он от вас будет скрыт.
Все бы хорошо, но возникает вопрос, а как компьютер ищет эту ячейку памяти, ведь память у нас может быть разная:
3 уровень кэша
2 уровень кэша
1 уровень кэша
основная память
жесткий диск
Это все разные памяти, но компьютер легко находит в какой из них лежит наша переменная int data.
Этот вопрос решается операционной системой совместно с процессором.
Вся дальнейшая статья будет посвящена разбору этого метода.
Архитектура х86 поддерживает стек.
Стек это непрерывная область оперативной памяти организованная по принципу стопки тарелок, вы не можете брать тарелки из середины стопки, можете только брать верхнюю и класть тарелку вы тоже можете только на верх стопки.
В процессоре для работы со стеком организованны специальные машинные коды, ассемблерные мнемоники которых выглядят так:
push operand
помещает операнд в стек
pop operand
изымает из вершины стека значение и помещает его в свой операнд
Стек в памяти растет сверху вниз, это значит что при добавлении значения в него адрес вершины стека уменьшается, а когда вы извлекаете из него, то адрес вершины стека увеличивается.
Теперь кратко рассмотрим что такое регистры.
Это ячейки памяти в самом процессоре. Это самый быстрый и самый дорогой тип памяти, когда процессор совершает какие-то операции со значением или с памятью, он берет эти значения непосредственно из регистров.
В процессоре есть несколько наборов логик, каждая из которых имеет свои машинные коды и свои наборы регистров.
Basic program registers (Основные программные регистры) Эти регистры используются всеми программами с их помощью выполняется обработка целочисленных данных.
Floating Point Unit registers (FPU) Эти регистры работают с данными представленными в формате с плавающей точкой.
Еще есть MMX и XMM registers эти регистры используются тогда, когда вам надо выполнить одну инструкцию над большим количеством операндов.
Рассмотрим подробнее основные программные регистры. К ним относятся восемь 32 битных регистров общего назначения: EAX, EBX, ECX, EDX, EBP, ESI, EDI, ESP
Для того чтобы поместить в регистр данные, или для того чтобы изъять из регистра в ячейку памяти данные используется команда mov:
mov eax, 10
загружает число 10 в регистр eax.
mov data, ebx
копирует число, содержащееся в регистре ebx в ячейку памяти data.
Регистр ESP содержит адрес вершины стека.
Кроме регистров общего назначения, к основным программным регистрам относят шесть 16битных сегментных регистров: CS, DS, SS, ES, FS, GS, EFLAGS, EIP
EFLAGS показывает биты, так называемые флаги, которые отражают состояние процессора или характеризуют ход выполнения предыдущих команд.
В регистре EIP содержится адрес следующей команды, которая будет выполнятся процессором.
Я не буду расписывать регистры FPU, так как они нам не понадобятся. Итак наше небольшое отступление про регистры и стек закончилось переходим обратно к организации памяти.
Как вы помните целью статьи является рассказ про преобразование логической памяти в физическую, на самом деле есть еще промежуточный этап и полная цепочка выглядит так:
Логический адрес —> Линейный (виртуальный)—> Физический
Все линейное адресное пространство разбито на сегменты. Адресное пространство каждого процесса имеет по крайней мере три сегмента:
Сегмент кода. (содержит команды из нашей программы, которые будут исполнятся.)
Сегмент данных. (Содержит данные, то бишь переменные)
Сегмент стека, про который я писал выше.
Линейный адрес вычисляется по формуле:
линейный адрес=Базовый адрес сегмента(на картинке это начало сегмента) + смещение
Сегмент кода
Сегмент данных
Сегмент стека
Используемый сегмент стека задается значением регистра SS.
Смещение внутри этого сегмента представлено регистром ESP, который указывает на вершину стека, как вы помните.
Сегменты в памяти могут друг друга перекрывать, мало того базовый адрес всех сегментов может совпадать например в нуле. Такой вырожденный случай называется линейным представлением памяти. В современных системах, память как правило так организована.
Теперь рассмотрим определение базовых адресов сегмента, я писал что они содержаться в регистрах SS, DS, CS, но это не совсем так, в них содержится некий 16 битный селектор, который указывает на некий дескриптор сегментов, в котором уже хранится необходимый адрес.
Так выглядит селектор, в тринадцати его битах содержится индекс дескриптора в таблице дескрипторов. Не хитро посчитать будет что 2^13 = 8192 это максимальное количество дескрипторов в таблице.
Вообще дескрипторных таблиц бывает два вида GDT и LDT Первая называется глобальная таблица дескрипторов, она в системе всегда только одна, ее начальный адрес, точнее адрес ее нулевого дескриптора хранится в 48 битном системном регистре GDTR. И с момента старта системы не меняется и в свопе не принимает участия.
А вот значения дескрипторов могут меняться. Если в селекторе бит TI равен нулю, тогда процессор просто идет в GDT ищет по индексу нужный дескриптор с помощью которого осуществляет доступ к этому сегменту.
Пока все просто было, но если TI равен 1 тогда это означает что использоваться будет LDT. Таблиц этих много, но использоваться в данный момент будет та селектор которой загружен в системный регистр LDTR, который в отличии от GDTR может меняться.
Индекс селектора указывает на дескриптор, который указывает уже не на базовый адрес сегмента, а на память в котором хранится локальная таблица дескрипторов, точнее ее нулевой элемент. Ну а дальше все так же как и с GDT. Таким образом во время работы локальные таблицы могут создаваться и уничтожаться по мере необходимости. LDT не могут содержать дескрипторы на другие LDT.
Итак мы знаем как процессор добирается до дескриптора, а что содержится в этом дескрипторе посмотрим на картинке:
Дескрипторы состоит из 8 байт.
Биты с 15-39 и 56-63 содержат линейный базовый адрес описываемым данным дескриптором сегмента. Напомню нашу формулу для нахождения линейного адреса:
линейный адрес = базовый адрес + смещение
[база; база+предел)
(база+предел; вершина]
Кстати интересно почему база и предел так рвано располагаются в дескрипторе. Дело в том что процессоры х86 развивались эволюционно и во времена 286х дескрипторы были по 8 бит всего, при этом старшие 2 байта были зарезервированы, ну а в последующих моделях процессоров с увеличением разрядности дескрипторы тоже выросли, но для сохранения обратной совместимости пришлось оставить структуру как есть.
Значение адреса «вершина» зависит от 54го D бита, если он равен 0, тогда вершина равна 0xFFF(64кб-1), если D бит равен 1, тогда вершина равна 0xFFFFFFFF (4Гб-1)
С 41-43 бит кодируется тип сегмента.
000 — сегмент данных, только считывание
001 — сегмент данных, считывание и запись
010 — сегмент стека, только считывание
011 — сегмент стека, считывание и запись
100 — сегмент кода, только выполнение
101- сегмент кода, считывание и выполнение
110 — подчиненный сегмент кода, только выполнение
111 — подчиненный сегмент кода, только выполнение и считывание
44 S бит если равен 1 тогда дескриптор описывает реальный сегмент оперативной памяти, иначе значение S бита равно 0.
Самым важным битом является 47-й P бит присутствия. Если бит равен 1 значит, что сегмент или локальная таблица дескрипторов загружена в оперативку, если этот бит равен 0, тогда это означает что данного сегмента в оперативке нет, он находится на жестком диске, случается прерывание, особый случай работы процессора запускается обработчик особого случая, который загружает нужный сегмент с жесткого диска в память, если P бит равен 0, тогда все поля дескриптора теряют смысл, и становятся свободными для сохранения в них служебной информации. После завершения работы обработчика, P бит устанавливается в значение 1, и производится повторное обращение к дескриптору, сегмент которого находится уже в памяти.
На этом заканчивается преобразование логического адреса в линейный, и я думаю на этом стоит прерваться. В следующий раз я расскажу вторую часть преобразования из линейного в физический.
А так же думаю стоит немного поговорить о передачи аргументов функции, и о размещении переменных в памяти, чтобы была какая-то связь с реальностью, потому размещение переменных в памяти это уже непосредственно, то с чем вам приходится сталкиваться в работе, а не просто какие-то теоретические измышления для системного программиста. Но без понимания, как устроена память невозможно понять как эти самые переменные хранятся в памяти.
В общем надеюсь было интересно и до новых встреч.