Параллельные вычисления в ИММ УрО РАН
 
 
                      Файл PROGDEVL.TXT
                  ИПМ им. М. В. Келдыша РАН.
          Отдел ИВСиЛС, сектор эксплуатации МВС-100.
                                                  А. О. Лацис.

             СИСТЕМА-100 - ОС СУПЕР-ЭВМ МВС-100.
             ИНСТРУКЦИЯ ПРИКЛАДНОМУ ПРОГРАММИСТУ
             ПО РАБОТЕ НА МВС-100 В СРЕДЕ Router.

     Здесь и далее первое,  определяющее вхождение термина или
важного понятия выделяется в тексте заглавными буквами.
     Предполагается, что  читатель настоящего документа знаком
с документами:  "Система-100 - ОС супер-ЭВМ МВС-100.  Наиболее
общие понятия. Аннотированный список документов", "Система-100
- ОС супер-ЭВМ МВС-100.  Инструкция пользователю по работе  на
МВС-100".
     Данный документ  посвящен описанию системы обмена сообще-
ниями для программ,  разрабатываемых и выполняемых  на  основе
транспортной сети Router.

СОДЕРЖАНИЕ.
Введение.
1. Структура программного обеспечения.
2. Настройка программы на логическую топологию.
3. Управление ресурсами сети во время счета.
4. Формат обращения к системным функциям.
  4.1. Обмен сообщениями и синхронизация.
  4.2. Управление сервером-концентратором.
5. Графический вывод.
  5.1. Основные понятия для программиста.
  5.2. Предоставляемые режимы и реализации.

Введение.
     Вообще говоря,  разработка программы для  транспьютеропо-
добных  систем включает в себя,  помимо написания программ для
отдельных процессов,  также описание в том или ином виде ЛОГИ-
ЧЕСКОЙ КОНФИГУРАЦИИ,  то есть картины связи между процессами в
задаче,  ФИЗИЧЕСКОЙ КОНФИГУРАЦИИ,  то есть картины связи между
процессорами в системе,  и отображения логической конфигурации
на физическую.  В принципе, теоретически, обе конфигурации мо-
гут  меняться во время счета,  и тогда задача конфигурирования
становится совсем нетривиальной.
     В МВС-100 физическая конфигурация во время счета не меня-
ется. При разработке программ в рамках возможностей транспорт-
ной  сети Router логическая конфигурация всегда одинакова и от
физической конфигурации не зависит (подробнее см. ниже). Нако-
нец,  вся  работа  по отображению предоставляемых пользователю
логических конфигураций на физические выполнена раз и навсегда
системным администратором и скрыта в командах запуска программ
на счет. Это полностью освобождает пользователя от задачи кон-
фигурирования -  требуется только выбрать количество процессо-
ров и распределить между ними работу,  написав  программу  для
каждого. Ниже  рассматривается  используемая  при  этом логика
взаимодействия.

1. Структура программного обеспечения.
     Логически МВС-100 используется как сеть процессоров i860,
связанных линками.
     Это означает,  что  ни на транспьютере процессорного эле-
мента,  ни на управляющей ЭВМ не может выполняться  программа,
написанная  пользователем.  Там  выполняются только компоненты
ОС,  и само наличие этих процессоров от  прикладной  программы
скрыто. Поэтому впредь вместо "процессорный элемент" будем го-
ворить просто "процессор", имея в виду процессорный элемент, в
котором  i860  выполняет  прикладную программу,  а транспьютер
выступает в роли процессора ввода-вывода. Будем также говорить
о  связи  линками  между процессорами,  не оговариваясь всякий
раз, как обстоит дело на физическом уровне.
     Таким образом,  модель вычислений  в  ОС-100  практически
совпадает с транспьютерной.  Перечислим здесь основные понятия
последней, не вдаваясь в их подробное определение, но вводя по
ходу  дела  ограничения и уточнения,  свойственные ОС-100 в ее
нынешнем виде.
     На сети процессоров выполняется ЗАДАЧА, состоящая из ПРО-
