Как стать автором
Обновить

Комментарии 89

НЛО прилетело и опубликовало эту надпись здесь

Как такое реализовать между единицами трансляции? Адреса это также параметры функций. Соответственно, автоматическая система адресации могла такого накрутить, что потом слинковать было бы проблемой. В сях слинковалось бы, но я такое боюсь запускать.

Интересно, конечно) Вся страна играла в Wolfenstein, а потом в Doom, но пользовалась Борландом вместо Watcom, в котором под DOS4GW (и нескольких других менее популярных экстендерах) был легко доступен 32 разрядный режим с линейной адресацией. Почему?) Возможно потому, что в Watcom не было IDE с контекстным хелпом и встроенных графических средств конфигурирования проектов.

Хуже. 386 появился в ~1986, а микрософт ещё лет 15 после этого не решался отказаться от всего этого 16-битного убожества.

Ну и пусть старые программы работают как работали, а новые используют 32-битную адресацию. В Windows 95 так оно и реализовано.

Хуже. 386 появился в ~1986, а микрософт ещё лет 15 после этого не решался отказаться от всего этого 16-битного убожества

Вроде бы Windows 95 появилась в 95-м году?

Через 9-ть лет...

А Xenix и OS/2 и того раньше...

386 был жутко дорогой, даже в 92ом Windows 3.1 вынужден был поддержать standard mode (286), и 386 enhanced mode для тех кому повезло.

Жаль, что «ДОС Четырежды ЖИВой» не дорос до полноценной «ОС поверх ОС», типа Win95… вложиться бы в него тогда как следует — не пришлось бы столько лет иметь дело с мастдаями (пока дебианы не подросли до нынешних убунто-минтов).

Страшная, тёмная эра мрака между «досом четырежды живым» и сошествием благодетельной Убунты :)

Но цепочка легаси бы былааааа… ой :) «В любой непонятной ситуации обращайся к 16-битному драйверу или службе BIOS». Хотя, с другой стороны, это гораздо лучше, чем «ваш драйвер не подходит для этой версии мастдая» и всё, финиш! Лучше уж при отсутствии 32-битных дров уходить в легаси со всеми причитающимися тормозами и тормозящими причитаниями…

Жаль, что не вышла 32-битная версия DOS, однозадачная, консольная, но с полным доступом ко всей памяти.

А зачем однозадачность-то?

Чтобы что?

В процессоре она есть, а в ОС нужно меры для ее ограничения применить?

Чтобы сохранить главную фишку DOS - прямой доступ к железу. Программа монопольно владеет компьютером и делает с ним что хочет. В многозадачной системе это невозможно.

Вообще, это от системы зависит.

В RSX-11M (бабка Винды) и в VAX/VMS (мамаша Винды) привилегированная задача могла получить доступ к железу напрямую, попросив об этом ОС (что позволяло писать драйверы, являющиеся не частью ядра системы, а задачами режима пользователя; в частности, в RSX-11M именно так были сделаны драйверы файловых систем). Вот непривилегированная задача получить доступ к железу не могла.

А в OS/360 была вообще макрокоманда супервизора (т. е. функция API ядра системы) с весьма говорящим названием MODESET. Как говорится, угадайте с трёх попыток, что она делает :) (и да, она, в принципе, могла использоваться кем угодно, если это не было специальным образом заблокировано и оставлено лишь для избранных).

Так начиная с DOS 3.3, если не ошибаюсь "прямой доступ к железу" - иллюзия.

80286 имел несколько конвейеров и семафоры для вытесняющей многозадачности.

Механизм виртуализации памяти, правда, не был доделан из-за чего Microsoft Xenix не пошел.

На 80386 вполне себе запускался SCO Unix и Free BSD. Тот же Microsoft Xenix в 91-м вполне себе крутился на PC AT 386...

Нет, все ДОСы сами по себе работают в реальном режиме, и поэтому программа имеет полный доступ к любому железу.

80286 имел несколько конвейеров и семафоры для вытесняющей многозадачности

Насчёт конвейеров и семафоров не понял, но для вытесняющей многозадачности не нужно ничего, кроме таймера -- чтобы по прерыванию снять задачу, слишком долго занимающую процессор. Всё это имелось уже в самом первом ПК на 8088. Защита памяти для многозадачности не требуется (она позволяет защищать задачи и систему, но не нужна для переключения задач, т. е. для собственно многозадачности).

Механизм виртуализации памяти, правда, не был доделан из-за чего Microsoft Xenix не пошел

Xenix не пошёл не из-за механизма виртуализации памяти 80286 как такового, а из-за этого, что этот механизм не позволял сохранить совместимость с ДОС, а значит, исключал использование в Xenix уже весьма и весьма многочисленных программ, написанных под ДОС. В 80836 ввели режим виртуального 8086 (по сути, не настоящий режим, а подрежим защищённого 32-разрядного режима), что и позволило создать систему, способную выполнять как собственные программы, так и программы ДОС.

Кстати, в современных 64-разрядных системах использовать программы для ДОС нельзя, поскольку режим V86 из 64-разрядного режима был выпилен, он может использоваться лишь в 32-разрядном защищённом режиме.

Вы бы подкрепляли то, что вещаете какими-то аргументами.

Ваши убеждения - не аргумент, даже если они крепкие...

