Хабрахабр

Apollo Guidance Computer — архитектура и системное ПО. Часть 2

Ссылка на часть 1

Список литературы и источников приведён в конце первой части статьи. В этой части мы рассмотрим, как AGC организован с точки зрения программиста. Материал этой части основан на материале книги [1].

Представление чисел в памяти AGC

AGC использует 15-битные слова, со знаком в 15-м разряде. Также имеется разряд чётности, который записывается и контролируется аппаратно и полностью прозрачно для программного обеспечения, при каждой операции чтения и записи в память.

Он заключается в следующем: Целые числа представлены в формате «дополнения 1».

Неотрицательные числа от 0 до 16387 представлены в виде кодов от 000 000 000 000 000 до 011 111 111 111 111 соответственно.

-1 представлен как 011 111 111 111 111, и до -16387, представленным двоичным кодом 100 000 000 000 000. Отрицательные числа образуются путём инверсии положительных, т.е.

Арифметические действия выполняются так:

2: 000 000 000 000 010
-5: 111 111 111 111 010 111 111 111 111 100 (= -3)

Сложение двух отрицательных чисел несколько более сложно.

Если мы будем складывать по обычным правилам, то ничего не получится:

-2: 111 111 111 111 101
-5: 111 111 111 111 010 111 111 111 110 111 (= -8)

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

-2: 111 111 111 111 101
-5: 111 111 111 111 010 111 111 111 110 111 (= -8)
Carry 1 111 111 111 111 000 (= -7)

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

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

AGC использует двоично-десятичное представление чисел длиной 28 бит (9 десятичных знаков), которое занимает две ячейки памяти, по 14 бит в каждой. ACG поддерживает только целочисленную арифметику и может выполнять операции с вещественными числами аппаратно, но может делать это программно. То есть, может быть число, представленное как, например, +5 * 10000 + -5*100 = 49500. Знаковые разряды тоже используются, причём младшее слово и старшее слово могут иметь разный знак! Странно, но возможно.

Расстояния и скорости при расчётах представлены в метрической системе, но данные для экипажа отображаются в английской системе мер (футы и т.д).


Формат инструкции

Модель памяти

В предыдущей части уже упоминалось, что память компьютера делится на ОЗУ объёмом 2 Кслова и ПЗУ объёмом 36Кслов. Так как в инструкции для значения адреса отведено всего 12 бит, используется принцип разделения памяти на банки. Для указания текущего банка используется специальный регистр.

Для перехода на новый банк памяти используется команда «Transfer into New Bank» (TNB), которая выполняет следующее:

  • Копирует текущий регистр банка («Bank») в регистр «Saved Bank»
  • Копирует 12-битный адрес из ячейки памяти, следующей за командой TNB, в регистр «Return Address»
  • Загружаем новый адрес банка в регистр «Bank», а в счётчик команд загружаем смещение, на которое указывает инструкция TNB.

Регистры

AGC имеет отображаемые на адресное пространство регистры. Они занимают первые 48 слов физической памяти.

Аккумулятор занимает адрес 08.

Хотя AGC оперирует с 15-битными словами, аккумулятор имеет разрядность 16 бит, так как он хранит разряд переполнения. Аккумулятор используется в большинстве арифметических и логических операций (OR, AND и т.п.). После выполнения арифметической операции, если не произошло переполнения, то бит 15 будет просто содержать копию знака, и этот разряд невидим для программиста. Когда данные загружаются в аккумулятор, они попадают в младшие биты, при этом бит 14 содержит знак числа. Если произошло переполнение, то S1 и S2 будут не равны между собой. Назовём эти знаковые разряды как S1 и S2 соответственно. И хотя бит S2 остаётся невидим для программиста, в AGC есть целых два способа установить состояние переполнения.

