Каковы сегмент и смещение в реальном режиме адресации памяти?
Я читаю об адресации памяти. Я прочитал о смещении сегмента, а затем о смещении дескриптора. Я знаю, как рассчитать точные адреса в реальном режиме. Все это нормально, но я не могу понять, что именно смещение? Везде, где я читаю:
В реальном режиме регистры только 16 бит, поэтому вы можете адресовать только до 64 КБ. Чтобы разрешить адресацию большего объема памяти, адрес קד рассчитывается по сегменту * 16 + смещение.
Здесь я могу понять первую строку. У нас 16 бит, поэтому мы можем адресовать до 2^16 = 64 КБ.
Но что это за вторая строка? Что представляет собой сегмент? Почему мы умножаем это на 16? почему мы добавляем смещение. Я просто не могу понять, что это за смещение? Кто-нибудь может объяснить мне или дать мне ссылку на это, пожалуйста?
6 ответов
В x86 Real-Mode Memory физический адрес имеет длину 20 бит и вычисляется:
PhysicalAddress = Segment * 16 + Offset
Проверьте также: Управление памятью в реальном режиме
Когда Intel собирала 8086, существовал веский случай, когда в машине было больше 64 КБ, но не было способа использовать 32-битное адресное пространство. Тогда даже мегабайт был огромным объемом памяти. (Помните печально известную цитату "640K должно быть достаточно для всех"? По сути, это неверный перевод того факта, что в те времена 1МБ был чертовски огромен.) Слово "гигабайт" не использовалось бы еще 15-20 лет., и это не будет относиться к оперативной памяти в течение еще 5-10 лет после этого.
Поэтому вместо реализации настолько огромного адресного пространства, что оно "никогда" не будет полностью использовано, они реализовали 20-разрядные адреса. Они по-прежнему использовали 16-битные слова для адресов, потому что, в конце концов, это 16-битный процессор. Верхнее слово было "сегмент", а нижнее слово было "смещение". Тем не менее, две части значительно перекрываются - "сегмент" - это фрагмент памяти размером 64 КБ, начинающийся с (segment) * 16
и "смещение" может указывать в любом месте этого фрагмента. Чтобы вычислить фактический адрес, вы умножаете сегментную часть адреса на 16 (или сдвигаете ее влево на 4 бита... тоже самое), а затем добавляете смещение. Когда вы закончите, у вас есть 20-битный адрес.
19 4 0
+--+--+--+--+
| segment |
+--+--+--+--+--+
| offset |
+--+--+--+--+
Например, если сегмент был 0x8000, а смещение было 0x0100, фактический адрес выходит на ((0x8000 << 4) + 0x0100)
== 0x80100
,
8 0 0 0
0 1 0 0
---------------
8 0 1 0 0
Математика редко бывает такой аккуратной, хотя - 0x80100
могут быть представлены буквально тысячами различных комбинаций сегмент: смещение (4096, если моя математика верна).
Я вижу, что вопросу и ответам уже несколько лет, но есть неверное утверждение, что в реальном режиме существуют только 16-битные регистры.
В реальном режиме регистры не только 16-битные, потому что есть также 8-битные регистры. Каждый из этих 8-битных регистров является частью 16-битного регистра, который разделен на нижнюю и верхнюю части 16-битного регистра.
И начиная реальный режим с 80386+, мы становимся 32-битными регистрами, а также добавляем также два новых префикса инструкций, один для переопределения / реверсирования размера операнда по умолчанию и один для переопределения / реверсирования размера адреса по умолчанию одной инструкции внутри сегмент кода.
Эти префиксы команд могут использоваться в комбинации для обратного изменения размера операнда и размера адреса для одной инструкции. В реальном режиме размер операнда и размер адреса по умолчанию равен 16 битам. С этими обоими префиксами инструкций мы можем использовать пример 32-битного операнда / регистра для вычисления 32-битного значения в одном 32-битном регистре или для перемещения 32-битного значения в и из ячейки памяти. И мы можем использовать все 32-битные регистры (возможно, в сочетании с базой + индекс * масштаб + смещение) в качестве адреса-регистра, но сумма эффективного адреса не должна превышать предел размера сегмента 64 КБ,
(На странице OSDEV-Wiki мы можем найти в таблице "Префикс размера операнда и размера адреса", что "Префикс операнда 0x66" и "Префикс адреса 0x67" равен N/A(недоступно) для реальный режим и виртуальный режим 8086. http://wiki.osdev.org/X86-64_Instruction_Encoding
Но это в корне неверно, потому что в руководстве Intel мы можем найти следующее утверждение: "Эти префиксы могут использоваться как в режиме реального адреса, так и в защищенном режиме и режиме virtual-8086".)
Начиная с Pentium MMX, мы становимся восемью 64-битными MMX-регистрами.
Начиная с Pentium 3, мы становимся восемью 128-битными XMM-регистрами.
..
Если я не ошибаюсь, то 256-битный регистр YMM и 512-битный регистр ZMM и 64-битный регистр общего назначения x64 не могут использоваться в реальном режиме.
кортик
Минимальный пример
С:
- смещение =
msg
- сегмент =
ds
mov $0, %ax
mov %ax, %ds
mov %ds:msg, %al
/* %al contains 1 */
mov $1, %ax
mov %ax, %ds
mov %ds:msg, %al
/* %al contains 2: 1 * 16 bytes forward. */
msg:
.byte 1
.fill 15
.byte 2
Итак, если вы хотите получить доступ к памяти выше 64 КБ:
mov $0xF000, %ax
mov %ax, %ds
Обратите внимание, что это позволяет использовать адреса шириной более 20 бит, если вы используете что-то вроде:
0x10 * 0xFFFF + 0xFFFF == 0x10FFEF
На более ранних процессорах, которые имели только 20 адресных проводов, он был просто урезан, но позже все усложнилось с линией A20 (21-й адресный провод): https://en.wikipedia.org/wiki/A20_line
На репозитории GitHub с необходимым шаблоном для его запуска.
Я хочу добавить ответ только потому, что я искал в интернете, пытаясь понять это тоже. Другие ответы опускали ключевую информацию, которую я получил по ссылке, представленной в одном из ответов. Однако я почти полностью пропустил это. Читая связанную страницу, я все еще не понимал, как это работает.
Вероятно, проблема, с которой я столкнулся, заключалась в том, что я действительно понимал, как Commodore 64 (процессор 6502) распределяет память. Он использует аналогичные обозначения для адресации памяти. Он имеет 64 КБ общей памяти и использует 8-битные значения PAGE:OFFSET для доступа к памяти. Каждая страница имеет длину 256 байт (8-разрядное число), и смещение указывает на одно из значений на этой странице. Страницы расположены в памяти один за другим. Итак, страница 2 начинается там, где заканчивается страница 1. Я собирался в 386, думая в том же стиле. Это не так.
Реальный режим использует похожий стиль, даже если он отличается от слов SEGMENT:OFFSET. Сегмент размером 64 КБ. Тем не менее, сами сегменты расположены не так, как Коммодор. Они разнесены на 16 байт друг от друга. Смещение по-прежнему работает так же, указывая, сколько байтов с начала страницы \ сегмента.
Я надеюсь, что это объяснение поможет любому, кто найдет этот вопрос, оно помогло мне написать его.
16-разрядный регистр может адресовать только до 0xFFFF (65 536 байт, 64 КБ). Когда этого было недостаточно, Intel добавила сегментные регистры.
Любая логическая схема просто объединила бы два 16-разрядных регистра для создания 32-разрядного адресного пространства (например, 0xFFFF : 0xFFFF = 0xFFFFFFFF
), но неааа... Intel пришлось заполучить нас.
Исторически передняя шина (FSB) имела только 20 адресных линий и, следовательно, могла передавать только 20-битные адреса. Чтобы "исправить" это, Intel разработала схему, в которой сегментные регистры расширяют ваш адрес только на 4 бита (16 бит + 4 = 20, в теории).
Для этого регистр сегмента сдвигается влево от своего первоначального значения на 4 бита, а затем добавляется к адресу в общем регистре (например, [es:ax] = ( es << 4 ) + ax
) Примечание. Сдвиг влево на 4 бита эквивалентен умножению на 16.
Вот и все. Вот несколько иллюстративных примеров:
;; everything's hexadecimal
[ 0:1 ] = 1
[ F:1 ] = F1
[ F:0 ] = F0
[ F:FF] = 1EF ; [F becomes F0, + FF = 1EF]
[ F000 : FFFF ] = FFFFF (max 20-bit number)
[ FFFF : FFFF ] = 10FFEF (oh shit, 21-bit number!)
Таким образом, вы все еще можете адресовать более 20 бит. Что просходит? Адрес "оборачивается", как модульная арифметика (как естественное следствие аппаратного обеспечения). Так, 0x10FFEF
становится 0xFFEF
,
И вот оно у тебя! Intel наняла немых инженеров, и мы должны жить с этим.