Я в свое время провел довольно много времени за попыткой запуска Microsoft Xenix на PC AT 286. На которой он будто бы "работал".

А те, кто в теме - прослезились наверное, прочитав про то, что семафоры с конвейерами для многозадачности не нужны.

Вы гигант мысли прямо...

Многозадачные ОС существуют даже на микроконтроллерах, без всяких конвееров и семафоров. Конвеер - это вообще из другой оперы, он никак с многозадачностью не связан. Он нужен для того, чтобы выбирать из памяти следующую команду, пока идет обработка предыдущей.
А Xenix вполне себе работал на 286, но был там никому не нужен. Юниксоиды сидели на PDP и VAX, а на PC правил балом DOS.

А Xenix вполне себе работал на 286, но был там никому не нужен.

Поделитесь, пожалуйста, своим опытом где он работал и что на нём запускалось.

Я тоже видел работающие системы на Xenix для 286.

Определенный набор программ с инструкцией, как можно запускать, а как нельзя.

Я же откуда-то взял его дистрибутив.

Долго пытался запустить задачу. Думал, что память глючит.

И только изучение с помощью nmon/pmon состояния "ненужных" семафоров с конвейерами позволили понять, что причина не в неисправной памяти.

Видимо вам просто не повезло. Например 286 с Xenix шли одно время, как рабочие места к немецким телефонным станциям EWSD. Все прекрасно работало, я даже переставлял после замены HDD, коробка 3,5 дискет в комплекте была. Не помню никаких проблем.
Компы, если не изменяет память были PS/2

Видимо вам просто не повезло. Например 286 с Xenix шли одно время, как рабочие места к немецким телефонным станциям

В каком-то смысле - да.

С другой стороны я приложил к обоснованию того, что нужны i386 перевод из "эхи"

И мне их дали.

А разве были PS/2 на i286 ?

Вроде бы OS/2 сразу в защищённом режиме запускалась.

Вы не перепутали i286 и i386SX?

Наверное это были PS/1?

Было такое чудо.

Поговаривали, что на них работал защищённый режим памяти.

ИБМ-овский контроллер памяти ECC был особенный и драйвер этого контроллера исправлял багу процессора.

Но поставить на нее Xenix я не сподобился.

PS/2 (англ. Personal System) — серия персональных компьютеров компании IBM на процессорах серий Intel 80286 и Intel 80386, выпускавшаяся с апреля 1987 года.

Да. Действительно.

Были двойки PS/2. Нашел сканы буклетов IBM 89-90гг

Не встречал таких, но я и начал свои упражнения с PC в 91-м.

Может быть, что и работал Xenix на 286-х PS/2.

Ну и наверняка работал раз Clipper for Xenix и у нас не глючил, но на время впадал в медитацию при перегрузке.

OS/360 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая.

RSX-11 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая.

Ну а какие конвейеры с семафорами Вы нашли в 80286, для меня остаётся загадкой. Хоть бы пояснили, что ли.

Вы бы не рассказывали мне об IBM 360/370/SystemZ и об архитектуре процессоров...

Не знаю, чем Вы там занимаетесь, но, если "программатор" вдруг не видит под слоем микропрограмм семафоров и конвееров - не значит, что их там нет ;)

Вернее - в SystemZ семафоров нет, поскольку многозадачность "невытесняющая".

А конвееры - на месте.

И почему Вы решили привести в качестве примера System 360?

Дедушкин учебник по ней в сети нашли?

Больше неоткуда "скачать бесплатный реферат"?

"Если на клетке со львом написано "Слон" - не верь глазам своим." (С) Козьма Прутков.

OS/360 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая

Это Вы сейчас о чем?

Внутри ВМ или в MVS или вообще о варианте с набором микропрограмм для AIX?

А версия S360 - какая?

Почему S360?

S370 и S390 чем-то не устраивают?

Ну а какие конвейеры с семафорами Вы нашли в 80286, для меня остаётся загадкой.

Откройте руководства от Интел по этой серии - разгадайте для себя загадку...

Ключевые слова "BU", "EU"...

Фигню пишете. Всё это реализуется софтово, с оверхедом о-го-го. Но реализуется. Более того, есть форки Линукс для процессоров без MMU вообще. (А не так давно было и в основном ядре)

Фигню пишете. Всё это реализуется софтово, с оверхедом о-го-го

Почему же..

Я не болтаю, а привожу пример.

Из своего личного опыта.

Оно, конечно, дебил "малтитаскин" и на таймаутах реализует.

Как в первых айфонах.

Но разработчики FoxBase "отбарабанили" свой продукт по гайду.

Я когда отослал отчёт из PMON в тематическую эхо-конференцию - мне ответили, что мол это известный баг 80286. Архитектурно-зависимый.

Посоветовали развернуть это на i386 и не париться...

А Ваши советы основаны на чем-то кроме фантазий?

Про проблемы малтитаскинга без аппаратной поддержки ещё Керниган с Пайком писали.

Вы читали отцов-основателей перед раздачей советов?

Кстати, позже, после развертывания сервера на i386 был найден Clipper(Clip?) for Xenix.

Он отрабатывал без сбоев на i286

Иногда задумывался о чем-то своем как ранний айфон, но стабильно выходил из состояния задумчивости рано или поздно.

Но и люди уже привыкли к хорошему да и БД на DBaseVI было написано достаточно.