Обработчик прерывания, если оно возникнет в этот момент, мог бы сбросить данный бит, что было бы очень нежелательно. Во-первых, при возникновении переполнения автоматически запрещаются прерывания. Флаг сбрасывается только при очистке аккумулятора или при загрузке нового значения. Прерывания разрешаются только при очистке флага переполнения. Команда TS также пропускает следующую инструкцию программы в том случае, если переполнение произошло. Для проверки флага переполнения может использоваться команда Transfer to Storage (TS), которая сохраняет значение аккумулятора в память только в том случае, если переполнения не было, а если было переполнение в большую или меньшую сторону, значение в аккумуляторе заменяется на +1 или -1 соответственно. Предполагается, что программист напишет код, обрабатывающий переполнение, и разместит его через одну команду от TS, а сразу после TS вставит переход на инструкцию после обработчика переполнения.

Регистр L — адрес 000018

Также может использоваться для временного хранения переменных. Регистр L также называется «the low order accumulator» и предназначен для расширения диапазона чисел, с которыми производятся операции.

Регистр Q — адрес 000028

Регистр Q содержит 12-битный адрес, который в совокупности с текущим банком памяти даё полный адрес возврата из подпрограммы. Регистр Q предназначен для сохранения адреса возврата.

Регистр EBANK (Erasable Storage Bank) — адрес 000038

Адрес банка ОЗУ имеет 3 бита и содержится в регистре EBANK. ОЗУ (называемая в AGC также «стираемой памятью»), содержит 2048 слов, разделённых на 8 банков по 256 слов.

Регистр FBANK (Fixed Storage Bank) — адрес 000048

Регистр FBANK имеет 5 бит и позволяет адресовать 32 банка. ПЗУ имеет банки по 1024 слова и содержит 36 банков.

Fixed Extension Bit (Superbank Bit)

Используется для адресации последних 4Кслов ПЗУ.

Регистр BBANK (Both Banks Register) — адрес 000068

Регистр BBANK содержит оба адреса — номера банков ОЗУ и ПЗУ. При передаче управления на другую программу нужно поменять оба регистра FBANK и EBANK одновременно. Запись в него автоматически обновляет регистры FBANK и EBANK.

Регистр Z (The program counter) — 000058

Он имеет разрядность 12 бит. Регистр Z является счётчиком программы, то есть он определяет адрес выполняемой в настоящее время команды.

Регистр нуля (A source of zeros) — адрес 000078

Содержит константу 0.

Регистры обработчика прерывания — адреса 000088 — 000128

По этим адресам расположены соответственно регистры ZRUPT, BRUPT, ARUPT, LRUPT, QRUPT и BANKRUPT.

Регистры ZRUPT и BRUPT — автоматически сохраняют содержимое регистра Z (счётчик инструкций), и регистра B (внутренний регистр, который содержит адрес команды, которая будет выполняться следующей).

Эти регистры должны сохраняться вручную и восстанавливаться вручную до выполнения инструкции RESUME, служащей для возврата из прерывания. Регистры ARUPT, LRUPT, QRUPT и BANKRUPT служат для сохранения аккумулятора и регистров L, Q и BB.

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

Однако регистр ARUPT, в который сохраняется аккумулятор при прерывании, имеет 15 бит. Ранее уже упомигалось о том, что аккумулятор имеет разрядность 16 бит, и старший бит используется для детектирования переполнения и недоступен программно. Каждый раз, когда наступает состояние переполнения, прерывания запрещаются до тех пор, пока флаг переполнения не будет очищен.

Физически они остаются доступными, но с точки зрения основной программы, их состояние меняется в произвольные моменты времени. Регистры ARUPT, LRUPT, QRUPT и BANKRUPT нельзя использовать вне обработчика прерывания.

Регистры редактирования — адреса 000208 — 000238

Система команд AGC не имеет операций сдвига, и для того, чтобы произвести сдвиг числа на один разряд, его нужно записать в один из этих регистров и затем считать. Первые три регистра — это регистры сдвига: Cycle Right, Shift Right, Cycle Left, то есть циклический сдвиг вправо, сдвиг вправо и циклический сдвиг влево. При каждой записи производится сдвиг на один бит.

Регистр EDOP (EDit Interpretive OPcode) — четвёртый из регистров редактирования.

Для чтения младшей команды достаточно операции AND с маской, но для старшей понадобится сдвиг на 7 бит. Команды интерпретатора, о котором пойдёт речь ниже, хранятся по две в одном слове и занимают по 7 бит каждая. Регистр EDOP выполняет такой сдвиг за одну операцию.

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

Таймеры и часы

Часы реального времени

AGC не использует календарное время, дни, месяцы и год. Вместо этого отсчёт производится от «нулевой» точки, которая начинается за несколько часов до старта. Часы отображаются на два слова в памяти, по адресам 000248 (T2), 000258 (T1). Слово T1 инкрементируется каждые 10 мс, слово T2 — приблизительно каждые 164 секунды, при переполнении слова T1.

Таймеры

000268 (T3) Wait list — инкремент каждые 10 мс., сдвинут относительно T4RUPT на 5 мс
000278 (T4) T4RUPT — инкремент каждые 10 мс.
000308 (T5) Автопилот — инкремент каждые 100 мс.
000318 (T6) часы высокого разрешения — инкремент каждые 1/1600 с = 0.625 мс.

Wait list — это список из очень коротких задач, каждая из которых занимает маленькое время, и может быть выполена непосредственно в обработчике прерывания. Первый таймер, T3, нужен для работы переключателя задач (Wait list). Время выполнения задачи строго ограничено 4 мс. Список содержит до семи задач, каждая из которых запускается с определённым интервалом. За это время компьютер успевает выполнить около 160 инструкций.

Таймер T4 запускает критические периодические задачи, имеющие интервал от 20 до 120 мс, включающие в себя обмен данными с DSKY, опрос переключателей на панелях управления кораблём и другие задачи.

Блок инерциальных измерений IMU (The Inertial Measurement Unit)

IMU — это гироскопически стабилизированная платформа с акселерометрами, которая служит для определения положения и ускорений корабля в пространстве.

Это устройство вырабатывает импульсы при повороте осей гироскопа, производя 32768 импульсов на полный оборот, что соответствует разрешению 39,55 угловых секунды на импульс. Мы не будем описывать здесь принцип действия гироскопа, отметим только, что положение осей гироскопа измеряется устройством CDU (Coupling Data Unit).

Так как секстант есть только в командном модуле, а радар — только в лунном модуле, они используют один и тот же порт AGC. Также CDU передаёт в AGC положение осей секстанта и радара сближения.

Но здесь есть небольшая тонкость. Также IMU имеет три маятниковых акселерометра (Pulsed Integrating Pendulous Accelerometers, PIPA). Дипазон скоростей IMU командного модуля составляет от 0 до 11000 м/c, а у лунного модуля — до 1700 м/с. Несмотря на то, что лунный модуль и командный модуль имеют одинаковые IMU, их диапазоны измерения скорости различны. Разрешение IMU командного модуля составляет 5,85 см/с, у лунного модуля — 1 см/с.

Счётчики CDUS (X, Y, Z, OPTIS, OPTT) и PIPAS (X, Y, Z)

Передача данных от CDU в AGC происходит следующим образом: импульсы от датчиков могут инкрементировать и декрементировать счётчики. Число в счётчике имеет знак, отображающий направление движения. Счётчики расположены по определённым адресам в памяти, и могут быть считаны программно. Всего используется 8 счётчиков, шесть из которых отображают скорости и углы, и два предназначены для отображения углового положения секстанта в командном модуле или радара сближения в лунном модуле.

Управление устройствами через счётчики

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

Другие интерфейсы компьютера

На пульте лунного модуля имеется рукоятка (Attitude Controller Assembly, ACA) положение которой могло быть считано программно. Каждая ось этого контроллера посылала значения в переменные P_RHCCTR, Q_RHCCTR и R_RHCCTR.


Контроллер ACA


Контроллер ACA, внешний вид

Контроллер ACA установлен только в лунном модуле

INLINK (канал передачи телеметрии)

Устройство INLINK обеспечивает двустороннюю связь с Землёй, и служит для передачи телеметрической информации и приёма данных от центра управления полётами. Астронавты могут вводить необходимые для полёта данные через DSKY, но это процесс медленный и чреватый ошибками. Через регистр INLINK данные могут вводиться с Земли напрямую в компьютер.

Управление двигателями

На протяжении процесса посадки лунного модуля AGC непрерывно вычисляет необходимые значения силы тяги и подаёт на двигатели сигналы управления. В течение 12 минут, которые длится посадка, двигатель сжигает примерно половину топлива, и программа должна учитывать уменьшение массы. Тяга двигателя меняется от уровня 92,5%, что составляет 46700 Н, до 10% полной тяги. Но тяга выше 65% вызывает сильный износ камеры сгорания и сопла, поэтому программа AGC должна минимизировать время, когда двигатель работает в таком режиме.

Управление происходит через регистр THRUST. Компьютер связан с двигателями посадочной платформы через Descent Engine Control Assembly (DECA). Экипаж может корректировать значение тяги вручную через контроллер Thrust/Translational Hand Controller (TTHC).


Контроллер Thrust/Translational Hand Controller (TTHC).

Внешний вид.
Контроллер Thrust/Translational Hand Controller (TTHC).

Рукоятка контроллера подключена к DECA напрямую, компьютер не видит введённых вручную значений.

Аналоговые приборы

Также используются аналоговые индикаторы, ALTM (analog displays: altimeter and rate meters), для индикации высоты над поверхности и скрости изменения высоты, которыми AGC управляет через регистр ALTM. Аналоговые индикаторы выполнены в виде вертикальных шкал (tapemeters).


Индикаторы высоты и вертикальной скорости

Адресация и банки памяти

Как уже упоминалось, AGC имеет два типа памяти, ОЗУ, называемое также «стираемой памятью» (erasable memory), и ПЗУ (fixed memory). Объём памяти составляет 38 Кслов, что не позволяет адресовать всю память непосредственно, так как длина адреса в командном слове составляет 12 бит.

Это расширяет адресуемое пространство 32Кслов, и для дальнейшего расширения адресуемого пространства ПЗУ используется бит Fixed Extension Bit, который позволяет получить доступ к 36Кслов. Для разделения памяти на банки используются регистры банков EBANK и FBANK, задающие банк ОЗУ и ПЗУ соответственно.

Банки ОЗУ

ОЗУ имеет объём 2Кслов, и делится на 8 банков по 256 слов.


Дешифрация адреса ОЗУ

Ещё два бита нужно для определения типа банка: непереключаемый (unswithed) или переключаемый (swithed). Для адресации слова в банке ОЗУ нужно 8 бит. Если эти биты содержат 00, 01 и 10, то регистр EBANK не используется, если 11 — используется, то содержимое EBANK объединяется с 8-битным адресом, записанным в командном слове, как показано на рисунке ниже. За это отвечают биты 9 и 10 на рисунке выше (обратите внимание, что биты нумеруются с 1). Для обращения к ОЗУ биты 11 и 12 должны быть установлены в 0. Если регистр EBANK не используется, то происходит обращение к первым трём банкам памяти, которые назваются немного вводящим в заблуждение термином «Fixed Erasable».


Дешифрация адреса ОЗУ при использовании регистра EBANK

ПЗУ

Для обращения к ПЗУ используется аналогичный подход. Биты 11 и 12 командного слова определяют, какие банки используются, если эти биты содержат 00, то используется ОЗУ, как показано в предыдущем разделе, если 10 или 11, то все 12 бит используются как адрес в ПЗУ, регистр FBANK при этом не используется, если 01, то используется адрес, состоящий из младших 10 бит командного слова и содержимого регистра FBANK.


Дешифрация адреса ПЗУ


Дешифрация адреса ПЗУ при использовании регистра FBANK

Общие банки

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


Схема использования ОЗУ и ПЗУ в одном адресном пространстве

Память за пределами 32Кслов

Для доступа к памяти выше 32Кслов используется бит расширения ПЗУ, Fixed Extension Bit, он же бит супербанка (Superbank Bit). Бит супербанка находится в бите 7 канала ввода-вывода 7. Канал 7 отличается от остальных каналов тем, что поддерживает как чтение, так и запись. Разумеется, бит расширения ПЗУ необходимо сохранять как во время обработки прерываний, так и при переключении задач.

Передача управления между банками

Обратим внимание, в каком порядке расположены регистры FBANK, Z и BB в нижней памяти. Казалось бы, почему не объединить их в одном слове? Но так сделано специально, чтобы создать мехагизм передачи управления. При переключении на другой банк должны быть установлены новые значения FBANK и EBANK, либо BBANK. Однако, при этом возникает проблема. пусть, например, программа выполняется по адресу 010338 в банке ПЗУ 07, и нужно сделать переход на адрес 023718 в банке ПЗУ 13. При изменении регистра Z передаст управление по адресу 023718 в текущем банке, чего нам не нужно. Если мы сначала переключим текущий банк, возникнет похожая ситуация. Необходимо одновременное переключение регистра Z и банка памяти. Для этого используется инструкция DXCH, которая читает аккумулятор и регистр L, и обменивает их содержимое с двумя последовательными локациями в памяти. Таким образом, можно обменять аккумулятор и регистр L либо с парой FBANK, Z, либо с парой Z, BB. Эти варианты кодируются двумя мнемониками: Double Transfer Control Switching Both Banks (DTCB) и Double Transfer Control Switching Fixed Bank (DTCF). Команда DTCB позволяет не только перейти по другому адресу, но и поменять банк ОЗУ, а команда DTCF передаёт управление, оставляя банк ОЗУ прежним. Возврат из функции производится так. Исходные значения Z и BBANK (или FBANK) оказываются записанными в аккумулятор и регистр L. Вызываемая функция должна сохранит эти значения, а затем проделать обратную операцию, обменяв значения с регистрами банков и Z.

Некоторые недостатки архитектуры AGC

В большинстве компьютерных архитектур присутствует указатель стека и/или индексные регистры (хотя бы один). Но не в AGC. Поддержка указателя стека потребовала бы дополнительных аппаратных затрат. Индексных регистров, которые позволяли бы организовывать доступ к структурам данных по адресу (указатель + смещение) тоже нет, но есть команда INDEX, которая устраняет необходимость в таком регистре. Также, хотя аппаратных индексных регистров нет, они эмулируются виртуальной машиной Interpreter, о которой речь пойдёт ниже.

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

Прерывания

40008 Startup
Стартовый адрес после подачи питания AGC

Таймер используется автопилотом. 40048 T6RUPT
TIME6 достиг 0.

Таймер используется автопилотом. 40108 T5RUPT
TIME5 достиг переполнения.

Используется планировщиком задач WAITLIST. 40148 T3RUPT
TIME3 достиг переполнения.

Опрос и и обновление индикации DSKY 40208 T4RUPT
TIME4 достиг переполнения.

Код нажатой клавиши с главного DSKY доступен в канале 15 40248 KEYRUPT1
Нажате кнопки DSKY.

Код клавиши навигационного DSKY доступен в канале 16 (только в командном модуле) 40308 KEYRUPT2
Нажате кнопки второго DSKY.

40348 UPRUPT
Данные в регистре INLINK
Используется для DSKY

Используется для телеметрии AGC 40408 DOWNRUPT
Регистр Downlink содержит данные.

Данные от радара сближения 40448 RADARUPT
Данные в регистре RNRAD.

40508 RUPT10
LM P64

Система команд

Код операции, или Order Code, в терминах того времени, кодируется тремя битами, то есть возможно всего лишь восемь опкодов, чего явно недостаточно для развитой системы команд. Однако разработчики нашли выход. Некоторые коды расширены дополнительным двухбитовым полем, Q-Code.

Опкод 000 соответствует большому числу специальных операций, опкоды 011, 100 и 111 соответствуют одной операции каждый, Оставшиеся опкоды 001, 010, 101 и 110 используют Q-коды.


Формат команды с Q-кодом

Некоторые инструкции не могут работать с ОЗУ, и указание ОЗУ в качестве адреса могло бы привести к неопределённому результату, но такие опкоды с адресами операнда в ОЗУ использовались для совершенно других операций. Также для расширения числа возможных опкодов использовались и другие ухищрения. Например, команда передачи управления TC (transfer control), не может передать управление на ОЗУ, но если адрес указывает на ОЗУ, то такой опкод соответствует команде разрешения прерываний (Enable Interrupts).

Если мы совершаем переход командой ТС на адрес 000068, на самом деле никакого перехода не происходит, вместо этого следующий за командой ТС 000068 опкод интерпретируется как принадлежащий к совершенно другому набору инструкций. Когда и этих опкодов не хватило, был использован следующий подход. Часто используемые команды включены в основной набор инструкций, а оставшиеся в расширенный. Конечно, такие инструкции требуют больше времени на выполнение, потому что процессор должен прочитать и декодировать два опкода, но эта проблема сглаживается тщательным распределением команд между двумя наборами инструкций.

Основные инструкции

Команды делятся на 6 групп: Всего AGC содержит 41 команду.

  • Арифметико-логические
  • Передачи управления
  • Перемещения данных
  • Модификация инструкций
  • Инструкции ввода-вывода
  • Разное

Однако мы не будем описывать здесь все команды AGC. Интересующиеся могут обратиться к книге [1].

Связь с внешним миром: подсистема ввода-вывода

AGC не имеет жестких дисков или ленточных накопителей, и всё общение с внешним миром сводится к установке и чению битов в портах ввода-вывода. Периферийными устройствами AGC являются инерциальная платформа, двигатели, радар, DSKY и переключатели на панели управления. Скоростной обмен данными при этом не требуется, и укорость обмена не является существенным ограничивающим фактором.

Запись и чтение канала происходит сходно с записью и чтением ячейки ОЗУ, но в отличте от ОЗУ, большинство каналов однонаправлены. Для ввода и вывода служат так называемые каналы. Импульсы от датчиков углов инкрементируют и декрементируют счётчики, которые затем могут быть считаны AGC. Также есть порты-счётчики, которые служат для считывания положений осей IMU, радара и секстанта.


Периферийные устройства AGC

Команды ввода вывода имеют опкод 000, затем идёт трёхбитный PCode. Инструкции ввода-вывода относятся к расширенному набору команд и требуют команды EXTEND перед опкодом. Остальные 9 бит командного слова — это номер канала. Различные значения PCode используются только для наземгых тестов, по умолчанию PCode равен нулю. Большинство периферийных устройств связано не с полным 15-битным словом, а с отдельным битом в слове, то есть при операциях ввода-вывода требуются логические операции AND, OR и Exclusive OR. AGC, таким образом, может адресовать до 512 каналов, но на практике используются только 16. Для этого предназначены инструкции WOR (Write with OR), WAND (Write with AND), ROR (Read and OR) и RXOR (Read and XOR). Такие операции могут быть объединены в одну операцию с операцией ввода-вывода.

Но есть также регистр L (low-order accumulator), который может быть отображён на порт, тогда всё, что читается и записывается в него, будет автоматически попадать в порт. Инструкции ввода-вывода используют аккумулятор для чтения и сохранения данных. Это позволяет использовать для работы с портом обычные логические операции AND, OR и XOR.

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

Ещё один пример, когда необходима передача большого количества данных через порт, это интерфейсы uplink и downlink, обеспечивающие связь с Землёй на скорости 51 кбит/c или 1900 бит/с (выбирается вручную экипажем).

Программное обеспечение

Их мы рассмотрим подробно в следующей части. Программное обеспечение AGC основано на операционной системе реального времени Executive и виртуальной машине Interpreter.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть