Некоторое время назад я написал несколько статей о различных трюках, применявшихся в операционной системе DOS, чтобы вписаться в те жёсткие лимиты памяти, которые действовали в реальном режиме на архитектуре x86. Постоянно возникал и оставался без ответа один вопрос: а каковы были различные «модели», которые предлагались компиляторами тех времён? Взгляните, как выглядело меню для генерации кода в Borland Turbo C++:

Tiny (крошечный), small (маленький), medium (средний), compact (компактный), large (большой), huge (огромный)… Что означают эти опции? Каковы их эффекты? Ещё важнее… а так ли важен весь этот антиквариат сегодня, в мире 64-разрядных машин и гигабайтных ОЗУ? Чтобы ответить на этот вопрос, сделаем небольшой обзор архитектуры 8086 и тех двоичных форматов, которые поддерживались в DOS.
❯ Сегментация 8086
В архитектуре 8086 — именно той, для которой проектировалась операционная система DOS — ссылки в памяти состоят из двух частей: 2-байтного сегмента «идентификатора» и 2-байтного сдвига внутри сегмента. Эти пары часто выражаются как segment:offset
.
Сегменты — это непрерывные 64-килобайтные участки памяти, каждый из которых идентифицируется по собственному базовому адресу. Чтобы можно было адресовать целый 1 МБ памяти, поддерживаемый 8086, между сегментами оставляют зазоры по 16 байт каждый. Соответственно, сегменты частично перекрываются, и поэтому на конкретное местоположение в физической памяти может указывать множество ссылок вида сегмент/сдвиг.

Например, сегментированный адрес B800h:0032h
соответствует физическому адресу B8032h
, который вычисляется как B800h * 10h + 0032h
. Притом, что эта пара является человеко-читаемой, на уровне инструкций машинного кода она запрограммирована иначе. При работе инструкции опираются на регистры сегментов, и в 8086 поддерживается четыре таких регистра: CS (сегмент кода), DS (сегмент данных), ES (дополнительный сегмент данных) и SS (сегмент стека). Зная это, для доступа к произвольной позиции в памяти требуется сначала загрузить B800h
в DS,
в потом поставить ссылку DS:0032h
.
При обращениях к памяти инструкции опираются именно на регистры сегментов, а не на идентификаторы сегментов не в последнюю очередь по соображениям эффективности. Чтобы закодировать регистр сегмента, требуется всего 2 бита (всего у нас будет 4 регистра сегментов) — по сравнению с 2 байтами, которые понадобились бы, чтобы сохранить базу сегмента. Подробнее об этом ниже.
❯ Файлы COM
Файлы COM — это максимально тривиальный формат исполняемых файлов, какой только можно себе представить. В них содержится необработанный машинный код, который можно разместить практически в любой области памяти, а после выполнения он не потребует никакой постобработки. Не будет никаких перемещений, не применяются разделяемые библиотеки, вообще не о чем беспокоиться. Можно просто скопировать двоичный файл в память как блок битов и запустить.
Такой механизм работает благодаря тому, как устроена сегментированная архитектура 8086: COM-образ загружается в любой сегмент памяти, причём, всегда со сдвигом 100h в рамках этого сегмента. Все адреса памяти в образе COM отсчитываются относительно этого сдвига (именно поэтому существует конструкция ORG 100h
, возможно, уже попадавшаяся вам ранее). При этом образу не требуется знать, какой именно сегмент был загружен. Загрузчик (в нашем случае это DOS, но вообще файлы COM происходят из CP/M) устанавливает CS, DS, ES и SS именно в этот сегмент и передает управление CS:100h
.
Магия! COM-файлы — это, в сущности, файлы PIE (Исполняемый код, не зависящий от адреса), и для работы с ними не требуется никаких блоков управления памятью (MMU) или причудливых приёмов управления со стороны ядра.
К сожалению, не всё так радужно. COM-файлы ограничены по размеру, и в этом заключается проблема с ними. Поскольку каждый из таких файлов загружается в один сегмент, а длина сегмента составляет не более 64 КБ, самый крупный COM-файл может иметь размер 64 КБ минус 256 байт спереди, резервируемые для подсистемы PSP. В этот объём должны уместиться и код, и данные, а 64 килобайта — в принципе немного. Естественно, при работе программа COM полностью владеет процессором и может обращаться к любым областям памяти вне отдельно взятого сегмента, сбрасывая значения регистров CS, DS, ES и/или SS, но всё управление памятью остаётся на долю программиста.
❯ Файлы EXE
Чтобы справиться с ограничениями COM-файлов в DOS, Microsoft предложила для этой системы иной исполняемый формат: EXE-файлы, также известные под названием MZ-исполняемые файлы.
EXE-файлы отличаются от COM-файлов наличием внутренней структуры; при этом они не ограничены лимитом в 64 КБ. Соответственно, в них могут содержаться более крупные блоки кода и более объёмные данные. Но… как же так получается, учитывая, что размер в 64 КБ — это потолок для сегментов 8086? Ответ прост: в EXE-файле содержится множество сегментов, код и данные распределены по ним.
Чтобы можно было поддерживать множество сегментов во время исполнения, в заголовках EXE-файлов содержится информация о перемещении (relocation). В сущности, из этой информации загрузчик узнаёт, в каких позициях образа двоичного файла могут содержаться «неполные» указатели. Такие указатели потребуется исправить на уровне базовых адресов сегментов после того, как они будут загружены в память. DOS в данном случае действует в качестве загрузчика, и именно она отвечает за такое пропатчивание.
Но, всё-таки, сколько сегментов входит в состав EXE-файла? Зависит от ситуации, так как у разных программ разные нужды. Есть программы, которые в принципе настолько крошечные, что умещаются в единственном COM-файле. В других программах содержатся большие объёмы данных, но мало кода. Бывают и программы, в которых содержится множество кода и данных. И т.д.
В таком случае возникает вопрос: каким образом EXE-формат, рассчитанный сразу на все эти варианты, может эффективно их поддерживать? Именно для этого и становятся важны модели памяти, но, прежде чем поговорить о них, сделаем ещё одно отступление и разберём типы указателей.
❯ Типы указателей
Согласно принципу локальности, «обычно в течение краткого промежутка времени процессор обращается к одному и тому же множеству адресов в памяти». Это вполне логично: обычно код выполняется почти последовательно, а данные упаковываются в виде следующих друг за другом фрагментов памяти, например, массивов или структур.
Именно поэтому было бы расточительно пытаться выразить все адреса в памяти в виде 4-байтных пар segment:offset
. Именно в данной ситуации сегментация 8086 снова оборачивается в нашу пользу. Сначала можно загрузить регистр сегмента, содержащий базовый адрес «всех наших данных». После этого нам остаётся просто записать адреса как сдвиги в рамках этого сегмента. Чем реже приходится перезагружать регистры сегментов — тем лучше, поскольку уменьшается объём той информации, которую требуется переносить туда-сюда в каждой инструкции и в каждой ссылке на память.
Но не получится просто в любом случае обойтись сдвигами в рамках отдельно взятого сегмента, так как, возможно, нам придётся иметь дело не с одним, а с двумя и более сегментами. Сдвиги также бывают разных размеров, поэтому также было бы расточительно подбирать общий размер, в который умещались бы любые из них. Таким образом, нужно предусмотреть адреса в памяти или указатели разных размеров и форм, так, чтобы на любой практический случай нашёлся указатель, который лучше всего подходит именно для него.
Короткий указатель занимает всего один байт и выражает адрес, записанный относительно той инструкции, которая сейчас выполняется. Такие адреса часто используются в инструкциях переходов, чтобы их двоичное представление оставалось компактным. Переход происходит в любой условной конструкции или цикле, а зачастую ветка условного перехода или тело цикла настолько коротки, что целесообразно минимизировать объём кода, нужного для выражения этих точек ветвления.
При помощи ближних указателей можно ссылаться на адреса в пределах 64-килобайтного сегмента, которые, как подразумевает контекст, имеют по 2 байта в длину. Например, в инструкции вида JMP 12829h
обычно не требуется информация о сегменте, на который ссылается этот адрес, поскольку переходы почти всегда осуществляются в пределах того же CS, в котором находится код, спровоцировавший переход. Аналогично, инструкция вида MOV AX, [5610h]
предполагает, что заданный адрес содержит ссылку на выбранный DS, поэтому не приходится каждый раз выражать сегмент. Сдвиг, закодированный в ближнем указателе, может быть относительным или абсолютным.
Дальние указатели могут ссылаться на любой адрес в памяти, кодируя как соответствующий сегмент, так и его сдвиг. Они имеют по 4 байта в длину. При использовании в арифметике указателей сегмент остаётся фиксированным, варьируется только смещение. Это важно, например, при переборе массивов, поскольку мы можем всего один раз загрузить базовый адрес в DS или ES, а затем оперировать сдвигом в пределах сегмента. Правда, это означает, что размер такой итерации может составить не более 64 КБ.
Огромные указатели напоминают дальние в том, что также имеют по 4 байта в длину и могут ссылаться на любой адрес в памяти, но при работе с ними не действует ограничение на 64 КБ в контексте арифметики указателей. Дело в том, что такие указатели заново вычисляют участки сегмента и сдвига при каждом обращении к памяти (напомню, что сегменты перекрываются, так что для любого физического адреса у нас получается множество пар «сегмент/сдвиг». Понятно, что для этого при каждом обращении к памяти требуется дополнительный код, поэтому огромные указатели заметно обременяют любую работу во время выполнения.
❯ Модели памяти
Итак, мы уже немало узнали о сегментации в 8086, EXE-файлах и типах указателей. Теперь мы, наконец-то, можем связать все эти феномены вместе, и окажется, что нет ничего таинственного в тех моделях памяти, что применяются в старых компиляторах, рассчитанных на работу с DOS.
Разберём по порядку:
Крошечная: это модель памяти, действующая в COM-образах. Вся программа умещается в одном 64-килобайтном сегменте, и все регистры сегментов также устанавливаются именно в его пределах, это необходимо для запуска программы. Таким образом, все указатели в рамках программы являются короткими или близкими, так как они всегда ссылаются на один и тот же 64-килобайтный сегмент.
Малая: повсюду используются близкие указатели, но сегменты с данными и стеком отличаются от сегмента с кодом. Таким образом, в программах отводится по 64 КБ на код и 64 КБ на данные.
Компактная: для кода применяются короткие указатели, а для данных — дальние. Соответственно, такие программы могут задействовать под данные пространство памяти в 1 МБ. Поэтому они особенно полезны в играх, где код должен располагаться настолько плотно, насколько это возможно, и при этом в нём нужно предусмотреть возможность быстро загрузить все ресурсы в память и расставить ссылки на них.
Средняя: противоположна компактной. Для работы с кодом используются дальние указатели, а для работы с данными — короткие. Эта модель странная, поскольку, если у вас есть программа с большим количеством кода, то логично ожидать, что в ней также будет обрабатываться много данных.
Большая: повсюду используются дальние указатели, поэтому и код, и данные могут в полном объёме ссылаться на всё адресное пространство размером 1 МБ. Однако, в силу самой природы дальних указателей, все сдвиги в памяти составляют по не более чем по 64 КБ, поэтому размеры структур данных и, в частности, массивов, получаются ограниченными.
Огромная: повсюду используются огромные указатели. В результате удаётся обойти все ограничения, предыдущей большой модели, поскольку гигантская модель выдаёт код, который вычисляет абсолютные адреса при каждом обращении к памяти и допускает создание массивов и структур, занимающих в памяти по 64 КБ и более. Естественно, за это приходится платить: код программы увеличивается, и издержки во время исполнения теперь гораздо больше.
Вот и всё!
Стоит подчеркнуть, что все эти модели есть соглашения, которыми старинный компилятор C руководствовался при порождении кода. Если вы пишете на ассемблере вручную, то можете по своему усмотрению сочетать и смешивать указатели разных типов и делать всё, что вам захочется, поскольку сами эти концепции не имеют никакого особого значения на уровне операционной системы.
❯ Развитие до современного состояния
Всё, о чём я рассказал в этом посте — это легаси, и вы вполне можете отбросить эту информацию как ненужную. Или нет?
В этом посте я не затронул, в частности, плотность кода и то, как она связана с производительностью. Ваш выбор указателей для кода прямо сказывается на плотности кода. Вот почему вычислительная техника развилась от 16-битных машин, таких, как 8086, до современных 64-битных машин. Представления указателей сильно выросли, и теперь нам то и дело приходится сталкиваться со сложным выбором.
Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩

? Читайте также:
Комментарии (74)
nv13
08.02.2025 08:19Интересно, конечно) Вся страна играла в Wolfenstein, а потом в Doom, но пользовалась Борландом вместо Watcom, в котором под DOS4GW (и нескольких других менее популярных экстендерах) был легко доступен 32 разрядный режим с линейной адресацией. Почему?) Возможно потому, что в Watcom не было IDE с контекстным хелпом и встроенных графических средств конфигурирования проектов.
mpa4b
08.02.2025 08:19Хуже. 386 появился в ~1986, а микрософт ещё лет 15 после этого не решался отказаться от всего этого 16-битного убожества.
adeshere
08.02.2025 08:19Обратная совместимость, не?
CrashLogger
08.02.2025 08:19Ну и пусть старые программы работают как работали, а новые используют 32-битную адресацию. В Windows 95 так оно и реализовано.
Gredko
08.02.2025 08:19Хуже. 386 появился в ~1986, а микрософт ещё лет 15 после этого не решался отказаться от всего этого 16-битного убожества
Вроде бы Windows 95 появилась в 95-м году?
Через 9-ть лет...
А Xenix и OS/2 и того раньше...
mentin
08.02.2025 08:19386 был жутко дорогой, даже в 92ом Windows 3.1 вынужден был поддержать standard mode (286), и 386 enhanced mode для тех кому повезло.
NickDoom
08.02.2025 08:19Жаль, что «ДОС Четырежды ЖИВой» не дорос до полноценной «ОС поверх ОС», типа Win95… вложиться бы в него тогда как следует — не пришлось бы столько лет иметь дело с мастдаями (пока дебианы не подросли до нынешних убунто-минтов).
Страшная, тёмная эра мрака между «досом четырежды живым» и сошествием благодетельной Убунты :)
Но цепочка легаси бы былааааа… ой :) «В любой непонятной ситуации обращайся к 16-битному драйверу или службе BIOS». Хотя, с другой стороны, это гораздо лучше, чем «ваш драйвер не подходит для этой версии мастдая» и всё, финиш! Лучше уж при отсутствии 32-битных дров уходить в легаси со всеми причитающимися тормозами и тормозящими причитаниями…
CrashLogger
08.02.2025 08:19Жаль, что не вышла 32-битная версия DOS, однозадачная, консольная, но с полным доступом ко всей памяти.
Gredko
08.02.2025 08:19А зачем однозадачность-то?
Чтобы что?
В процессоре она есть, а в ОС нужно меры для ее ограничения применить?
CrashLogger
08.02.2025 08:19Чтобы сохранить главную фишку DOS - прямой доступ к железу. Программа монопольно владеет компьютером и делает с ним что хочет. В многозадачной системе это невозможно.
SIISII
08.02.2025 08:19Вообще, это от системы зависит.
В RSX-11M (бабка Винды) и в VAX/VMS (мамаша Винды) привилегированная задача могла получить доступ к железу напрямую, попросив об этом ОС (что позволяло писать драйверы, являющиеся не частью ядра системы, а задачами режима пользователя; в частности, в RSX-11M именно так были сделаны драйверы файловых систем). Вот непривилегированная задача получить доступ к железу не могла.
А в OS/360 была вообще макрокоманда супервизора (т. е. функция API ядра системы) с весьма говорящим названием MODESET. Как говорится, угадайте с трёх попыток, что она делает :) (и да, она, в принципе, могла использоваться кем угодно, если это не было специальным образом заблокировано и оставлено лишь для избранных).
Gredko
08.02.2025 08:19Так начиная с DOS 3.3, если не ошибаюсь "прямой доступ к железу" - иллюзия.
80286 имел несколько конвейеров и семафоры для вытесняющей многозадачности.
Механизм виртуализации памяти, правда, не был доделан из-за чего Microsoft Xenix не пошел.
На 80386 вполне себе запускался SCO Unix и Free BSD. Тот же Microsoft Xenix в 91-м вполне себе крутился на PC AT 386...
SIISII
08.02.2025 08:19Нет, все ДОСы сами по себе работают в реальном режиме, и поэтому программа имеет полный доступ к любому железу.
80286 имел несколько конвейеров и семафоры для вытесняющей многозадачности
Насчёт конвейеров и семафоров не понял, но для вытесняющей многозадачности не нужно ничего, кроме таймера -- чтобы по прерыванию снять задачу, слишком долго занимающую процессор. Всё это имелось уже в самом первом ПК на 8088. Защита памяти для многозадачности не требуется (она позволяет защищать задачи и систему, но не нужна для переключения задач, т. е. для собственно многозадачности).
Механизм виртуализации памяти, правда, не был доделан из-за чего Microsoft Xenix не пошел
Xenix не пошёл не из-за механизма виртуализации памяти 80286 как такового, а из-за этого, что этот механизм не позволял сохранить совместимость с ДОС, а значит, исключал использование в Xenix уже весьма и весьма многочисленных программ, написанных под ДОС. В 80836 ввели режим виртуального 8086 (по сути, не настоящий режим, а подрежим защищённого 32-разрядного режима), что и позволило создать систему, способную выполнять как собственные программы, так и программы ДОС.
Кстати, в современных 64-разрядных системах использовать программы для ДОС нельзя, поскольку режим V86 из 64-разрядного режима был выпилен, он может использоваться лишь в 32-разрядном защищённом режиме.
Gredko
08.02.2025 08:19Вы бы подкрепляли то, что вещаете какими-то аргументами.
Ваши убеждения - не аргумент, даже если они крепкие...
Я в свое время провел довольно много времени за попыткой запуска Microsoft Xenix на PC AT 286. На которой он будто бы "работал".
А те, кто в теме - прослезились наверное, прочитав про то, что семафоры с конвейерами для многозадачности не нужны.
Вы гигант мысли прямо...
CrashLogger
08.02.2025 08:19Многозадачные ОС существуют даже на микроконтроллерах, без всяких конвееров и семафоров. Конвеер - это вообще из другой оперы, он никак с многозадачностью не связан. Он нужен для того, чтобы выбирать из памяти следующую команду, пока идет обработка предыдущей.
А Xenix вполне себе работал на 286, но был там никому не нужен. Юниксоиды сидели на PDP и VAX, а на PC правил балом DOS.Gredko
08.02.2025 08:19А Xenix вполне себе работал на 286, но был там никому не нужен.
Поделитесь, пожалуйста, своим опытом где он работал и что на нём запускалось.
Я тоже видел работающие системы на Xenix для 286.
Определенный набор программ с инструкцией, как можно запускать, а как нельзя.
Я же откуда-то взял его дистрибутив.
Долго пытался запустить задачу. Думал, что память глючит.
И только изучение с помощью nmon/pmon состояния "ненужных" семафоров с конвейерами позволили понять, что причина не в неисправной памяти.
Alesh
08.02.2025 08:19Видимо вам просто не повезло. Например 286 с Xenix шли одно время, как рабочие места к немецким телефонным станциям EWSD. Все прекрасно работало, я даже переставлял после замены HDD, коробка 3,5 дискет в комплекте была. Не помню никаких проблем.
Компы, если не изменяет память были PS/2
Gredko
08.02.2025 08:19Видимо вам просто не повезло. Например 286 с Xenix шли одно время, как рабочие места к немецким телефонным станциям
В каком-то смысле - да.
С другой стороны я приложил к обоснованию того, что нужны i386 перевод из "эхи"
И мне их дали.
А разве были PS/2 на i286 ?
Вроде бы OS/2 сразу в защищённом режиме запускалась.
Вы не перепутали i286 и i386SX?
Gredko
08.02.2025 08:19Наверное это были PS/1?
Было такое чудо.
Поговаривали, что на них работал защищённый режим памяти.
ИБМ-овский контроллер памяти ECC был особенный и драйвер этого контроллера исправлял багу процессора.
Но поставить на нее Xenix я не сподобился.
Alesh
08.02.2025 08:19PS/2 (англ. Personal System) — серия персональных компьютеров компании IBM на процессорах серий Intel 80286 и Intel 80386, выпускавшаяся с апреля 1987 года.
Gredko
08.02.2025 08:19Да. Действительно.
Были двойки PS/2. Нашел сканы буклетов IBM 89-90гг
Не встречал таких, но я и начал свои упражнения с PC в 91-м.
Может быть, что и работал Xenix на 286-х PS/2.
Ну и наверняка работал раз Clipper for Xenix и у нас не глючил, но на время впадал в медитацию при перегрузке.
SIISII
08.02.2025 08:19OS/360 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая.
RSX-11 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая.
Ну а какие конвейеры с семафорами Вы нашли в 80286, для меня остаётся загадкой. Хоть бы пояснили, что ли.
Gredko
08.02.2025 08:19Вы бы не рассказывали мне об IBM 360/370/SystemZ и об архитектуре процессоров...
Не знаю, чем Вы там занимаетесь, но, если "программатор" вдруг не видит под слоем микропрограмм семафоров и конвееров - не значит, что их там нет ;)
Вернее - в SystemZ семафоров нет, поскольку многозадачность "невытесняющая".
А конвееры - на месте.
И почему Вы решили привести в качестве примера System 360?
Дедушкин учебник по ней в сети нашли?
Больше неоткуда "скачать бесплатный реферат"?
"Если на клетке со львом написано "Слон" - не верь глазам своим." (С) Козьма Прутков.
Gredko
08.02.2025 08:19OS/360 -- конвейеров нет, семафоров нет, даже защита памяти не обязательна -- а многозадачность есть. Вытесняющая
Это Вы сейчас о чем?
Внутри ВМ или в MVS или вообще о варианте с набором микропрограмм для AIX?
А версия S360 - какая?
Почему S360?
S370 и S390 чем-то не устраивают?
Ну а какие конвейеры с семафорами Вы нашли в 80286, для меня остаётся загадкой.
Откройте руководства от Интел по этой серии - разгадайте для себя загадку...
Ключевые слова "BU", "EU"...
Yami-no-Ryuu
08.02.2025 08:19Фигню пишете. Всё это реализуется софтово, с оверхедом о-го-го. Но реализуется. Более того, есть форки Линукс для процессоров без MMU вообще. (А не так давно было и в основном ядре)
Gredko
08.02.2025 08:19Фигню пишете. Всё это реализуется софтово, с оверхедом о-го-го
Почему же..
Я не болтаю, а привожу пример.
Из своего личного опыта.
Оно, конечно, дебил "малтитаскин" и на таймаутах реализует.
Как в первых айфонах.
Но разработчики FoxBase "отбарабанили" свой продукт по гайду.
Я когда отослал отчёт из PMON в тематическую эхо-конференцию - мне ответили, что мол это известный баг 80286. Архитектурно-зависимый.
Посоветовали развернуть это на i386 и не париться...
А Ваши советы основаны на чем-то кроме фантазий?
Про проблемы малтитаскинга без аппаратной поддержки ещё Керниган с Пайком писали.
Вы читали отцов-основателей перед раздачей советов?
Gredko
08.02.2025 08:19Кстати, позже, после развертывания сервера на i386 был найден Clipper(Clip?) for Xenix.
Он отрабатывал без сбоев на i286
Иногда задумывался о чем-то своем как ранний айфон, но стабильно выходил из состояния задумчивости рано или поздно.
Но и люди уже привыкли к хорошему да и БД на DBaseVI было написано достаточно.
Gredko
08.02.2025 08:19Xenix не пошёл не из-за механизма виртуализации памяти 80286 как такового, а из-за этого, что этот механизм не позволял сохранить совместимость с ДОС,
Освежил даты...
Xenix для 80286 в "защищённом режиме" вышел в 1983 году.
исключал использование в Xenix уже весьма и весьма многочисленных программ, написанных под ДОС
Тогда же был анонсирован XEDOS.
"Многочисленными программами " в 1983 году и не пахло.
Я Xenix пытался запускать на "двойке" в связи с желанием работать на Microsoft FoxBase на текстовых терминалах.
В "двойках" многозадачный режим постоянного вылетал на операциях с памятью.
На "тройке" и Xenix и его брат SCO Unix завелся и работал без проблем...
Если бы в 1983 году Xenix на двойках пошел бы, то неизвестно как бы выглядела программная среда в последующие годы.
NickDoom
08.02.2025 08:19Как я уже говорил (поиск по камментам бы…) — фридос надо реализовать как модуль уефи, одно ядро — одна задача, и никакого квантования. Индустриальная реалтаймовая оська «искаропки», даже без загрузки, по щелчку питания. И творите через порты, чего душе угодно.
Можно и 32-битную, со вшитым дос4гв. Даже скорее «нужно».
litos
08.02.2025 08:19А UEFI приложения точно запускаются в реалтайме? Сомнения просто есть...
NickDoom
08.02.2025 08:19А вот хороший вопрос, на самом деле. Учитывая, что там уже «мастдай вшит в мозг» — могут быть с этим проблемы. ХЗ, насколько он там обрезан — относительно напрямую модули работают или уже практически всё свелось к мастдайным дровам под маленькой 95-й, со всеми вытекающими.
Возможно, придётся наоборот — фридос реализовать как основу (типа либребута), а уёфище уже из-под него запускать. Но это надо очень хорошо овладеть искусством дёрганья портов на каждой конкретной материнке, то есть исчезает эта прелесть лёгкости портирования :( ну и наверняка будут проблемы со всякими цифровыми подписями и прочими цифровыми ошейниками, упыри приложили много усилий к тому, чтобы это был не «мой компьютер», а «этот компьютер».
Gredko
08.02.2025 08:19Вся страна играла в Wolfenstein, а потом в Doom, но пользовалась Борландом
Вы не торопитесь?
Статья про архитектуру PC/XT.
А драйверы HIMEM/EMM386 для PC AT 286/386 написаны.
Не пойдет DOOM на XT...
nv13
08.02.2025 08:19У меня пост про Борланд, преимущественно, был. И я не помню, чтобы компилятор Майкрософт имел такое же количество моделей памяти, как Борланд, может я и не прав, конечно.
А himem был достаточно бесполезный для программирования - разве что общую память расширить вынеся туда драйверы, если вынесутся.
Но если автор продолжит, то да, поторопился))SIISII
08.02.2025 08:19И я не помню, чтобы компилятор Майкрософт имел такое же количество моделей памяти, как Борланд
Имел-имел. Не помню, была ли модель tiny, т.е. возможность создания COM-файлов, но остальные были.
NickDoom
08.02.2025 08:19Кстати, как правильно из .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 оно кладётся. И, насколько я помню, за неимением указания о том, сколько надо отдать — отдаётся всё, система-то однозадачная.
SIISII
08.02.2025 08:19Вроде б, была в INT 21h функция "заполучить память после конца программы", но голову на отсечение не дам, смотреть надо.
NickDoom
08.02.2025 08:19Кстати, смутно вспоминается, что какая-то такая функция мне отдавала ноль. Типа, «у тебя и так уже есть ВСЯ память, ты же .com, чего ты ещё хочешь у меня выклянчить?»
NickDoom
08.02.2025 08:19Перепроверил — так и есть. Мои сегменты — начало адресного пространства (конкретный сегмент зависит от версии дос-подобной оси и количества в ней всякого загруженного), а зааллокачено на меня всё по самый 0x9FFF (именно это я увидел во втором слове PSP). Кому и чем мой вопрос не понравился — непонятно, но ответ — «в начале собственного единственного сегмента, в PSP, по адресу 0x02 лежит word с сегментом, к которому относится первый «запретный» байт абсолютного адреса».
Всё остальное — автоматически зааллокачено для ком-файла, и его можно использовать по своему усмотрению, если прямые руки (ибо асм). Ком-файл не так прост, как кажется — если вся игрушка вместе с переменными влезла в один сегмент, у нас ещё есть полметра под спрайты и звук, которые мы можем с диска грузить.
Но значение, на один параграф отличающееся от 0xA000, я там увидеть не ожидал, конечно.
SIISII
08.02.2025 08:19Кому и чем мой вопрос не понравился — непонятно
Странных людей на хабре хватает :)
Но значение, на один параграф отличающееся от 0xA000, я там увидеть не ожидал, конечно
A000 -- это что, в данном случае? Сегмент, т.е. физический адрес A0000? А запрещает использовать, начиная с 9FFF0? Если да, то довольно странным выглядит. Но, может, не первый запрещённый сегмент, а последний разрешённый?
Само по себе "откусывание" какого-то объёма ОЗУ непосредственно перед A0000 -- явление нормальное, довольно многие BIOS хранят там некие расширенные данные. Но такое откусывание происходит килобайтами, поскольку именно их INT 12h возвращает, а вот чтоб параграф...
NickDoom
08.02.2025 08:19Я бы ещё не удивился, увидев первый сегмент, в котором занят хотя бы один байт. Но в сегменте 9FFF занят очень сильно не один байт :)
Видимо, так и есть — какой-то параграф статусов с флагами. Надо бы погонять на более широком круге как натуральных систем, так и продуктов дос-содержащих :)
maxlilt
08.02.2025 08:19С какими моделями памяти использовались оверлеи?
qw1
08.02.2025 08:19Оверлей - кусок данных/кода, который лежит за пределами структуры exe-файла, или даже в отдельном файле. Компилятор про него ничего не знает, а программист может кусок использовать на своё усмотрение, в любой модели памяти.
geher
08.02.2025 08:19Компилятор (по крайней мере борландовский) про него как раз вроде знал и добавлял соответствующий код для подгрузки кода из оверлеев. Точно уже не помню, могу ошибаться, но вроде что-то там было на уровне компилятора для работы с оверлеями.
NickDoom
08.02.2025 08:19Только вчера, на ночь глядя, в припадке ретро-кодинга накололся: Ватком, большая модель, один массив декларирован как __huge (он размером больше сегмента). При попытке в него писать любыми библиотечными средствами (хоть мемсетом), принимающими MyHuge+MyIndex, даром что результат имеет тип __huge ptr — всё равно они пишут как в __far, то есть просто игнорят то, что сегмент подошёл к концу, и в какой-то момент начинают гадить в начало сегмента (переполнение смещения).
К счастью, читать из файла надо было кусочками меньше одного сегмента, поэтому перед вызовом библиотечной функции я руками стал приводить __huge к __far — то есть тупо оставлял в смещении последние биты адреса (внутри параграфа), а всё, что можно, переносил в сегмент. Получался каждый раз большой «запас» впереди на чтение, и всё заработало.
Но обнаружил не сразу, конечно. Долго локализовывал, пока не «поймал за руку» стандартную библиотеку :) Оказывается, они не умеют в __huge :-D ну и хрен бы с ними, я умею в __huge.
adeshere
08.02.2025 08:19DOS фортран, кстати, примерно в 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К обычно хватало. Хотя для всяких экстремальных задачек типа БПФ или сортировки - да, приходилось весь сигнал целиком загонять в массив...
;-)
NickDoom
08.02.2025 08:19Интересно, выравнивание по параграфам кто-нибудь использовал? Ну, чтобы вообще обойтись только сегментом :) Допустим, вся структура у нас 43 байта, выравниваем до 48 и 16-битный указатель инкрементируем на 3, потому что он указывает чисто на сегмент, а смещение уже хардкодом сообразно элементу структуры :)
Или с многомерными массивами такой же фокус %) Сделать массив из структур, состоящих из одного массива, и полетели :)
А то что-то получается многовато операций с этими __huge, сначала им абсолютный адрес вынь-положь, потом его разбей на сегмент-смещение… как-то кисло и тормозно :) При строгом равенстве размера структуры параграфу — вообще сегмент становится индексом массива структур (если выровнять при аллокации не забыли), а смещение становится индексом байта в ней %)
В принципе, руками можно — наделал юнионов (которые сразу два шорта и __huge *) и крути там сегмент-смещение сообразно индексам %) можно прозрачно сопоставить их массиву MyHuge[N][16], мне кажется %)
qw1
08.02.2025 08:19Идея довольно крутая с точки зрения хранения указателей, мы снова возвращаемся к слову (16 бит) на указатель, отменяем всю чехарду с моделями памяти, приводя всё к этой единой.
Но проблема в том, что это не поддержано архитектурой. Нет команд типа "прибавить константу к сегментному регистру", или "загрузить сегментный регистр из ячейки памяти, напрямую или косвенно". А это значит, что код будет очень раздутым, постоянно перемещая адреса из регистров общего назначения в сегментные, данные из обычных регистры будут постоянно вытесняться этими манипуляциями.
NickDoom
08.02.2025 08:19Да, пожалуй. Главное — не только и не столько переложить
[постоянное перемещение адресов] из регистров общего назначения в сегментные
на плечи компилятора, который должен распознать выравнивание по параграфам и избавить юзера от этого вот
руками можно — наделал юнионов (которые сразу два шорта и __huge *) и крути там сегмент-смещение сообразно индексам
сведя всё просто к обычным обращениям к массивам структур; главное — скорее всё-таки
[команды] типа "прибавить константу к сегментному регистру", или "загрузить сегментный регистр из ячейки памяти, напрямую или косвенно"
просто для того, чтобы у компилятора была возможность это всё собрать не совсем уж в конченые простынки регистровых перезаписей.
qw1
08.02.2025 08:19чтобы у компилятора была возможность это всё собрать не совсем уж в конченые простынки
А вот не будет такой возможности.
В идеале, в архитектуре должна быть инструкция
INC DS
или
ADD DS, 4
для итерации по 0x40-байтовым структурам.
Тогда код получается коротким и оптимальным.В DOS была чехарда с типами указателей и моделями памяти, но компилятор знал, что массив, переданный через small или far-указатель, не может превышать 64k, и поэтому в цикле мог инкрементировать
BX
и обращаться черезDS:[BX]
А если передан huge указатель, то никаких гарантий на размер массива нет, и надо каждый раз пересчитывать сегмент.В предлагаемой модели все указатели имеют единственный тип, и компилятор теряет ценную для оптимизации информацию.
DmitryKoterov
08.02.2025 08:19В защищенном режиме в регистрах типа DS, ES и т.д. хранился не индекс сегмента (который надо умножить на 16, чтобы получить абсолютный адрес), а селектор. И была таблица селекторов (в памяти по определенному адресу, заданному в другом регистре), где говорилось, к примеру (условно): “селектор 5 ссылается на область памяти, начиная со смещения 123456, длиной 789 и с атрибутами read-only и no-exec”. Запись номера селектора в DS приводила к чтению этой структуры и сохранению ее во внутреннее состояние регистра, ассоциированное с DS (и это очень дорогая операция). Блин, не помню уже все термины.
Так что операция инкремента селектора не имела смысла. Вероятно, поэтому разработчики процессора ее и не добавили в 8086, понимая, куда будет дуть ветер в будущем.
А вообще, все эти регистры сегментов и селекторы - мертворожденная идея. Поэтому ее и выпилили в x64 в пользу обычной страничной адресации. Да и в 8086 они пожадничали и сделали шаг сегментов 16-байтным, что не позволяло адресовать больше 1М памяти даже в теории - а могли бы шаг сделать, например, 256 байтов, и тогда бы получили «забесплатно» возможность в будущем расширить архитектуру до 16М памяти без переписывания софта.
Wesha
08.02.2025 08:19Не было никаких таблиц селекторов в 8086/80286, в регистровых сегментах действительно тупо хранилось число, которое домножалось на 16 и прибавлялось. Это Вы с таблицами страниц путаете — оно появилось, если мне не изменяет склероз, на 80386.
SIISII
08.02.2025 08:19В 16-разрядном защищённом режиме, появившемся в 80286, были таблицы описателей (дескрипторов), а сегментные регистры содержали именно селекторы, выбиравшие описатели из этих страниц. "Число, которое домножалось на 16" -- это только для реального режима, который был единственным для 8086 и поддерживается до сих пор.
80386 в своём защищённом режиме сохранил все возможности 80286, в том числе и использование селекторов сегментов для выборки описателей этих сегментов из соответствующих таблиц (только сами описатели были расширены -- помимо унаследованных для 16-разрядного кода, добавились новые для 32-разрядного кода), но добавил ко всему этому возможность использования страниц, которую можно было включать, а можно и не включать.
Wesha
08.02.2025 08:19Может быть, оно и с 286 началось — но я точно писал по той самой схеме с домножением на 16 безо всяких дескрипторов. Давно было.
titbit
08.02.2025 08:19Несколько дополнений, хотя про это можно отдельную статью писать.
На примере экзотичных режимов моделей памяти в x86 легко понять почему указатель на код (функцию) может не совпадать размером с указателем на данные и их нельзя преобразовать один в другой.
Модели памяти используются до сих пор даже при разработке 64-битных программ на x86. Только смысл немного поменялся. Проблема в том, что в x86 нет прямой адресации всего доступного 64-битного (точнее 48-битного или сколько там сейчас тянут по максимуму?) адресного пространства ни для кода (прыжки, вызовы), ни для данных, а значит для генерации эффективного кода надо знать как далеко может "полезть" код и данные. Но обычно все компиляторы настроены на small модель в 64-битах (доступ в пределах 4Гб), редко кому нужна бывает полная адресация, но людям пишущим некоторые виды программ она бывает нужна и вот тогда начинается интересная возня.
NickDoom
08.02.2025 08:19…и PAE для 32-битных приложений, кстати, сильно напоминает эти самые сегменты…
UPD: да, написать было бы хорошо. «Сегменты наносят ответный удар» %)
firehacker
08.02.2025 08:19PAE во-первых, не напоминает. А во-вторых — какие «эти»?
Сегменты реального режима — это одно. Сегменты защищённого режима — совсем другая вещь.
На какие именно сегменты похож по вашему PAE?
NickDoom
08.02.2025 08:19Вы — педант :) это очень ценное качество в плане Вашего каммента чуть ниже, хорошо расписали и про следующую инструкцию, и про перевод терминов… но Ваш «детектор похожести» может быть из-за этого более «узкополосный», чем мой :-D
По мне так PAE похож и на селекторы защищённого режима, и на сегментную адресацию реального режима. По мне так вообще все варианты адресации, где старшие биты задаются оптом для определённой группы адресов, похожи на сегменты в реальном режиме, а все варианты адресации, где область памяти описывается некоей таблицей (а не пишется напрямую в сегментный регистр) — на селекторы защищённого режима :) ну а если в таблице можно описать биты с более высоким весом, чем доступны напрямую в каком-нибудь esi/edi — то оно похоже и на то, и на это %)
Но я бы предпочёл об этом почитать в нормальных формулировках и с техническими деталями, а не рассказывать своё поверхностное о нём знание, да ещё в формулировках типа «хвост от бобра, уши от зайца».
qw1
08.02.2025 08:19Проблема в том, что в 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" модель?
SIISII
08.02.2025 08:19Скорей всего, произошло смешивание в одну кучу имеющегося набора команд 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 то же самое: адресация памяти всегда ведётся относительно регистров, а не прямо.
qw1
08.02.2025 08:19В плане же системы команд, насколько помню, нет возможности в одной команде прямо указать 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
SIISII
08.02.2025 08:19Загрузить в регистр можно, а прямо указать в качестве адреса данных, вроде бы, нельзя. Ну а если адрес в регистре, то получаем уже адресацию относительно регистра, о чём я и говорил.
firehacker
08.02.2025 08:19Короткий указатель занимает всего один байт и выражает адрес, записанный относительно той инструкции, которая сейчас выполняется.
Автор написал херню, переводчик спокойно прошел мимо и перевёл, комментаторы тоже спокойно проходят мимо.
Относительные адреса переходов всегда задаются не относительно текущей, а относительно следующей инструкции.
Таким образом JMP с нулём в машинном коде (а не ассемблерном листинге) не порождает бесконечный цикл, а просто переходит к следующей инструкции.
А в целом, раздражает, что относительные адреса, которые используются исключительно в инструкциях CALL/Jxx, автор называет указателями. Они никакие не указатели, а именно что смещения, и от модели памяти и разрядности сегмента в их отношении ничего не меняется.
Зато автор умалчивает о существовании вариации JMP инструкции, которая принимает абсолютный полный (far) адрес и о том, что jmp/call могут косвенно ссылаться на хранящийся в памяти near/far адрес.
Doomland
08.02.2025 08:19Поскольку каждый из таких файлов загружается в один сегмент, а длина сегмента составляет не более 64 КБ, самый крупный COM-файл может иметь размер 64 КБ минус 256 байт спереди, резервируемые для подсистемы PSP.
В 98 винде, в ДОСовском куске есть command.com. Он занимает не 64 КБ, а гораздо больше 95202 байта. Как они это сделали? подозреваю, что это не ком файл, а переименованный экзэшник. Ещё в учась в универе спрашивал наших гуру, но никто внятно объяснить не мог. Некоторые ссылались на какие-то недокументированные возможности.
qw1
08.02.2025 08:19Об этом в википедии написано
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.
SIISII
08.02.2025 08:19Это именно EXE-файл, достаточно глянуть его в любом просмотрщике. DOS определяет, что за файл ей подсунули, не по расширению, а по его началу: видит сигнатуру EXEшника -- обрабатывает соответствующим образом.
Вроде б, с этим даже уязвимости были что в ДОСе, что в Винде: можно подсунуть файл с расширением, как у, скажем, картинки, а в действительности EXEшник, и система его запускала. Но не помню, действительно ли такое было, а искать лениво :)
Doomland
08.02.2025 08:19Спасибо.
Ну я так и думал, что просто переименовали экзэшник. Залез внутрь ФАРовским вьювером, и точно: там прямо вначале MZ.
NickDoom
08.02.2025 08:19Вообще .COM — идеальный формат для «домашнего пека с программами на кассетах и выводом на телевизор», который, увы, не состоялся… @bodyawm грозился написать игру «как если бы оно тогда взлетело», опирающуюся только на средства BIOS (как если бы у нас была голая материнка с CGA и воткнутые в неё магнитофон, телек и клава), но его на всё не хватает, увы %)
SIISII
08.02.2025 08:19Ну, собсно, подобная конфигурация -- почти типичный IBM PC. У него же флопов могло и не быть, а вот поддержка магнитофона была всегда (позже её выпилили). Правда, вместо CGA мог быть MDA, а оперативы могло быть ващще 16 Кбайт.
bodyawm
08.02.2025 08:19Угу, мне даже подписчик подарил целый IBM PC полноценный, 486'ой в сборе! Я даже купил COM TO RS232 для еще одного проекта... но пока занимаюсь другими девайсами. Вот, например, ноутбуком на MIPS.
ednersky
интересно, что ни один компилятор тогда не предоставлял "автоматической" системы адресации, хотя она, казалось, лежала на поверхности.
данные внутри функции обычно помещаются в tiny, а переходы между функциями могут вычисляться хоть рантайм.
AlB80
Как такое реализовать между единицами трансляции? Адреса это также параметры функций. Соответственно, автоматическая система адресации могла такого накрутить, что потом слинковать было бы проблемой. В сях слинковалось бы, но я такое боюсь запускать.