Xenix не пошёл не из-за механизма виртуализации памяти 80286 как такового, а из-за этого, что этот механизм не позволял сохранить совместимость с ДОС,

Освежил даты...

Xenix для 80286 в "защищённом режиме" вышел в 1983 году.

исключал использование в Xenix уже весьма и весьма многочисленных программ, написанных под ДОС

Тогда же был анонсирован XEDOS.

"Многочисленными программами " в 1983 году и не пахло.

Я Xenix пытался запускать на "двойке" в связи с желанием работать на Microsoft FoxBase на текстовых терминалах.

В "двойках" многозадачный режим постоянного вылетал на операциях с памятью.

На "тройке" и Xenix и его брат SCO Unix завелся и работал без проблем...

Если бы в 1983 году Xenix на двойках пошел бы, то неизвестно как бы выглядела программная среда в последующие годы.

>Кстати, в современных 64-разрядных системах использовать программы для ДОС нельзя,

V8086 режима в 64 действительно нет, по поддержки программ дос нет по причине "идите нафик, вот почему", есть неофициальное расширение ntvdm64, решающее эту проблему
(тем более после первых 64 появились режимы виртуализации, намного более мощные, чем старый V8086, но поддержку досовых программ MS не вернула).

Как я уже говорил (поиск по камментам бы…) — фридос надо реализовать как модуль уефи, одно ядро — одна задача, и никакого квантования. Индустриальная реалтаймовая оська «искаропки», даже без загрузки, по щелчку питания. И творите через порты, чего душе угодно.

Можно и 32-битную, со вшитым дос4гв. Даже скорее «нужно».

А UEFI приложения точно запускаются в реалтайме? Сомнения просто есть...

А вот хороший вопрос, на самом деле. Учитывая, что там уже «мастдай вшит в мозг» — могут быть с этим проблемы. ХЗ, насколько он там обрезан — относительно напрямую модули работают или уже практически всё свелось к мастдайным дровам под маленькой 95-й, со всеми вытекающими.

Возможно, придётся наоборот — фридос реализовать как основу (типа либребута), а уёфище уже из-под него запускать. Но это надо очень хорошо овладеть искусством дёрганья портов на каждой конкретной материнке, то есть исчезает эта прелесть лёгкости портирования :( ну и наверняка будут проблемы со всякими цифровыми подписями и прочими цифровыми ошейниками, упыри приложили много усилий к тому, чтобы это был не «мой компьютер», а «этот компьютер».

Вся страна играла в Wolfenstein, а потом в Doom, но пользовалась Борландом 

Вы не торопитесь?

Статья про архитектуру PC/XT.

А драйверы HIMEM/EMM386 для PC AT 286/386 написаны.

Не пойдет DOOM на XT...

У меня пост про Борланд, преимущественно, был. И я не помню, чтобы компилятор Майкрософт имел такое же количество моделей памяти, как Борланд, может я и не прав, конечно.
А himem был достаточно бесполезный для программирования - разве что общую память расширить вынеся туда драйверы, если вынесутся.
Но если автор продолжит, то да, поторопился))

И я не помню, чтобы компилятор Майкрософт имел такое же количество моделей памяти, как Борланд

Имел-имел. Не помню, была ли модель tiny, т.е. возможность создания COM-файлов, но остальные были.

Кстати, как правильно из .COM аллокатить себе всю остальную память? Там вроде куда-то ДОС клал количество «свободных параграфов после окончания твоего сегмента и до начала резидентов» (или до начала видеопамяти), насколько я помню.

UPD поясняю: отсутствие возможности для компилятора автоматически распределить на код и данные больше одного сегмента (.COM, модель «tiny») не означает автоматического запрета на использование остальной памяти другими средствами, включая обращение к службам ОС на ассемблере и разруливание полученных указателей на ассемблере же. Люди часто «на отвали» обращались к следующему сегменту через es, потому что на практике он почти всегда свободен, но такой метод нельзя назвать рекомендуемым.

Естественно, статически в .COM больше сегмента распределить нельзя. Или это уже не tiny и не .COM. Речь о динамическом распределении, в рантайме. Где-то у ДОСа лежали специально обученные величины, «откуда докуда» память свободна. И по причине однозадачности можно было просто прочитать их и пользоваться всем интервалом, независимо от модели памяти.

UPD²: кажется, вот это вот.

02h–03h word (2 bytes) Segment of the first byte beyond the memory allocated to the program

В PSP оно кладётся. И, насколько я помню, за неимением указания о том, сколько надо отдать — отдаётся всё, система-то однозадачная.

Вроде б, была в INT 21h функция "заполучить память после конца программы", но голову на отсечение не дам, смотреть надо.

Кстати, смутно вспоминается, что какая-то такая функция мне отдавала ноль. Типа, «у тебя и так уже есть ВСЯ память, ты же .com, чего ты ещё хочешь у меня выклянчить?»

Перепроверил — так и есть. Мои сегменты — начало адресного пространства (конкретный сегмент зависит от версии дос-подобной оси и количества в ней всякого загруженного), а зааллокачено на меня всё по самый 0x9FFF (именно это я увидел во втором слове PSP). Кому и чем мой вопрос не понравился — непонятно, но ответ — «в начале собственного единственного сегмента, в PSP, по адресу 0x02 лежит word с сегментом, к которому относится первый «запретный» байт абсолютного адреса».

