Первый заказной проект

Напомнили про MACRO-11 и вот вспомнилось. 31 год назад, в далёких 1994-1995, примерно в это время я закончил первый заказной софтверный проект. Это была прошивка для переговорных пунктов междугородней автоматической телефонной сети «Кварц». Так много поменялось за эти 30+ лет.

Контекст

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

«Кварц» запустили в конце 70-х, а к началу 80-х государственные переговорные пункты были почти в каждом городе и районном центре СССР. Ну а в начале 90-х всё пришло в движение. Эти переговорные пункты становились хозрасчётными/коммерческими организации, кооперативами и т. п. Услуги связи стали перепродаваться, т. е. к тарифам междугороднего телефонного оператора добавлялась маржа переговорного пункта. Потребовался автоматический расчёт и контроль всего этого, в том числе фискальный.

Во Фрунзе (aka Город-312) во временя СССР было несколько высокотехнологичных предприятий (Завод ЭВМ, Завод «Физприборов», ОКБ ИК по космосу и т. п.). Поэтому специалистов-электронщиков было достаточно, и вычислительная техника ещё оставалась. Причём в ДВК быстрее всего выходила из строя механика — жёсткие диски и дисководы гибких дисков, без которых использовать машину традиционным образом становилось невозможно. Соответственно, такие бездисковые станции были недорогими и в относительном изобилии — их и стали использовать в качестве контроллеров.

В начале 90-x я ушел из мелкой фирмы занимавшейся продажей вычислительной техники, модемов и т.п. С деньгами и работой было не очень, мягко говоря. Дав объявление в газету я нашел заказчика, от которого (видимо) все уже отказались.

Проект

В ДВК была внутренняя шина и несколько внутренних слотов, что допускало очень лёгкое расширение функционала. Спецы быстренько спроектировали и изготовили несколько плат, которые вставлялись внутрь, имели интерфейс взаимодействия с телефонией, кажется 16 Кб ПЗУ, часы реального времени с аккумулятором и килобайт ОЗУ подпитываемого от них. После включения ДВК запускался загрузчик из системного ПЗУ, оператор должен был ввести команду запуска — буквально команду перехода по адресу в ПЗУ на дополнительной плате.

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

Боясь не успеть я взял себе в компаньоны друга. Писать начали на паскале, компилятор был в разы быстрее сишного и генерировал приемлемый код. Получалось что каждые 2-3 дня я переписывал половину кода, а Александр приводил его в порядок. Потом цикл повторялся.

Челенджи

1. Объем кода, Сложность, Производительность

Объем кода рос очень быстро, вскоре мы перестали помещаться в доступные 16К ПЗУ (а это было жёстким требованием). Пришлось серьёзно заняться оптимизацией кода по-размеру, а затем перейти к отказу от каких-то функций/библиотек паскаля, что-то я начал переписывать на ассемблере (тот самый MACRO-11). Однако, постепенно стало ясно что у нас явные проблемы, как с объёмом кода, так и с производительностью — проще говоря, всё лишние инструкции добавленные компилятором процессору приходилось исполнять.

Для уменьшения объёма кода я даже рассматривал вариант байт-кода, но получалось монструозно, а главное ещё медленнее. Надо было что-то кардинально менять. Сложность кода тоже оказалось намного больше исходных предположений.

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

Насколько помню, надёжное определение номера требовало порядка 1000 опросов в секунду — если по прерываниям, то код на паскале этого уже не вытягивал. Поэтому я переписал это на ассемблере, ну и понеслось.

2. Полный провал

Перед первыми испытаниями протестировали всё очень тщательно, а потом ещё раз просмотрели глазами и прокрутили в голове. У меня была уверенность что всё заработает, а замечания будут только по форме выдачи на экран и т. п. Но внезапно результат был предельно отрицательным — в реальной ситуации всё стало глючить и замерзать, а потом встало колом.

Чтобы воспроизвести ситуацию пришлось делать симулятор активности пользователей, которые одновременно что-то делают. Это показало проблемы — снова не хватало производительности, только сложнее…
Симулирование активности показало, что при одновременных событиях производительности не хватает катастрофически, вдвое или ещё хуже. Но следом встала ещё одна проблема — не успевал вывод на экран, и это почти отдельная история.

3. Отображение

Вывод на монитор/терминал в ДВК — это отдельный контроллер, т. е. это отдельная плата, со своим процессором, который получает данные от основной системы через последовательный интерфейс, интерпретирует управляющие коды VT52 и буквально рисует это в своей видеопамяти. У этого видео-сопроцессора есть своя производительность, а скорость канала ограничена.

У нас же в задании был явный пункт-запрос на отображение актуальной информации по каждой переговорной кабинки. Коих кажется могло быть 24 или 32, сейчас уже не помню. Но суть в том, что экран (25 строк по 80 колонок) был полностью занят «ячейками» с информацией по кабинкам, и в каждой ячейке несколько раз в секунду могло что-то меняться.
Точно я сейчас уже не помню, но у нас были какие-то серьёзные заморочки с отображением всего этого. Главное, что даже при разгоне интерфейса видео-сопроцессор всё равно не успевал всё отрисовать. Точнее говоря, процессор видео-контроллера просто тупо выполнял команды VT52, не хватая звёзд с неба и конкурируя за доступ к видеопамяти с CRT. Поэтому 9600 бод было вполне достаточно чтобы перегрузить его работой. Но были и интересные возможности. Например, внутрь видео-контроллера можно было загрузить и выполнить свой код, которым сделать что-то особенное. Если не ошибаюсь, то я там что-то похачил в режиме работы последовательного интерфейса, а потом ещё и чуток поразвлекался сделав при старте небольшую анимированную заставку.

