Вступление
Небольшое предупреждение! Субъективно, эта статья содержанием немного не удовлетворяет смыслу переданному в заголовке. Я буду говорить не просто о том "Как запускаются команды?", а покажу часть внутреннего мира операционных систем и покажу принципиальную разницу в их работе.
Это моя первая статья, вырванная из моего дневника, который я веду пока что закрыто, особо не выкладывая заметки в публичный доступ.
Взгляд на CP/M
Начну очень издалека, поскольку
считаю важным немного заранее обозначить моменты, которые будут нужны.
Во времена 1970-ых годов, когда для процессоров Intel 8080 появилась на свет операционная система CP/M (полн. "Computer Program/Monitor").

Процессор i8080 (полн. "Intel 8080") был 8-разрядным,
соответственно объемы обрабатываемой памяти были не такими уж и большими. Все написанные под эту ОС программы помещались в один файл команды и весили не больше чем 64Кб.
Раз глава про управление памятью, сразу вижу необходимым обозначить структуру оперативной памяти после загрузки CP/M.
+----------------------------+ <-- 0xFFFF
| BIOS |
| Аппаратные драйвера BDOS |
+----------------------------+ <-- 0xF200
| BDOS |
| CP/M двоичный интерфейс |
+----------------------------+ <-- 0xE400
| Интерпретатор комманд |
+----------------------------+ <-- 0xDC00
| TPA |
|Область запущенной программы|
+----------------------------+ <-- 0x0100
| LowMem Storage |
| То, что нас интересует |
+----------------------------+ <-- 0x0000
Объясняю по-порядку все, что может вогнать в тупик:
BDOS
(полн. "Basic Disk Operating System") - это лишь часть CP/M, а не второе название системы.
Компонент BDOS
действительно для первого раза лучше принять за двоичный интерфейс, так как команды CLOSE
, READ
, SELECT
, и др, это "именно в эту дверь".
BIOS
(полн. " Basic Input/Output System") это буквально то, о чём могут подумать практически все, кто это читает. Но сразу обозначусь, в понимании CP/M это её часть, а не отдельное "нечто" и POST.
Выглядит немного необычно, не так ли?
Интересно в организации ОЗУ самое первое (или последнее) пространство -- "Low Memory Storage".
Низкоуровневая память или "Low Memory Storage"
бьется на разделы тоже.
На самом деле, эту часть лучше пропустить, поскольку много кого это загонит в тупик.
Я оставлю информацию, которую я нашел в архивах, но лучше пропустить это мимо глаз.
+----------------------------+ <-- 0xFF
| Файловый буфер | <-- 0x80 длинна
| | 0x81-0xFF
| | Список ASCII строк
+----------------------------+
| FCB (File Control Block) |
+----------------------------+
| ??? |
+----------------------------+
| BIOS рабочее пространство |
+----------------------------+
| Вектор перезагрузки #7 | <-- Использовался
| Вектор перезагрузки #6 | отладчиком
| . |
| . |
| Вектор перезагрузки #1 |
+--------+------+----+-------+
| F200 | Disk | IO | E400 |
+--------+------+----+-------+ <-- 0x00
Взгляд CP/M на Программы
И так, программа для CP/M
это файл с расширением ".COM
".
Файл "
.COM
" (сокр. "Command") - это монолит кода. Внутри он никак не разграничен и не имеет опознавательных знаков или подписей. В CP/M может занимать адреса до верней границы TPA (полн. "Transistent Program Area").
Напишем простой пример команды, чтобы показать структуру:
.z80 ; <-- Требуется Z80;
WRITE equ 9h ; <-- CP/M функционал;
_BDOS equ 5h ; <-- "Системное прерывание";
org 100h ; <-- Отступ в ОЗУ на 255 байт;
ld C, WRITE
ld DE, hello
call _BDOS ; syscall;
rst 0h ; Выход.
hello:
db "Hello World!$"
end
На всякий случай, команда ld
(полн. "LOAD") выполняется как mov
из IA-32, а не как lea
или push
. Команда rst
(полн. "RESET") переходит по адресу и запоминает предыдущее значение в стэке.
Стрелочками в комментариях я обозначил, что здесь определенно "делает погоду".
Для CP/M
в структуре программы есть три
немало важные детали.
требование к архитектуре;
двоичный интерфейс (англ. "Application Binary Interface") системы;
смещение программы (
ORG 100h
) и установка входной точки.
Начало TPA -->|
|
<-- Low Memstr -->|
+------------------+-----------------+
| ... | ... | ... | Код программы |
+------------------+-----------------+
|<-- 255 байт -->| |
Сама программа должна будет лежать в области TPA, как из таблицы видно, а выделенная область оказывается лежит в Low Memry Storage.
Эта таблица называется ZPCB
(полн. "Zero Page Control Block") или PPA
(полн. "Program Prefix Area") для поздних релизов этой системы.
Представьте себе почтовый ящик в подъезде по таковому адресу (адрес 0x0000-0x00FF
это Low Memory Storage)... Когда приходит жилец (загружается комманда), почтальон (Операционная Система CP/M
) кладет в этот ящик конверт с важной информацией именно для этого жильца. Конверт лежит в ящике. Жилец приходит "домой" (загружается по 0x0100 смещению) и первым делом заглядывает в свой ящик за конвертом (Содержит области памяти ещё и ZPCB
).
Загрузка и регистрация .COMманды в CP/M
Когда CP/M загружает .COM файл
ОС инициализирует PPA/ZPCB в Low Storage. Она заполняет поля, относящиеся к запуску программы. Это могут быть текущий диск, FCB1
, FCB2
, хвост командной строки, системные векторы BDOS
и BIOS
для этого запуска.
ОС загружает бинарный образ .COM файла начиная с адреса 0x0100. Именно благодаря ORG 100h
комманде процессору.
Код программы ожидает, что по 0x0000-0x00FF уже лежит корректно заполненная PPA.
ОС устанавливает регистры процессора: PC
(полн. "Program Counter") становится по адресу точки входа, SP
(полн. "Stack Pointer") - обычно глядит в конец доступной программе памяти (или в другое место, указанное в BDOS
).
Zero Page Control Block или Program Prefix Area (CP/M)
Теперь более детально опишу структуру ZPCB/PPA. В документации к CP/M
её действительно называют "нулевой страницей", поэтому непобоюсь написать так же.
Данную структуру в более полном виде (описания и типы данных) можно найти в архивах или на сайте с документацией.
Название поля |
Размерность |
Описание |
---|---|---|
|
|
|
|
|
Адрес сегмента кодовой группы |
|
|
Установлен, если программа в одном сегменте |
|
|
|
|
|
|
|
||
|
|
Дескриптор группы |
|
|
Дескриптор группы |
|
|
Дескриптор группы |
|
|
Дескриптор группы |
|
|
|
|
|
Диск с которого запущена программа |
|
|
Адрес ключевого слова для |
|
|
Длинна ключевого слова |
|
|
|
|
|
|
|
|
|
|
|
Количество аргументов |
|
|
Аргументы командного интерпретатора |
Теперь укажу условные обозначениям:
Префикс
b...
- этоBYTE
или машинное слово, размером в 8 бит.Префикс
h...
- это дескриптор (указатель) инкапсулирущий настоящий адрес данных.Нотация
[type; n]
взята из Rust, так как нахожу это правило универсальными и более понятными глазу, чем типы данных Windows. Это массивtype
-ячеек вn
-ом колличестве.Префикс
dw...
- этоDWORD
, а неdefine WORD
! Другими словами - машинное слово размером в 32-бит (на момент IA-32e).Тип данных
size
тоже взят из Rust. Путь Этот тип будет для всех, как максимально возможное машинное слово для процессоров тех времен и самой ОС. (имеется ввидуIntel 8080
иIntel 8086
). Все зависит только от того "Где работает ОС и какая ОС".
Как видно, тот самый "конверт" с письмом на самом деле содержит вот такие поля данных.
Очень важно запомнить, что таблица PPA
в CP/M
только одна. Она располагается в определенном месте,
имеет очень большой смысл в жизненном цикле программы.
Только в CP/M-86, ZPCB
(или PPA
) начинает поведением быть похожа на PSP таблицу из xx-DOS
.
Взгляд на xx-DOS
Сейчас речь пойдет в основном про PC-DOS
и MS-DOS 2.0
,
так как тема разговора и подопытные совпадают.

В предыдущей главе было описано более-менее подробно, как же понимает CP/M файлы комманд. Раз уж выделена отдельная глава для DOS, логично предположить, что поведение COM
файлов в DOS и CP/M разное.
Внимание, здесь не будет описан механизм чтения OVERLAY
частей программ, потому что рассматриваются немного другие времена
и лишние объяснения про страницы памяти будет избыточно.
К сожалению опять собираются грозовые тучи теории и истории, и придется их переждать, ведь без них всё высохнет и потеряет смысл.
Очень важная отличительная черта от предыдущего "подопытного" в организации процессов. Положим, MS-DOS
пусть и является однозадачной, по определению, но каким-то образом же может удерживать драйверы и DOS-extender'ы? Ведь да?
В оперативной памяти DOS
держится приблизительно таким образом:

Здесь логика местами похожа на CP/M
, но в глаза бросается строгое разграничение программной памяти. Отныне все нулевые страницы ~управляемой~ памяти пренадлежат области "Program Memory".
Писать про жизненно необходимые MSDOS.SYS
, CONFIG.SYS
, COMMAND.COM
не буду. Они как раз видны на схеме вооруженным глазом.
...и Взгляд xx-DOS на программы
Для 16-разрядных DOS при выполнении файла код,
данные и стек находятся в одном и том же 16-битном сегменте.
Поэтому размер файла не может превышать 65280 байт (что на 256 байт меньше размера сегмента — или 216 байт).
Простейшая программа для DOS на Flat Assembly (сокр. "FASM")
диалекте выглядит приблизительно так:
use16
DosWrite equ 9h ;<-- Функционал DOS/2.0
org 100h ;<-- Точка входа 0x100
mov dx, hello
mov ah, DosWrite;DOS call.
int 21h ;syscall. (вызов DOS функции)
mov ax, 4C00h ;AH = 4Ch, AL = 00h.
int 21h ;syscall (вызов DOS функции)
hello db 'Hello, world!$'
Стрелочками я обозначил практически те же самые места, такие как смещение и обозначение точки входа (не смещение точки входа), и ABI системы. С виду структурно это похоже на предыдущий образец. Разница только в архитектуре и в системном BI.
Впринципе, отличить .COM
ы можно только при детальном анализе сырого содержимого, так как дисковых операционных систем успело
появиться достаточно, и скорее всего системный интерфейс у всех отличается. Можно ловить их по опкодам прерываний, как делал это я, можно придумать более умную стратегию.
Загрузка регистрация .COMманд в MS-DOS 2
И так, идея комманды не меняется. .COM
это все еще PIE-монолит (исполняемый файл независящий от точки входа).
При загрузке файла .COM
в свободной области ОЗУ, операционная система
создает несколько структур.
Структура "перед" кодом программы - это PSP
(полн. "Program Segment Prefix"), что отвечает за состояния
выполняемого файла и предоставляет список аргументов командной строки.
A:\> COMMAND.COM /A /B /C
argv[ 0 ][1][2][3]
В области ОЗУ будет приблизительно такая схема ячеек:
+-----+-----+----------+----------------------------------+-----+
| ... | ... | PSP | [Программа] [STACK] | ... |
+-----+-----+----------+----------------------------------+-----+
| 256 байт | Размер Программы |
| |
| |
Начало -->| Конец области программы -->|
Как видно из таблицы, именно поэтому образец программы начинался с org 100h
.
Сама программа в ОЗУ разбивается по области кода, данных и стэка.
Ещё раз: здесь не рассматривается OVERLAY
части, потому что это здесь избыточно.
Но в следующий раз, возможно про .EXE
файлы и MS-DOS 2.0
я это обязательно возьмусь показательно разбирать.
В документации DOS
я не встретил упоминания "нулевой страницы", поэтому назову структуру "как есть".
Program Segment Prefix (PSP)
Теперь спускаемся ещё дальше в специфику DOS и CP/M семейств.
С первых интернет ресурсов буквально представляется полная информация о PSP
секции. Представлена ниже:
Название |
Размер |
Описание |
---|---|---|
|
|
|
|
|
Сегмент расположенный после выделенной памяти |
|
|
|
|
|
Содержит |
|
|
Адрес |
|
|
Адрес |
|
|
Адрес обработчика ошибок |
|
|
Сегмент PSP процесса-родителя |
|
|
Общее расположение пространства памяти |
|
|
Сегмент переменных среды |
|
|
Маска вида |
|
|
Максимальное количество открытых файлов |
|
|
Адрес обработчика ручных записей |
|
|
|
|
|
Опкод прерывания ( |
|
|
Номер прерывания. |
|
||
|
|
Первый закрытый |
|
|
Перезаписывается, если открыт |
|
|
Колличество аргументов коммандной строки |
|
|
Аргументы коммандной строки. Всегда заканчивается на |
В CP/M и DOS главах упомянались FileControl
-блоки, но почему-то ни слуху - ни духу, о них в разборе... Исправляюсь.
Специально выделю два подраздела для неизвестных структур.
File Control Block (FCB)
FCB (File Control Block) в DOS — структура, в которой поддерживается состояние открытого файла. Она находится внутри памяти программы, которая использует файл, а не в памяти операционной системы.
Такое решение позволяет процессу иметь одновременно несколько файлов, если он может выделить достаточно памяти для FCB на каждый файл
В зависимости от версии MS-DOS
сама структура потерпела изменения:
Я показываю структуру FCB такой, какая она есть в MS-DOS 2.0
, поскольку там присутствуют поля случайного доступа.
#pragma pack(push, 1)
struct FCB_STANDARD {
unsigned char drive; // 00: 0 = default, 1=A, 2=B, ...
char filename[8]; // 01-08: имя файла (лево выровнено, пробелы в конце)
char extension[3]; // 09-0B: расширение (лево выровнено, пробелы)
unsigned short current_block; // 0C-0D: текущий блок (начиная с 0)
unsigned short record_size; // 0E-0F: размер логической записи в байтах
unsigned long file_size; // 10-13: размер файла в байтах
unsigned short date; // 14-15: дата (формат: см. выше)
unsigned short time; // 16-17: время (формат: см. выше)
unsigned char reserved[8]; // 18-1F: зарезервировано (зависит от версии)
unsigned char current_record; // 20: текущая запись в блоке (0-127)
// MS-DOS 1.25 не имеет поля случайных записей
unsigned long random_record; // 21-24: случайная запись (относительно начала файла)
}
#pragma pack(pop)
У блока управления файлом в DOS 2.0
есть расширенный (мне кажется лучше расширяющий) подраздел.
#pragma pack(push, 1)
struct FCB {
unsigned char signature; // -7: 0xFF
unsigned char reserved[5]; // -6 - -2: зарезервировано
unsigned char attribute; // -1: атрибут файла
FCB_STANDARD fcb; // 00-24: стандартный FCB
}
#pragma pack(pop)
Советую думать, что FCB
это своеобразная карточка книги в библиотеке,
а расширенный FCB это пометка [СЕКРЕТНО]. Увы, подробнее об этом в другой раз.
Таблицы процесса (JFT и SFT)
Изначально этот материал я долго не мог уложить в голове, и взял его практически "как есть", без обработки. Но со временем дополнения появились.
Для обеспечения доступа к открытым файлам MS-DOS
использует системные таблицы двух типов.
Таблица SFT
(полн. "System File Table") содержит записи о всех файлах, в данный момент открытых программами пользователя и самой ОС.
Эта таблица хранится в системной памяти, число записей в ней определяется параметром FILES
в файле конфигурации CONFIG.SYS
, но не может превышать 255
.
Если один и тот же файл был открыт несколько раз (неважно, одной и той же программой или разными), то для него будет несколько записей в SFT
.
Каждая запись содержит подробную информацию о файле, достаточную для выполнения операций с ним. В частности, в записи SFT содержатся:
копия информации о файле;
адрес записи (сектор и номер записи в секторе);
текущее положение указателя чтения/записи;
номер последнего записанного или прочитанного кластера файла;
адрес в памяти программы, открывшей файл;
режим доступа, заданный при открытии.
Кроме того, в записи SFT
содержится значение счетчика ссылок на данную запись из всех таблиц JFT
, речь о которых пойдет позже. Когда этот счетчик становится равным нулю, запись SFT
становится свободной, поскольку файл закрыт.
В отличие от единственной SFT
, таблицы JFT
(полн. "Job File Table")
создаются для каждой запускаемой программы,
поэтому одновременно может существовать несколько таких таблиц.
Теперь к одному из главных вопросов: "Откуда в однозадачной MS-DOS
могут взяться одновременно
несколько программ?"
Ответ прост: когда одна программа запускает другую,
то в памяти присутствуют обе.
Таблица JFT
имеет простейшую структуру: она состоит из однобайтовых
записей, причем значение каждой записи представляет
собой индекс (номер записи) в таблице SFT
.
Неиспользуемые записи содержат значение 0xFF
.
Размер таблицы по умолчанию составляет 20 записей (байт),
но может быть увеличен до 255.
Сама JFT находится в PSP
области. Если рассматривать поближе
часть ОЗУ где находится процесс, его структура будет такова:
+-----+-----------------------------------------+-----------+-----+
| ... | [argv]; argc.....[1; 6; 2; FF; FF;].... | Программа | ... |
+-----|------------------|----------------|-----|-----------+-----+
| | <-- JFT --> | | |
| 20 байт | |
| | |
| <-- Начало PSP Конец PSP -->| |
| <-- Начало Конец управляемой памяти --> |
Что происходит с файлами при завершении программы,
которая их открыла? Это не важно.
В любом случае ОС должна при завершении программы закрыть
все её файлы. Как ОС узнает, какие файлы следует закрыть?
Достаточно просмотреть таблицу JFT завершаемой программы и
найти там все записи, отличные от 0xFF
.
Вывод
Это было большое и насыщенное для меня путешествие в NTVDM (полн. "Windows NT Virtual DOS Machine"), тонкости MS-DOS и её предков, но то, что я нашел на просторах интернета, возможно скоро канет в Лету. Пытаться найти мелочи жизни и как-то уложить их в голову, а потом и сюда.
Я приведу таблицу итогов для .COMманд, и это самая последняя таблица в этом документе.
Характеристики COM |
CP/M |
DOS |
---|---|---|
Подпись |
нет |
нет |
Точка входа |
везде |
везде |
Секции |
нет |
нет |
Нулевая страница |
|
|
Ареал обитания |
|
|
В целом, и так понятно, что загрузить программу из MS-DOS 2.0
в чужеродной среде не получится, поскольку нет слоя совместимости системного интерфейса.
Если бы даже архитектуры программ были одинаковы (например сборщик для Intel 8086
), то разногласия в вызовах уже бы помешали злодеянию.
Обратная совместимость для CP/M
комманд в DOS была, и некоторые поля PSP
структуры существуют только для её обеспечения.
Со временем ZPCB/PPA
таблица в CP/M-86 станет похожа на .PSP
раздел, а сам формат таблицы .PSP
станет родителем таблицы релокаций и первых файлов .EXE
- MZ-исполняемых файлов.
DOS
системы продолжут существовать и станут самостоятельнее, появится BW-DOS
с новым форматом исполняемых файлов, а Microsoft и IBM придумают NE-сегментные файлы.
Источники
CP/M ZeroPage control block;
SIISII
В целом статья написана, с точки зрения русского языка, грамотно (что является весьма редким явлением), но вот в её "предисловии" команды почему-то с двумя м :)
Пара замечаний. Во-первых, "у них", когда имеются в виду килобайты, принято сокращать их до KB, ну а Kb означают килобиты. Понятно, что в данном случае проблемы с пониманием вряд ли возникнут, но в других ситуациях такое может произойти (скажем, когда указывают ёмкость микросхемы памяти).
Ну а во-вторых, разрядность процессоров и размеры программ не имеют совсем уж прямой связи. Как, например, транслятор ассемблера в OS/360 мог выполняться в разделе памяти размером порядка 16 килобайт, если он сам суммарно имел куда больший объём и мог транслировать программы, чей размер, по большому счёту, ограничивался только доступным местом на дисках? (Система 360 -- 32-разрядная машина, адрес там был 24-битным, занимая три младших байта слова, т.е. теоретически адресуемый объём памяти достигал 16 Мбайт, но сами машины имели весьма небольшой объём физической памяти; у самой младшей модели 30 он составлял от 8 до 64 Кбайт -- всё ж середина 1960-х).
Так что "помещались в один файл" -- это именно особенности CP/M, а не безусловная необходимость для 8-разрядного процессора.
И, кстати, были же оверлейные программы, суммарный размер кода которых мог существенно превосходить 64 Кбайта. Это сейчас про оверлеи, наверное, мало кто помнит :)