Всё остальное — автоматически зааллокачено для ком-файла, и его можно использовать по своему усмотрению, если прямые руки (ибо асм). Ком-файл не так прост, как кажется — если вся игрушка вместе с переменными влезла в один сегмент, у нас ещё есть полметра под спрайты и звук, которые мы можем с диска грузить.

Но значение, на один параграф отличающееся от 0xA000, я там увидеть не ожидал, конечно.

Кому и чем мой вопрос не понравился — непонятно

Странных людей на хабре хватает :)

Но значение, на один параграф отличающееся от 0xA000, я там увидеть не ожидал, конечно

A000 -- это что, в данном случае? Сегмент, т.е. физический адрес A0000? А запрещает использовать, начиная с 9FFF0? Если да, то довольно странным выглядит. Но, может, не первый запрещённый сегмент, а последний разрешённый?

Само по себе "откусывание" какого-то объёма ОЗУ непосредственно перед A0000 -- явление нормальное, довольно многие BIOS хранят там некие расширенные данные. Но такое откусывание происходит килобайтами, поскольку именно их INT 12h возвращает, а вот чтоб параграф...

Я бы ещё не удивился, увидев первый сегмент, в котором занят хотя бы один байт. Но в сегменте 9FFF занят очень сильно не один байт :)

Видимо, так и есть — какой-то параграф статусов с флагами. Надо бы погонять на более широком круге как натуральных систем, так и продуктов дос-содержащих :)

Оверлей - кусок данных/кода, который лежит за пределами структуры exe-файла, или даже в отдельном файле. Компилятор про него ничего не знает, а программист может кусок использовать на своё усмотрение, в любой модели памяти.

Компилятор (по крайней мере борландовский) про него как раз вроде знал и добавлял соответствующий код для подгрузки кода из оверлеев. Точно уже не помню, могу ошибаться, но вроде что-то там было на уровне компилятора для работы с оверлеями.

Только вчера, на ночь глядя, в припадке ретро-кодинга накололся: Ватком, большая модель, один массив декларирован как __huge (он размером больше сегмента). При попытке в него писать любыми библиотечными средствами (хоть мемсетом), принимающими MyHuge+MyIndex, даром что результат имеет тип __huge ptr — всё равно они пишут как в __far, то есть просто игнорят то, что сегмент подошёл к концу, и в какой-то момент начинают гадить в начало сегмента (переполнение смещения).

К счастью, читать из файла надо было кусочками меньше одного сегмента, поэтому перед вызовом библиотечной функции я руками стал приводить __huge к __far — то есть тупо оставлял в смещении последние биты адреса (внутри параграфа), а всё, что можно, переносил в сегмент. Получался каждый раз большой «запас» впереди на чтение, и всё заработало.

Но обнаружил не сразу, конечно. Долго локализовывал, пока не «поймал за руку» стандартную библиотеку :) Оказывается, они не умеют в __huge :-D ну и хрен бы с ними, я умею в __huge.

DOS фортран, кстати, примерно в 1991г уже не требовал таких танцев с бубном. Если я правильно помню, мы пользовались MSF5.1, и там это делал

волшебный атрибут HUGE

Вот фрагмент readme, прилагавшегося к компилятору:

Расширения microsoft-fortran.

ALLOCATE(array([[l:]]u[[,[[l:]]u ...]])[[,STAT=ierr]]) ... array - имя располагаемого массива. ierr - целая переменная, подучающая статус размещения (0-успешно). l - целая переменная или выражение, определяющее нижнюю границу массива. u - целая переменная или выражение, определяющее верхнюю границу массива. Любой располагаемый массив должен быть быть предварительно декларирован ALLOCATABLE вместе с числом своих размерностей без указания границ (с помощью только :). Например: INTEGER dataset[ALLOCATABLE] (:,:) После описания ALLOCATABLE через запятую может указываться модель памяти: NEAR или HUGE. Модель HUGE должна использоваться для массивов длинной более 65536 байт. Модель NEAR используется по умолчанию и может не указываться (как в примере). При этом запятая также не требуется. В операторе ALLOCATE могут быть указаны несколько массивов через запятую. При этом выражение STAT=ierr должно быть последним. Попытка расположить уже расположенный массив вызывает ошибку в процессе исполнения программы. Если присутствует выражение STAT=ierr, номер этой ошибки возвращается в ierr. Ограничения: Располагаемые массивы не могут использоваться в выражениях AUTOMATIC, COMMON, DATA, EQUIVALENCE. Попытка обращения к нерасположенному массиву приводит к непредсказуемым результатам

Кстати, последняя фраза могла бы быть вишенкой на тортике недавнего обсуждения UB в фортране... К сожалению, я тогда про этот случай не вспомнил :-(

позволявший объявлять массивы более 64К, а затем работать с ними обычными способами. Впрочем, мы этим не очень активно пользовались, так как изначально решили, что рабочее пространство (с данными) у нас будет лежать не в памяти, а на диске (у нас данные уже тогда мегабайтами измерялись). А для скользящего окна 64К обычно хватало. Хотя для всяких экстремальных задачек типа БПФ или сортировки - да, приходилось весь сигнал целиком загонять в массив...

;-)