ЦЕССОВ, которые обмениваются СООБЩЕНИЯМИ по КАНАЛАМ. В отличие
от чисто транспьютерных систем,  в ОС-100 на каждом процессоре
выполняется только один процесс. Реализован он в виде ПРОГРАМ-
МЫ,  которая  с точки зрения системы программирования является
отдельным загрузочным модулем:  пользователь пишет ее текст на
языке программирования, транслирует и компонует. В тексте явно
выписываются обмены сообщениями с другими программами. Средств
написания программ на сети в целом сейчас не предоставляется.
     Настоящая версия ОС не позволяет порождать процессы дина-
мически: на  каждом  процессоре при запуске задачи выполняется
один и только один процесс.
     В дополнение к возможности обмена сообщениями по каналам,
каждый процесс прикладной задачи в  полном  объеме  пользуется
стандартным вводом/выводом:  файлами, потоками stdin, stdout и
stderr.
     Поскольку каждый процессор выполняет один процесс, реали-
зованный как одна программа, мы будем впредь использовать сло-
ва "процессор",  "процесс" и "программа" как синонимы. В част-
ности,  будем говорить о номере (в смысле п.1) процесса  и/или
программы,  об обмене сообщениями между процессорами или прог-
раммами.
     Введем несколько  важных определений,  связанных с техни-
ческой реализацией упомянутой выше модели вычислений.
     Программа в среде ОС-100 имеет доступ к системе ввода-вы-
вода, включающей:
     - некоторое  подмножество системных запросов ввода/вывода
Unix,
     - средства обмена сообщениями по каналам.
     Для программы,  выполняющейся на i860,  доступ к  системе
ввода/вывода прозрачен, как если бы линки, терминалы и магнит-
ные диски были напрямую доступны из i860.
     Процессоры в рамках одной задачи занумерованы  подряд  от
нуля. Эти номера и используются для адресации сообщений.
     Физическая топология процессорной  сети  также  прозрачна
для задачи пользователя:  каждый процессор связан с каждым па-
рой противонаправленных ВИРТУАЛЬНЫХ КАНАЛОВ. Обмениваясь сооб-
щениями по каналам, программа указывает НОМЕР КАНАЛА, совпада-
ющий с номером процессора,  к которому (от которого)  проложен
этот канал.  Если процессор номер 2 посылает сообщение процес-
сору номер 8, он должен послать его в свой восьмой канал. Про-
цессор номер 8, в свою очередь, должен прочитать его из своего
второго канала.

2. Настройка программы на логическую топологию.
     В виртуально  полносвязной  сети  настройка на логическую
топологию заключается в том,  что программа во время счета уз-
нает свой  номер  процессора  и общее число процессоров (прог-
рамм). Дополнительно системный администратор может (и  должен)
устанавливать некоторые соглашения о системе нумерации,  чтобы
пользователь мог  оптимизировать  наиболее  массовые   обмены,
распределяя программы  по процессорам наилучшим образом.  Так,
рекомендуется нумеровать физически смежные процессоры последо-
вательными номерами. Возможны и более сложные соглашения.
     В ОС-100 информация о номере процессора передается  через
получаемую  программой  командную  строку.  Программа получает
argc == 3, и следующие значения argv:
     - argv[0] - имя файла с программой,
     - argv[1] - номер процессора,
     - argv[2] - общее число процессоров в конфигурации.
     argv[1]  и  argv[2]  представлены как текстовые строки в
десятичном виде, так что:
     (int)atol(argv[1]) есть номер процессора,
     (int)atol(argv[2]) есть число процессоров в конфигурации.

     В программе на Фортране можно использовать  целочисленные
подпрограммы-функции без параметров,  включенные в стандартную
библиотеку:

        n = node_number()
c n теперь равно номеру процессора
        nn = n_of_nodes()
c nn теперь равно числу процессоров в конфигурации.

3. Управление ресурсами сети во время счета.
     Необходимость для  программы в явном управлении ресурсами
сети во время счета вытекает  из  невозможности,  в  некоторых
случаях,  одновременного  открытия  большого (обычно свыше 12)
количества файлов одной программой в управляющей ЭВМ.  Точнее,
одна  многопроцессорная  задача пользователя может открыть од-
новременно не более 12 файлов, где каждое явное открытие файла
на  каждом  процессоре  считается  за  новый открытый файл,  а
stdin, stdout и stderr считаются по одному разу, независимо от
количества процессоров. Добавим к этому числу еще и файлы заг-
рузочных модулей самих программ, которые с точки зрения транс-
портной сети являются в момент начального старта просто данны-
ми,  читаемыми из файлов наравне со  всеми.  Тогда  становится
просто  непонятно,  как именно может загрузиться в вычислитель
программа для 9 и более процессоров, выполняющая хоть какой-то
ввод/вывод. На самом деле, конечно, может, причем значительная
часть работы по разрешению такого рода конфликтов транспортная
сеть  выполняет  автоматически.  Чтобы  программа пользователя
могла успешно разрешить оставшуюся  часть,  рассмотрим  работу
транспортной сети в этом смысле более подробно.
     При запуске задачи  прежде  всего  стартует  транспортная
сеть, то есть управляющий код, выполняющийся на транспьютерах.
Аппарат запуска транспортной сети не создает никакой  нагрузки
на ту  часть  ОС,  которая  способна "не справиться" с большим
числом одновременно открытых файлов. Сразу после старта транс-
портной сети  открываются  общие  для  всех процессоров stdin,
stdout и stderr.
     При отсутствии  каких-либо  мер  предосторожности  работа
транспортной сети начинается с того,  что каждый  ее  узел  на
своем  транспьютере  пытается  открыть  файл  с программой для
i860, прочитать его и загрузить программу. Если ему это удает-
ся,  все  загруженные программы без всякой синхронизации между
собой начинают открывать свои файлы с исходными данными.  Оче-
видно,  что при числе процессоров больше 9 эта процедура обре-
чена на неудачу. Реально трудности начинаются уже на трех про-
цессорах. Проще всего избежать одновременного открытия большо-
го числа файлов,  заставив программы на разных процессорах чи-
тать свои данные по очереди, так, чтобы следующая программа не
начинала открывать файлы с исходными данными,  пока предыдущая
не  прочитает  и  не  закроет своих файлов,  оставив открытыми
только необходимые ей во время счета. Такую дисциплину может в
принципе  реализовать сам пользователь,  синхронизировав прог-
раммы посредством обмена сообщениями, но это не решает вопроса
об  одновременном открытии файлов с самими программами с целью
их загрузки. Кроме того, нежелательно возлагать на пользовате-
ля обязанности, с которыми легко может справиться система.
     Проблема решается следующим образом.  В состав транспорт-
ной  сети  входит  специальный скрытый от прикладной программы
системный процесс - сервер-концентратор ввода-вывода,  который
собирает  запросы на ввод-вывод от всех узлов и передает их по
одному на управляющую ЭВМ.  Сервер-концентратор написан  таким
образом, что он следит за суммарным числом одновременно откры-
тых файлов. Если максимально возможное число файлов уже откры-
то,  и приходит запрос на открытие еще одного, сервер-концент-
ратор просто задерживает выполнение этого запроса до тех  пор,
пока какой-нибудь файл не закроется.  Выдавшая задержанный та-
ким образом запрос программа просто  "зависает"  на  системном
вызове  "открыть  файл" до закрытия какого-либо другого файла.
По крайней мере, проблему открытия файлов с загрузочными моду-
лями это решает:  ведь после загрузки модуля файл с ним закры-
вается, автоматически разблокируется отложенный запрос на заг-
рузку следующего и т.  д. Много хуже обстоит дело с ситуацией,
когда загруженная на один  из  процессоров  программа  открыла
предельно  допустимое  число  файлов до того,  как загрузилась
программа на соседний процессор. Если открывшая файлы програм-
ма ждет сообщений от процессора, который не может загрузиться,
неизбежен deadlock.
     Эту особенность сети следует учитывать в прикладных прог-