Пришлось полностью переосмыслить вывод на экран, т. е. на медленный терминал VT-52 через 9600 бод. Много оптимизировать мы не могли. Классический подход, с поиском отличий и выстраивании минимальной последовательности команд для обновления, не проходил — основной процессор просто не успевал этого на фоне остальных задач. На этом шаге был набор флажков для минимизации вывода и циклическое (round-robin) обновление по мере опустошения выходной очереди.

4. Снова производительность

После переделки вывода на терминал стало лучше, но и хуже. Симулятор позволил увидеть, что одновременную активность во всех кабинках мы не тянем. Очень много времени съедали прерывания: примерно 1000 в секунду на опрос датчиков и ещё порядка 1000 на вывод в терминал. Причём по прерываниям иногда происходила существенная работа, приходилось сохранять/восстанавливать все регистры.
Решение пришло достаточно быстро. Я оставил только прерывания от таймера привязав к ним опрос датчиков, а вывод на терминал перевёл в режим опроса в том же обработчике. Дальше ещё раз пришлось переделать логику вывода на терминал чтобы убрать лишние очереди и копирование. Обработчик прерывания стал делать минимум только самых критических действий. Дальнейшую обработку выполняли вторичные обработчики, идеологически похожие на bottom-halves/tasklets в Linux. Но только от переключения контекстов и явной много-поточности пришлось отказаться — диспетчеризация в цикле оказалось вдвое быстрее чем возня с сохранением/восстановлением регистров для переключения потоков.
Затем мы ещё раз внимательно взвесили пути развития событий и их обработки, раздробили на более мелкие шаги и сделали общий цикл обслуживания всех конечных автоматов, но с учётом приоритета шагов/состояний.

Вишенкой, со самым слабым приоритетом я добавил циклическое приращение счётчика — по количеству его приращений можно было точно оценивать загрузку ЦПУ (сколько у него остаётся времени на накрутку счётчика). Это позволило видеть объективную ситуацию и ловить ситуации перегрузки, когда аналог idle-процесса не получает ЦПУ несколько тиков подряд.

5. Снова объём кода

Заработало, но объём кода был почти 16Кб, а ещё нужно было добавить редактор справочника кодов городов с тарифами, фискальный аккумулятор, режим супервизора, возможность печати (вывода в LPT или другой RS-232). Подстройка параметров под аппаратуру и номеронабиратели была опциональной, но если их не выносить в изменяемый настройки, то пришлось бы задавать во время сборки прошивок.

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

Дальше получилось интересное. Нам принесли плату с удвоенным «на коленках» объёмом ПЗУ. Это был выход, а главное — свет в конце тоннеля. Имея запас по объёму где-то за неделю-две мы дописали и отладили оставшееся.

Отдали пробный вариант на недельную тестовую эксплуатацию. Сам я там не присутствовал, но со слов заказчика сначала у операторов была некоторая оторопь, растерянность и боязнь. Тогда фильм «Терминатор-2» был относительно свежим, и народ увидел в стройном и чётком дизайне наших ячеек на экране знакомые мотивы из фильма ;)

Но через день операторы не просто освоились, а уже очень не хотели отдавать прототип. Нареканий не было, но были некоторые «закидоны». Народ не совсем понимал как оно работает и стал просить почти чудес.

А за неделю пробной работы я нашёл способ поместится в 16Кб — просто сжать код перед прошивкой в ПЗУ, а потом распаковывать и выполнять из ОЗУ. Сначала была мысль реализовать кодирование Хаффмана или LZx, но оказалось достаточным совсем примитивного решения: у PDP-11 команды 16-битные и частота их использования сильно разная, поэтому я добавил обратный просмотр и при наличии совпадения/повтора стал кодировать 1-байтовое смещение, а иначе 16-битный код команды, ну и 1-битный флажок перед всем этим. Сжатый таким примитивным способом код поместился в 16Кб с запасом и именно 8-битное смешение давало лучшее сжатие. Ну а 56 основного ОЗУ хватало и на распакованный исполнимый код и всё остальное.

После сдачи проекта я пару раз что-то там правил совсем по мелочи.

Но в какой-то момент было несколько обращений типа «вот тут похоже глючит». Разбирались детально, в итоге показали что контрольные суммы ловили объективные сбои энергонезависимого ОЗУ. Далось это сложнее чем предполагалось — пришлось добавить в прошивку функцию распечатки дампа энергонезависимой памяти и контрольных сумм, а также ввод этих данных. Заказчик сам несколько раз поставил эксперимент: поставил две системы рядом, что-то там распечатывал и вводил, сравнивал. Увидел факт различий и нашёл выпадающий бит, заменил микросхему… После этого стал доверять контрольным суммам и что-то чинил/менял ещё в двух экземплярах.

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

В электронном виде исходников у меня не сохранилось, но как-тоя попросил распечатку и мне её дали. Где-то она лежит, вроде-бы не выкидывал. Очень любопытно посмотреть.

Александр, друг и напарник, через несколько лет погиб в конце 90-х . Он так и остался для всех знавших его замечательным молодым улыбчивым парнем.

27 views·1 share