Интересно, выравнивание по параграфам кто-нибудь использовал? Ну, чтобы вообще обойтись только сегментом :) Допустим, вся структура у нас 43 байта, выравниваем до 48 и 16-битный указатель инкрементируем на 3, потому что он указывает чисто на сегмент, а смещение уже хардкодом сообразно элементу структуры :)

Или с многомерными массивами такой же фокус %) Сделать массив из структур, состоящих из одного массива, и полетели :)

А то что-то получается многовато операций с этими __huge, сначала им абсолютный адрес вынь-положь, потом его разбей на сегмент-смещение… как-то кисло и тормозно :) При строгом равенстве размера структуры параграфу — вообще сегмент становится индексом массива структур (если выровнять при аллокации не забыли), а смещение становится индексом байта в ней %)

В принципе, руками можно — наделал юнионов (которые сразу два шорта и __huge *) и крути там сегмент-смещение сообразно индексам %) можно прозрачно сопоставить их массиву MyHuge[N][16], мне кажется %)

Идея довольно крутая с точки зрения хранения указателей, мы снова возвращаемся к слову (16 бит) на указатель, отменяем всю чехарду с моделями памяти, приводя всё к этой единой.

Но проблема в том, что это не поддержано архитектурой. Нет команд типа "прибавить константу к сегментному регистру", или "загрузить сегментный регистр из ячейки памяти, напрямую или косвенно". А это значит, что код будет очень раздутым, постоянно перемещая адреса из регистров общего назначения в сегментные, данные из обычных регистры будут постоянно вытесняться этими манипуляциями.

Да, пожалуй. Главное — не только и не столько переложить

[постоянное перемещение адресов] из регистров общего назначения в сегментные

на плечи компилятора, который должен распознать выравнивание по параграфам и избавить юзера от этого вот

руками можно — наделал юнионов (которые сразу два шорта и __huge *) и крути там сегмент-смещение сообразно индексам

сведя всё просто к обычным обращениям к массивам структур; главное — скорее всё-таки

[команды] типа "прибавить константу к сегментному регистру", или "загрузить сегментный регистр из ячейки памяти, напрямую или косвенно"

просто для того, чтобы у компилятора была возможность это всё собрать не совсем уж в конченые простынки регистровых перезаписей.

чтобы у компилятора была возможность это всё собрать не совсем уж в конченые простынки

А вот не будет такой возможности.

В идеале, в архитектуре должна быть инструкция

        INC DS

или

        ADD DS, 4

для итерации по 0x40-байтовым структурам.
Тогда код получается коротким и оптимальным.

В DOS была чехарда с типами указателей и моделями памяти, но компилятор знал, что массив, переданный через small или far-указатель, не может превышать 64k, и поэтому в цикле мог инкрементировать BX и обращаться через DS:[BX]
А если передан huge указатель, то никаких гарантий на размер массива нет, и надо каждый раз пересчитывать сегмент.

В предлагаемой модели все указатели имеют единственный тип, и компилятор теряет ценную для оптимизации информацию.

В защищенном режиме в регистрах типа DS, ES и т.д. хранился не индекс сегмента (который надо умножить на 16, чтобы получить абсолютный адрес), а селектор. И была таблица селекторов (в памяти по определенному адресу, заданному в другом регистре), где говорилось, к примеру (условно): “селектор 5 ссылается на область памяти, начиная со смещения 123456, длиной 789 и с атрибутами read-only и no-exec”. Запись номера селектора в DS приводила к чтению этой структуры и сохранению ее во внутреннее состояние регистра, ассоциированное с DS (и это очень дорогая операция). Блин, не помню уже все термины.

Так что операция инкремента селектора не имела смысла. Вероятно, поэтому разработчики процессора ее и не добавили в 8086, понимая, куда будет дуть ветер в будущем.

А вообще, все эти регистры сегментов и селекторы - мертворожденная идея. Поэтому ее и выпилили в x64 в пользу обычной страничной адресации. Да и в 8086 они пожадничали и сделали шаг сегментов 16-байтным, что не позволяло адресовать больше 1М памяти даже в теории - а могли бы шаг сделать, например, 256 байтов, и тогда бы получили «забесплатно» возможность в будущем расширить архитектуру до 16М памяти без переписывания софта.

Не было никаких таблиц селекторов в 8086/80286, в регистровых сегментах действительно тупо хранилось число, которое домножалось на 16 и прибавлялось. Это Вы с таблицами страниц путаете — оно появилось, если мне не изменяет склероз, на 80386.

В 16-разрядном защищённом режиме, появившемся в 80286, были таблицы описателей (дескрипторов), а сегментные регистры содержали именно селекторы, выбиравшие описатели из этих страниц. "Число, которое домножалось на 16" -- это только для реального режима, который был единственным для 8086 и поддерживается до сих пор.

80386 в своём защищённом режиме сохранил все возможности 80286, в том числе и использование селекторов сегментов для выборки описателей этих сегментов из соответствующих таблиц (только сами описатели были расширены -- помимо унаследованных для 16-разрядного кода, добавились новые для 32-разрядного кода), но добавил ко всему этому возможность использования страниц, которую можно было включать, а можно и не включать.

Может быть, оно и с 286 началось — но я точно писал по той самой схеме с домножением на 16 безо всяких дескрипторов. Давно было.