раммах:  каждая из программ многопроцессорной задачи должна  в
начале  работы,  до  обмена  сообщениями  по каналам с другими
программами, закрыть как можно больше файлов, из которых чита-
лись исходные данные.  В противном случае возможны deadlock'и,
в том числе "плавающие".
     Имеется также  проблема в реализации непрерываемых после-
довательностей действий с системой ввода-вывода. Например, ес-
ли каждая из программ прикладной задачи периодически дописыва-
ет что-то в общий для всех файл с  протоколом  запуска,  жела-
тельно,  чтобы  строки выдач от разных программ не перемешива-
лись.  Эта проблема также решается в сервере-концентраторе до-
бавлением двух системных вызовов: "захватить сервер" и "отдать
сервер".  Если сервер  захвачен,  он  обслуживает  запросы  на
ввод-вывод  только от захватившего его процессора.  Выполнение
остальных запросов  откладывается  (соответствующая  программа
"зависает"  на  запросе,  пока захватившая сервер программа не
отдаст его и задержанный запрос не будет обработан).
     Типичный способ  борьбы с deadlock'ами из-за попыток отк-
рыть одновременно слишком много файлов, таким образом, следую-
щий:
     - в начале работы программы пользователя на всех  процес-
сорах прогоняют  через себя сообщение,  сначала в порядке воз-
растания номеров, затем обратно. Прямой ход сообщения гаранти-
рует, что  все программы загрузились.  На обратном ходу каждая
программа, получив сообщение от процессора с большим  номером,
захватывает сервер,  считывает свои исходные данные, закрывает
файлы и отдает сервер,  после чего шлет сообщение процессору с
меньшим номером.

4. Формат обращения к системным функциям.
     Прототипы описываемых здесь функций находятся в:

          #include 

     Сами функции включены в стандартную библиотеку libi860.a.
Возможно обращение из программ на C и  Фортране.  Функции  де-
лятся на 2 группы: функции обмена сообщениями  и синхронизации
и функции управления сервером-концентратором.

4.1. Обмен сообщениями и синхронизация.
     Имеется всего 6 функций обмена сообщениями между процес-
сами:
     -  запустить посылку указанного массива в указанный про-
цесс,
     - запустить прием в указанный массив из указанного  про-
цесса,
     - ждать конца посылки,
     - ждать конца приема,
     - проверить кончилась ли посылка,
     - проверить, кончился ли прием.
     Одновременно  процесс может запустить сколько угодно по-
сылок и приемов от разных процессов, но только один  обмен  в
каждом  из  направлений с одним и тем же процессом. Если про-
цесс A запустил прием сообщения от процесса B, он  не  сможет
запустить еще один прием от него же, пока первый обмен не за-
вершится.  То  же  верно  и  для передачи. Поэтому в функциях
"ждать конца" и "проверить, кончился ли" в качестве  парамет-
ра,  говорящего, о каком именно обмене идет речь, указывается
номер процесса, обмен с которым был запущен.
     Длина сообщения указывается как при приеме,  так  и  при
передаче,  и  должна быть согласована на обоих концах. Нельзя
передать некоторое сообщение одним обращением к функции "пос-
лать" и принять его двумя обращениями  к  функции  "принять",
указав каждый раз половину длины.
     Если  длина принимаемого сообщения больше, чем длина по-
сылаемого, принимающий процесс будет ждать вечно. Если  длина
принимаемого сообщения меньше, чем длина посылаемого, на при-
емном конце будет протерта память.
     Запуск приема происходит логически мгновенно и не  зави-
сит  от состояния сети, физической удаленности отправителя от
получателя и действий отправителя.
     Запуск передачи возможен в любое время,  но завершится он
только после того,  как получатель запустил прием.  Так,  если
процесс A хочет запустить передачу в процесс B, а процесс B не
запускает прием из A, обращение к функции "запустить передачу"
в процессе A никогда не завершится.
     Завершение передачи не свидетельствует о том, что  сооб-
щение  уже доставлено адресату, но гарантирует, что от отпра-
вителя оно уже ушло. Для физически смежных процессов эти  два
условия совпадают.
     Сообщения  между одними и теми же двумя процессами в од-
ном и том же направлении не могут в процессе  доставки  обог-
нать друг друга.
     Сформулированные здесь свойства механизма доставки сооб-
щений, а именно:
     - требование одинаковости длины передаваемого и принима-
емого сообщений,
     - строгая последовательность доставки при обмене в одном
направлении между одними и теми же двумя процессами,
     - запуск обмена по взаимной готовности к нему  на  обоих
концах
позволяют  говорить  о приведенной схеме как о схеме с вирту-
альными каналами от каждого процесса к каждому. Однако, в от-
личие от схемы, принятой, например, в Helios, и тоже  по  су-
ществу виртуально-канальной, в данной схеме не требуется опи-
сывать  виртуальную топологию, поскольку все возможные каналы
всегда существуют.
     Формат обращения:

        extern int r_write( int /*proc*/,
                          void* /*buffer*/, int /*length*/ );
     - запустить посылку. Первый аргумент - номер процесса, в
который послать, второй - адрес передаваемого массива, третий
- его длина.
     Возвращаемое значение:
      0 - обмен запущен,
     -1 - несуществующий номер процесса,
     -2 - предыдущая посылка не завершена.

        extern int r_read( int /*proc*/,
                         void* /*buffer*/, int /*length*/ );
     - запустить прием. Смысл аргументов  тот  же,  что  и  в
r_write.
     Возвращаемое значение:
      0 - обмен запущен,
     -1 - несуществующий номер процесса,
     -2 - предыдущий прием не завершен.

        extern int w_write( int /*proc*/ );
     -  ждать конца посылки. Аргумент - номер процесса, конца
посылки которому ждать.
     Возвращаемое значение:
      0 - несуществующий номер процесса,
      1 - конец посылки.
     Попытка повторно подождать заведомо завершенного обмена
немедленно завершается с кодом 1.

        extern int w_read( int /*proc*/ );
     -  ждать конца приема.  Аргумент - номер процесса, конца
приема от которого ждать.
     Возвращаемое значение:
      0 - несуществующий номер процесса,
      1 - конец приема.
     Попытка повторно подождать заведомо завершенного обмена
немедленно завершается с кодом 1.

        extern int t_write( int /*proc*/ );
     - проверить, кончилась ли посылка. Аргумент - номер про-
цесса, завершение посылки которому проверить.
     Возвращаемое значение:
      0 - несуществующий номер процесса, или посылка не завер-
шена,
      1 - конец посылки.

        extern int t_read( int /*proc*/ );
     - проверить, кончился ли прием. Аргумент - номер процес-
са, завершение приема от которого проверить.
     Возвращаемое значение:
      0 - несуществующий номер процесса, или прием не завершен,
      1 - конец приема.

     Для каждой из описанных выше функций имеется  Фортранный
эквивалент,  с тем же именем, но с одним дополнительным пара-
метром СПЕРЕДИ - целочисленным кодом ответа:

     CALL W_WRITE( IRET, N )
     ...

4.2. Управление сервером-концентратором.
     При работе с ресурсами управляющей ЭВМ иногда  возникает
необходимость  сделать  некоторую последовательность действий
не прерываемой другими процессами (например, дописать в конец
некоторого общего для всех файла). Для этого имеются две фун-
кции: захватить сервер ввода/вывода, то есть закрыть доступ к
нему от остальных процессов, и освободить сервер.

        extern void grab_server( void ); - захватить сервер,
        extern void ungrab_server( void ); - освободить.

     Прототипы - тоже в routelib.h. Попытки повторного захва-
та и повторного освобождения одним и тем же процессом игнори-
руются.

5. Графический вывод.

5.1. Основные понятия для программиста.
     Доступные прикладной программе внешние переменные и функ-
ции описаны в bgraphic.h. Общая схема работы приводится ниже.
     Перед началом работы следует инициализировать графический
экран. В  принятой  сейчас  реализации на основе системы X это
означает создание окна, в котором будет происходить рисование.
     Это делается обращением к функции

     extern int bgr_init( int mode, int *s_x, int *s_y );

     Здесь mode - номер требуемого графического режима (в сво-
ей нумерации,  принятой в данной библиотеке,  см. ниже), s_x и
s_y - возвращаемые размеры экрана в пикселах.  Функция возвра-
щает значение - код ответа: BGR_OK соответствует успешному за-
вершению, о прочих значениях см. ниже.
     Во всех режимах предполагается дисплей  класса  "псевдоц-
вет" с форматом видеопамяти "1 байт на пиксел". Для управления
палитрой используется функция:

     extern void bgr_setpal( int c, int cr, int cg, int cb );

     Здесь c - значение пиксела,  cr, cg, cb - компоненты цве-
та. Диапазон значений компонент цвета ни к чему не приводится:
значения непосредственно поступают в видеоадаптер.
     Для записи  в  видеопамять строки растра,  сформированной
прикладной программой, используется функция:

     extern void bgr_write( int xto, int yto, int xsize,
                            char *start );

     Здесь (xto, yto) - точка начала строки, xsize - длина за-
писываемой строки, start - указатель на строку.
     Также предоставляется возможность закрасить заданным цве-
том прямоугольник со сторонами, параллельными сторонам экрана:

     extern void bgr_rect( int c, int xmin, int ymin,
                           int xmax, int ymax );

и написать горизонтальную строку текста заданным цветом в за-
данной позиции:
     extern void bgr_text( int c, int xto, int yto,
                           char *string );

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

     extern void bgr_flbuf( int xmin, int ymin,
                            int xmax, int ymax );

     Аргументы этой функции задают так называемый динамический
прямоугольник со  сторонами,  параллельными  сторонам  экрана.
Сразу после обращения к bgr_flbuf невидимая видеопамять, в ко-
торой программа формирует изображение, в пределах динамическо-
го  прямоугольника  заполняется  нулями,  а за его пределами -
сохраняется.  Такая схема помогает повысить эффективность  для
некоторых способов двойной буферизации.
     Однопроцессные возможности библиотеки этим исчерпываются.
Рассмотрим  организацию работы многих процессов пользователя в
рамках системы Router.
     Прежде всего,  все процессы равноправны и симметричны при
вызове функций библиотеки. Внутри себя, конечно, Router разли-
чает  первое и последующие обращения к bgr_init.  Тот процесс,
который успел обратиться первым,  определяет, скольким процес-
сам разрешено на этот раз совместно работать с графикой. Чтобы
сделать это,  он должен перед обращением к bgr_init  присвоить
число процессов (включая себя), которые будут рисовать вместе,
внешней переменной NPROC_REQUIRED.  По умолчанию  ее  значение
равно 1.
     Каждый следующий процесс, выполнивший bgr_init, будет ре-
гистрироваться  как  подключившийся  к графике.  Если их будет
слишком много (больше,  чем первый указал в своем NPROC_REQUI-
RED), подключения не произойдет, bgr_init вернет аварийный код
ответа BGR_TOOMANY.
     Поскольку нельзя, вообще говоря, знать, кто будет первым,
все рисующие процессы одной программы должны указывать одно  и
то же значение NPROC_REQUIRED.
     Любой процесс, подключение которого прошло успешно, после
завершения bgr_init получит во внешней переменной NPROC_ACTUAL
действительное  число  процессов,  подключения  которых  будет
ждать сервер.  Ждать он их будет при каждом bgr_flbuf: очеред-
ное копирование буфера в экран произойдет только тогда,  когда
этого захотят все NPROC_ACTUAL процессов.  При этом динамичес-
кие прямоугольники суммируются:  берется минимальный, объемлю-
щий все заказанные.

5.2. Предоставляемые режимы и реализации.
     Реализация на базе X-графики - единственная, предоставля-
емая в настоящее время.
     Режимов несколько, чем больше номер, тем большего размера
открывается окно (режим 0 - 320 на 200). Глубина видеопамяти -
всегда требуется 8 бит на пиксел (иначе не произойдет  инициа-
лизация), балльность пушек - 256.