Для реального режима писали, а не для защищённого. Это возможно и на современном ПК. Я вот на днях ухитрился поставить MS DOS на один из моих ПК (Ryzen-что-то-там, наизусть не помню), причём на SSD. Правда, в какой-нибудь там Диггер поиграть затруднительно: кнопка Turbo не предусмотрена :)

Для реального режима писали, а не для защищённого.

Естественно. Ну так ведь его и обсуждаем.

Несколько дополнений, хотя про это можно отдельную статью писать.

  1. На примере экзотичных режимов моделей памяти в x86 легко понять почему указатель на код (функцию) может не совпадать размером с указателем на данные и их нельзя преобразовать один в другой.

  2. Модели памяти используются до сих пор даже при разработке 64-битных программ на x86. Только смысл немного поменялся. Проблема в том, что в x86 нет прямой адресации всего доступного 64-битного (точнее 48-битного или сколько там сейчас тянут по максимуму?) адресного пространства ни для кода (прыжки, вызовы), ни для данных, а значит для генерации эффективного кода надо знать как далеко может "полезть" код и данные. Но обычно все компиляторы настроены на small модель в 64-битах (доступ в пределах 4Гб), редко кому нужна бывает полная адресация, но людям пишущим некоторые виды программ она бывает нужна и вот тогда начинается интересная возня.

…и PAE для 32-битных приложений, кстати, сильно напоминает эти самые сегменты…

UPD: да, написать было бы хорошо. «Сегменты наносят ответный удар» %)

PAE во-первых, не напоминает. А во-вторых — какие «эти»?

Сегменты реального режима — это одно. Сегменты защищённого режима — совсем другая вещь.

На какие именно сегменты похож по вашему PAE?

Вы — педант :) это очень ценное качество в плане Вашего каммента чуть ниже, хорошо расписали и про следующую инструкцию, и про перевод терминов… но Ваш «детектор похожести» может быть из-за этого более «узкополосный», чем мой :-D

По мне так PAE похож и на селекторы защищённого режима, и на сегментную адресацию реального режима. По мне так вообще все варианты адресации, где старшие биты задаются оптом для определённой группы адресов, похожи на сегменты в реальном режиме, а все варианты адресации, где область памяти описывается некоей таблицей (а не пишется напрямую в сегментный регистр) — на селекторы защищённого режима :) ну а если в таблице можно описать биты с более высоким весом, чем доступны напрямую в каком-нибудь esi/edi — то оно похоже и на то, и на это %)

Но я бы предпочёл об этом почитать в нормальных формулировках и с техническими деталями, а не рассказывать своё поверхностное о нём знание, да ещё в формулировках типа «хвост от бобра, уши от зайца».

Проблема в том, что в x86 нет прямой адресации всего доступного 64-битного (точнее 48-битного или сколько там сейчас тянут по максимуму?) адресного пространства ни для кода (прыжки, вызовы), ни для данных

Но обычно все компиляторы настроены на small модель в 64-битах (доступ в пределах 4Гб), редко кому нужна бывает полная адресация

Поясните, что вы имеете в виду.
Обычный сишный код

#include <ctype.h>
#include <stdio.h>
#include <array>

char *messages[2] = { "OK", "CANCEL" };

int main() {
    for (std::size_t i = 0; i < std::size(messages); i++) {
        puts(messages[i]);
    }
    return 0;
}

Оперирует с полноразмерными 64-битными указателями (sizeof(char*) == 8).

Я могу поправить указатель в массиве куда угодно, за пределы 4GB-окна, и это будет работать (если по указанному адресу есть корректная строка)

    messages[1] = (char*)0xAAAABBBBCCCCDDDDEEEEFFFF;

С вызовами чуть сложнее, но тоже нет никакой привязки к "модели памяти". Есть короткая форма переходов, в пределах 256 байт, обычная (в пределах 4GB) и всегда доступна "длинная", по всему 64-битному пространству.

Если говорить о классических трансляторах (компилятор → объектный файл → линковщик → бинарный файл), то в объектном файле должны быть конкретные инструкции переходов (короткие или длинные), которым линковщик подставляет смещения. Но сейчас это всё уходит в историю, потому что новые языки обходятся без объектных файлов, а то и вовсе делают jit-компиляцию. А классические C++ всё больше напирают на Link-time Code Generation оно же Whole Program Optimization, где в объектных файлах нет машинного кода, а он генерится линкером, и таким образом, линковка модулей может быть оптимально выполнена вне зависимости от "расстояния" между функциями.

В случае же динамической линковки, переходы между модулями (меджу программой и ОС, между DLL-модулями) всегда идут по 64-битному адресу, где вы увидели "small" модель?

Скорей всего, произошло смешивание в одну кучу имеющегося набора команд AMD64/Intel64 и разрядности адресов, используемых процессорами этой архитектуры.

"48-битный или сколько там" -- это, скорей всего, про физический адрес (сколько линий адреса выведена из процессора наружу -- к контроллеру памяти и на шину) и про то, сколько разрядов виртуального адреса можно фактически использовать при адресных вычислениях в процессоре. В этой архитектуре, спасибо AMD, а затем и Интелу, принято откровенно дурацкое решение ограничить виртуальные адреса в фактической разрядности, см. "канонические адреса" в описании архитектуры (в ряде других 64-разрядных архитектур, в частности, в z/Architecture, виртуальный адрес является всегда 64-разрядным независимо от того, сколько разрядов физического адреса выходит потом наружу -- т.е. нет ограничений на используемые виртуальные адреса).

В плане же системы команд, насколько помню, нет возможности в одной команде прямо указать 64-разрядный адрес. Т.е. в 16- или 32-разрядном коде можно написать что-то вроде MOV EAX, addr32, где addr32 -- полный 32-разрядный адрес памяти; а в 64-разрядном коде такой возможности нет, и сформировать 64-разрядный адрес можно только косвенно -- используя сумму 64-разрядного содержимого какого-либо регистра и заданное в команде 32-разрядное смещение.

Это, конечно, создаёт некоторые затруднения, но, на самом деле, крайне небольшие, и их уже много десятилетий компиляторы успешно решают на других платформах. Скажем, в Системе 360 (из которой выросла упоминаемая мною выше современная z/Architecture) в команде можно задать только 12-разрядное смещение (называемое displacement) относительно содержимого какого-либо из общих регистров, а прямой адресации памяти там нет в принципе (не считая возможности прямо адресовать младшие 4 Кбайта памяти -- для чего как раз хватает разрядности смещения). В ARM то же самое: адресация памяти всегда ведётся относительно регистров, а не прямо.

В плане же системы команд, насколько помню, нет возможности в одной команде прямо указать 64-разрядный адрес. Т.е. в 16- или 32-разрядном коде можно написать что-то вроде MOV EAX, addr32, где addr32 -- полный 32-разрядный адрес памяти; а в 64-разрядном коде такой возможности нет, и сформировать 64-разрядный адрес можно только косвенно -- используя сумму

Такое есть в ARM32/64, по понятной причине: размер инструкции фиксирован, 32 бита, и 32-битный операнд не помещается в инструкцию. Там приходится загружать "половинками".

В x64 нет таких ограничений, можете написать любую программу, оперирующую длинными 64-битными числами, и посмотреть дизассемблер, хоть в оnline на godbolt.
Пример кода:

movabs rdx, 0x1234567812345678    # 48 ba 78 56 34 12 78 56 34 12
movabs rax, 0xccccddddeeeeffff    # 48 b8 ff ff ee ee dd dd cc cc

Загрузить в регистр можно, а прямо указать в качестве адреса данных, вроде бы, нельзя. Ну а если адрес в регистре, то получаем уже адресацию относительно регистра, о чём я и говорил.

в x86 нет прямой адресации всего доступного 64-битного (точнее 48-битного или сколько там сейчас тянут по максимуму?) адресного пространства ни для кода (прыжки, вызовы), ни для данных

абсолютно ложное утверждение, все пространство адресуется напрямую, например через 64х битные регистры:

call rax

mov rax, [rbx]

Сначала б почитали, о чём выше писалось. Адресация всего адресного пространства в 64-разрядном режиме -- не прямая, где адрес является частью кода команды, а косвенная -- через содержимое регистра (или индексная -- адрес формируется как сумма базового адреса в регистре и индекса). Вот в 32- и 16-разрядных режимах прямая адресация тоже имеется.

абсолютно ложное утверждение

Интуитивно кажется, что "прямая адресация" - это доступность любого адреса без доп. ухищрений, как переключение страниц и т.п.

Но это вполне конкретный термин из замшелых советских учебников, и он обозначает указание абсолютного адреса сразу за опкодом инструкции. Такого я не нашёл в системе команд x64, видимо разработчики из AMD посчитали, что это слишком громоздко и непрактично.

Если обратиться к той же статье, в английской редакции, то в английской литературе эта адресация называется immediate, а direct обозначает, что адрес не является результатом арифметической операции, то есть приведённые выше

call rax
mov rax, [rbx]

это register direct.

Обычно в английском immediate означает непосредственную адресацию, когда в команде задано значение операнда, а не его адрес. Например, в современном описании архитектуры Intel64:

If an instruction specifies an immediate operand, the operand always follows any displacement bytes. An immediate operand can be 1, 2 or 4 bytes.

Такое же использование термина immediate имеет место и в других архитектурах -- как древних (скажем, PDP-11, System/360, 6502, 8080), так и современных, вроде ARM. Вот, например, из описания на 6502:

Хотя мы видим, что названо "immediate addressing", но из описания видно, что имеется в виду операнд, являющийся частью кода команды (в данном случае -- её вторым байтом), а не лежащий где-то ещё в памяти.

Почему в той статье решили использовать это слово для обозначения адреса, а не значения операнда, мне неизвестно, но это явно не устоявшаяся терминология, а её нарушение.

Indirect повсеместно используется для случаев, когда в коде команды указывается не адрес операнда, а местоположение адреса (адрес адреса). Вот, например, в том же 6502 обе его косвенные адресации (довольно специфические :) ):

Direct же -- противоположность indirect, т. е. команда прямо задаёт адрес операнда. Хотя в разных архитектурах могут использоваться разные термины. Скажем, в PDP-11 и в 6502 используют термин "абсолютная адресация".

Ну и, в любом случае, в 64-разрядном режиме прямо в команде указать адрес операнда нельзя, только через регистры -- именно об этом говорилось.

Почему в той статье решили использовать это слово для обозначения адреса, а не значения операнда, мне неизвестно, но это явно не устоявшаяся терминология, а её нарушение.

Словосочетания

call immediate

jump immediate

у вас ничего в памяти не триггерят?

Нет, не триггерит.

Вообще говоря, названия методов адресации относятся, в первую очередь, к адресации данных, а не к адресам команд переходов, и все рассуждения выше (о невозможности прямо указать 64-разрядный адрес в AMD64/Intel64 и т.п.) относятся именно к адресам операндов в памяти, а не к переходам.

Комментатор, который начал эту ветку, заметил, что это справедливо и для данных, и для кода

в x86 нет прямой адресации всего доступного 64-битного (точнее 48-битного или сколько там сейчас тянут по максимуму?) адресного пространства ни для кода (прыжки, вызовы), ни для данных

Статья wiki (англ. редакция), которую я приводил выше, равноправно рассматривает и адресацию данных, и переходов.

Короткий указатель занимает всего один байт и выражает адрес, записанный относительно той инструкции, которая сейчас выполняется.

Автор написал херню, переводчик спокойно прошел мимо и перевёл, комментаторы тоже спокойно проходят мимо.

Относительные адреса переходов всегда задаются не относительно текущей, а относительно следующей инструкции.

Таким образом JMP с нулём в машинном коде (а не ассемблерном листинге) не порождает бесконечный цикл, а просто переходит к следующей инструкции.

А в целом, раздражает, что относительные адреса, которые используются исключительно в инструкциях CALL/Jxx, автор называет указателями. Они никакие не указатели, а именно что смещения, и от модели памяти и разрядности сегмента в их отношении ничего не меняется.

Зато автор умалчивает о существовании вариации JMP инструкции, которая принимает абсолютный полный (far) адрес и о том, что jmp/call могут косвенно ссылаться на хранящийся в памяти near/far адрес.

Поскольку каждый из таких файлов загружается в один сегмент, а длина сегмента составляет не более 64 КБ, самый крупный COM-файл может иметь размер 64 КБ минус 256 байт спереди, резервируемые для подсистемы PSP.

В 98 винде, в ДОСовском куске есть command.com. Он занимает не 64 КБ, а гораздо больше 95202 байта. Как они это сделали? подозреваю, что это не ком файл, а переименованный экзэшник. Ещё в учась в универе спрашивал наших гуру, но никто внятно объяснить не мог. Некоторые ссылались на какие-то недокументированные возможности.

Об этом в википедии написано

Files may have names ending in .COM, but not be in the simple format described above; this is indicated by a magic number at the start of the file. For example, the COMMAND.COM file in DR DOS 6.0 is actually in DOS executable format, indicated by the first two bytes being MZ (4Dh 5Ah), the initials of Mark Zbikowski.

Это именно EXE-файл, достаточно глянуть его в любом просмотрщике. DOS определяет, что за файл ей подсунули, не по расширению, а по его началу: видит сигнатуру EXEшника -- обрабатывает соответствующим образом.

Вроде б, с этим даже уязвимости были что в ДОСе, что в Винде: можно подсунуть файл с расширением, как у, скажем, картинки, а в действительности EXEшник, и система его запускала. Но не помню, действительно ли такое было, а искать лениво :)

Спасибо.

Ну я так и думал, что просто переименовали экзэшник. Залез внутрь ФАРовским вьювером, и точно: там прямо вначале MZ.

Вообще .COM — идеальный формат для «домашнего пека с программами на кассетах и выводом на телевизор», который, увы, не состоялся… @bodyawm грозился написать игру «как если бы оно тогда взлетело», опирающуюся только на средства BIOS (как если бы у нас была голая материнка с CGA и воткнутые в неё магнитофон, телек и клава), но его на всё не хватает, увы %)

Ну, собсно, подобная конфигурация -- почти типичный IBM PC. У него же флопов могло и не быть, а вот поддержка магнитофона была всегда (позже её выпилили). Правда, вместо CGA мог быть MDA, а оперативы могло быть ващще 16 Кбайт.

Ага. Потому и выпилили, что софтовая сторона поддержки была «хуже, чем никакая». Какой-то кассетный бейсик вместо загрузчика .COM-файлов, которые были прекрасно знакомы IBM, перешли с CP/M в MSDOS и, соответственно, в IBM PC DOS и любой BIOS здорового человека содержал бы в первую очередь загрузчик .COM-файла с кассеты, а уже во вторую, третью и сотую — всякие васики.

Если у них была очень дешёвая ПЗУ, в принципе, я могу понять их BIOS курильщика — в 16 кб оперативы BASIC.COM с кассеты грузить как-то не очень. А бинарный код «виртуальной бейсик-машины» очень компактен, и на нём можно в 16 К уложить хоть что-то.

Но всё равно это BIOS курильщика, потому что проектировать систему, изначально заточенную под расширение адресации с 64К до 640К, под наличие только 16К — архитупизм, особенно ценой потери производительности и без того не самого быстрого 8086 или даже 8088. Причём первые материнки расширялись только до 64 К, как будто завтрашнего дня не будет.

И самое «весёлое» — протупить, что в непопулярности порта виноват не порт, а BIOS. Ох дооооолго IBM была для домашнего юзера дороговата из-за необходимости в дисках…

Угу, мне даже подписчик подарил целый IBM PC полноценный, 486'ой в сборе! Я даже купил COM TO RS232 для еще одного проекта... но пока занимаюсь другими девайсами. Вот, например, ноутбуком на MIPS.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий